Last active
July 17, 2019 02:47
-
-
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
This file contains hidden or 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
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(); | |
} | |
} |
This file contains hidden or 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
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() | |
*/ |
This file contains hidden or 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
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! | |
*/ |
This file contains hidden or 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
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