Skip to content

Instantly share code, notes, and snippets.

@co3moz
Last active July 17, 2019 02:47
Show Gist options
  • Save co3moz/fa7a64da34236127f2894031a3179474 to your computer and use it in GitHub Desktop.
Save co3moz/fa7a64da34236127f2894031a3179474 to your computer and use it in GitHub Desktop.
Locking mechanism for critical tasks with basic deadlock prevention and acquire timeout features
export class Lock {
private chain: Promise<any> = Promise.resolve(null);
busy = false;
acquire(deadlockSafeTimeout = 0, acquireTimeout = 0) {
let acquireTimeoutId: any = null;
let acquireFailed = false;
let unlock: () => void;
let unlockingPromise = new Promise(r => unlock = r);
let chainWaitingPromise = this.chain.then(() => {
if (acquireFailed) return unlock();
this.busy = true;
let deadlockSafeTimeoutId = deadlockSafeTimeout ? setTimeout(Lock.deadlock, deadlockSafeTimeout, unlock) : null;
if (acquireTimeoutId) clearTimeout(acquireTimeoutId);
return () => { // the user unlocking function
if (deadlockSafeTimeoutId) clearTimeout(deadlockSafeTimeoutId);
unlock();
}
});
this.chain = chainWaitingPromise.then(() => unlockingPromise).then(() => this.busy = false);
if (acquireTimeout) {
return Promise.race([ // make race between chainWaitingPromise and acquireTimeout
chainWaitingPromise,
new Promise((_, reject) => {
acquireTimeoutId = setTimeout(() => {
acquireFailed = true;
reject(new Error('acquire timeout occurred!'));
}, acquireTimeout);
})]);
}
return chainWaitingPromise;
}
static deadlock(fn: () => void) { // the deadlock unlocking function
console.error('WARNING! Deadlock detected!');
fn();
}
}
const delay = ms => new Promise(r => setTimeout(r, ms));
const lock = new Lock();
async function task(i: number) {
let unlock;
console.log('task(%d)', i);
try {
unlock = await lock.acquire(1000, 1000);
console.log('task(%d) lock.acquire()', i);
await delay(500);
} catch (e) {
console.log(e.message);
} finally {
if (unlock) {
console.log('task(%d) unlock()', i);
unlock();
}
}
}
for (let i = 0; i < 5; i++) {
task(i);
}
/*
task(0)
task(1)
task(2)
task(3)
task(4)
task(0) lock.acquire()
task(0) unlock()
task(1) lock.acquire()
acquire timeout occurred!
acquire timeout occurred!
acquire timeout occurred!
task(1) unlock()
*/
const lock = new Lock();
async function task(i: number) {
let unlock;
try {
unlock = await lock.acquire(1000);
} catch (e) {
console.log(e.message);
} finally {
if (!unlock) { // oops, mistake?
unlock();
}
}
}
task();
/*
WARNING! Deadlock detected!
*/
const lock = new Lock();
async function not_important_task(i: number) {
while(true) {
await delay(1000);
if (lock.busy) continue; // can lock be acquired immediately?
const unlock = await lock.acquire();
// some task
unlock();
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment