Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions src/EventStore.js
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,17 @@ class EventStore extends events.EventEmitter {
this.storage.close();
}

/**
* Returns true if the storage is currently locked by a writer process.
* Useful when this store is opened read-only to check whether a writer holds an exclusive lock.
*
* @api
* @returns {boolean}
*/
isLocked() {
return this.storage.isLocked();
}

/**
* Override EventEmitter.on() to delegate 'preCommit' and 'preRead' event registrations
* to the underlying storage, so that `eventstore.on('preCommit', handler)` works naturally.
Expand Down
12 changes: 12 additions & 0 deletions src/Storage/ReadableStorage.js
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ class ReadableStorage extends events.EventEmitter {
this.hmac = createHmac(config.hmacSecret);

this.dataDirectory = path.resolve(config.dataDirectory);
this.lockFile = path.resolve(this.dataDirectory, this.storageFile + '.lock');

const partitionDefaults = { readBufferSize: DEFAULT_READ_BUFFER_SIZE };
this.partitionConfig = Object.assign(partitionDefaults, config);
Expand Down Expand Up @@ -157,6 +158,17 @@ class ReadableStorage extends events.EventEmitter {
return this.index.length;
}

/**
* Returns true if the storage is currently locked by a writer process.
* Useful for read-only clients to check whether a writer holds an exclusive lock.
*
* @api
* @returns {boolean}
*/
isLocked() {
return fs.existsSync(this.lockFile);
}

/**
* Scan partitions and secondary index files; emit 'index-created' for each found index.
* @param {function} done Called when both scans finish.
Expand Down
7 changes: 5 additions & 2 deletions src/Storage/WritableStorage.js
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,6 @@ class WritableStorage extends ReadableStorage {
ensureDirectory(config.dataDirectory);
super(storageName, config);

this.lockFile = path.resolve(this.dataDirectory, this.storageFile + '.lock');
this._lockMode = config.lock;
this.partitioner = config.partitioner;
}
Expand Down Expand Up @@ -226,14 +225,18 @@ class WritableStorage extends ReadableStorage {
if (this.locked) {
return false;
}
if (this.isLocked()) {
throw new StorageLockedError(`Storage ${this.storageFile} is locked by another process`);
}
try {
fs.mkdirSync(this.lockFile);
this.locked = true;
} catch (e) {
/* istanbul ignore if */
/* istanbul ignore next */
if (e.code !== 'EEXIST') {
throw new Error(`Error creating lock for storage ${this.storageFile}: ` + e.message);
}
/* istanbul ignore next */
throw new StorageLockedError(`Storage ${this.storageFile} is locked by another process`);
}
return true;
Expand Down
38 changes: 38 additions & 0 deletions test/EventStore.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -277,6 +277,44 @@ describe('EventStore', function() {
});
});

it('isLocked() returns true when a writer is open', function(done) {
eventstore = new EventStore({
storageDirectory
});

eventstore.once('ready', () => {
const readstore = new EventStore({
storageDirectory,
readOnly: true
});
readstore.once('ready', () => {
expect(readstore.isLocked()).to.be(true);
readstore.close();
done();
});
});
});

it('isLocked() returns false when no writer is open', function(done) {
eventstore = new EventStore({
storageDirectory
});

eventstore.once('ready', () => {
eventstore.close();
const readstore = new EventStore({
storageDirectory,
readOnly: true
});
readstore.once('ready', () => {
expect(readstore.isLocked()).to.be(false);
readstore.close();
eventstore = null;
done();
});
});
});

describe('commit', function() {

it('throws when no stream name specified', function() {
Expand Down
37 changes: 37 additions & 0 deletions test/Storage.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -1225,6 +1225,43 @@ describe('Storage', function() {
}).to.not.throwError();
});

it('isLocked() returns true when a writer holds the lock', function(){
storage = createStorage();
storage.open();
const reader = createReader();
reader.open();
expect(reader.isLocked()).to.be(true);
reader.close();
});

it('isLocked() returns false when no writer holds the lock', function(){
storage = createStorage();
storage.open();
storage.close();
const reader = createReader();
reader.open();
expect(reader.isLocked()).to.be(false);
reader.close();
});

it('isLocked() returns false on a writer before open', function(){
storage = createStorage();
expect(storage.isLocked()).to.be(false);
});

it('isLocked() returns true on a writer after open', function(){
storage = createStorage();
storage.open();
expect(storage.isLocked()).to.be(true);
});

it('isLocked() returns false on a writer after close', function(){
storage = createStorage();
storage.open();
storage.close();
expect(storage.isLocked()).to.be(false);
});

it('allows multiple readers for one storage', function () {
storage = createStorage();
storage.open();
Expand Down