dahlia-concurrency
Version:
High-level concurrency primitives and patterns for Node.js using worker_threads (queues, semaphore, mutex, worker pool, scheduler, and more)
54 lines (48 loc) • 1.5 kB
JavaScript
const { threadId, isMainThread } = require("node:worker_threads");
class Mutex {
constructor(sharedBuffer) {
if (!(sharedBuffer instanceof SharedArrayBuffer)) {
throw new Error("Invalid shared buffer: Must be a SharedArrayBuffer");
}
this.data = new Int32Array(sharedBuffer);
if (isMainThread) {
Atomics.store(this.data, 0, 0);
}
this.ownerThreadId = null;
// 1 - is locked
// 0 - is unlocked
}
lock(timeout = 5000) {
const start = Date.now();
while (Atomics.compareExchange(this.data, 0, 0, 1) !== 0) {
const waited = Atomics.wait(this.data, 0, 1, timeout);
if (Date.now() - start >= timeout || waited === "timed-out") {
throw new Error("Mutex lock timeout");
}
}
this.ownerThreadId = threadId;
}
unlock() {
if (this.ownerThreadId !== threadId) {
throw new Error(
"unlock: Only the thread that locked the mutex can unlock it.",
);
}
if (Atomics.load(this.data, 0) === 0) {
throw new Error("unlock: Mutex is already unlocked.");
}
Atomics.store(this.data, 0, 0);
Atomics.notify(this.data, 0, 1); // count is equal 1 because we free only one thread
this.ownerThreadId = null;
}
acquire() {
return new Promise((resolve) => {
while (Atomics.compareExchange(this.data, 0, 0, 1) !== 0) {
Atomics.wait(this.data, 0, 1);
}
this.ownerThreadId = threadId;
resolve();
});
}
}
module.exports = { Mutex };