Created
April 12, 2020 18:08
-
-
Save matheusb-comp/b07748b565ae29790fd8c7a2feac1bf4 to your computer and use it in GitHub Desktop.
Mutex lock class for JavaScript
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* Mutual-exclusion lock acquired through a promise. Based on: | |
* https://thecodebarbarian.com/mutual-exclusion-patterns-with-node-promises | |
*/ | |
class Lock { | |
constructor(timeout = 0) { | |
this._meta = null | |
this._token = null | |
this._locked = false | |
this._timeout = parseInt(timeout) || 0 | |
this._ee = new EventEmitter() | |
// Allow this Event Emitter to hold any amount of listeners (default is 10) | |
// https://nodejs.org/api/events.html#events_emitter_setmaxlisteners_n | |
this._ee.setMaxListeners(0) | |
} | |
get meta() { | |
return this._meta | |
} | |
get locked() { | |
return this._locked | |
} | |
get timeout() { | |
return this._timeout | |
} | |
set timeout(value = 0) { | |
if (isNaN(parseInt(value))) return | |
this._timeout = value | |
} | |
// Set the object as locked (internal) | |
_lock(meta = null) { | |
this._locked = true | |
this._token = randStr() | |
this._meta = meta | |
} | |
// Set the object as unlocked (internal) | |
_unlock() { | |
this._meta = null | |
this._token = null | |
this._locked = false | |
} | |
// Release the lock upon receiving the correct token | |
release(token) { | |
if (token == this._token) { | |
this._unlock() | |
setImmediate(() => this._ee.emit('release')) | |
} | |
} | |
acquire(meta = null) { | |
return new Promise((resolve, reject) => { | |
// Timeout to reject promise if lock not acquired | |
let rejectTimeout = null | |
// Check lock status and acquire instantly if not locked | |
// Safe because JavaScript doesn't interrupt synchronous executions | |
if (!this._locked) { | |
this._lock(meta) | |
clearTimeout(rejectTimeout) | |
return resolve(this._token) | |
} | |
// If locked, keep waiting for the 'released' event and trying again | |
const tryAcquire = () => { | |
if (!this._locked) { | |
clearTimeout(rejectTimeout) | |
this._lock(meta) | |
this._ee.removeListener('release', tryAcquire) | |
return resolve(this._token) | |
} | |
} | |
this._ee.on('release', tryAcquire) | |
// Setup the reject timeout, if requested | |
if (parseInt(this._timeout) || false) { | |
rejectTimeout = setTimeout(() => { | |
clearTimeout(rejectTimeout) | |
this._ee.removeListener('release', tryAcquire) | |
reject(new Error(`Lock wait timeout: ${this._timeout}ms`)) | |
}, this._timeout) | |
} | |
}) | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment