Skip to content

Instantly share code, notes, and snippets.

@matheusb-comp
Created April 12, 2020 18:08
Show Gist options
  • Save matheusb-comp/b07748b565ae29790fd8c7a2feac1bf4 to your computer and use it in GitHub Desktop.
Save matheusb-comp/b07748b565ae29790fd8c7a2feac1bf4 to your computer and use it in GitHub Desktop.
Mutex lock class for JavaScript
/**
* 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