Skip to content

Instantly share code, notes, and snippets.

@Hadaward
Last active May 8, 2024 16:08
Show Gist options
  • Select an option

  • Save Hadaward/56e0b812bcdf2c8dd4baf8bedbf44845 to your computer and use it in GitHub Desktop.

Select an option

Save Hadaward/56e0b812bcdf2c8dd4baf8bedbf44845 to your computer and use it in GitHub Desktop.
A mutex concept implemented in javascript, to ensure you don't have unexpected value changes.
export type MutexGuard<T> = Readonly<{
id: string,
get: () => T,
set: (value: T) => void,
unlock: () => void
}>;
export type Mutex<T> = Readonly<{
lock(): Promise<MutexGuard<T>>
}>;
export function mutex<T>(initialValue: T): Mutex<T> {
let value = structuredClone(initialValue);
let releasePromise: Promise<undefined> = new Promise(r => r(undefined));
let lockId: string = "";
return Object.freeze({
async lock(): Promise<MutexGuard<T>> {
await releasePromise;
if (lockId !== "") {
return await this.lock();
}
let resolver: (value: undefined) => void;
releasePromise = new Promise<undefined>(r => resolver = r);
lockId = crypto.randomUUID();
return Object.freeze({
id: lockId,
get(): T {
if (lockId !== this.id) {
throw new ReferenceError("Can't get value on unlocked mutex");
}
return structuredClone(value);
},
set(newValue: T) {
if (lockId !== this.id) {
throw new ReferenceError("Can't set value on unlocked mutex");
}
value = structuredClone(newValue);
},
unlock() {
if (lockId !== this.id) {
throw new ReferenceError("Can't unlock mutex from invalid reference");
}
lockId = "";
resolver(undefined);
}
})
}
})
}
// test
//import { type Mutex, mutex } from "./mutex.js";
const count: Mutex<number> = mutex(0);
function delay(ms: number) {
return new Promise(resolve => { setTimeout(resolve, ms); });
}
async function doJob(id: number) {
if (Math.random() > 0.5) {
await delay(Math.floor(Math.random() * 2000));
}
let guard = await count.lock();
console.log(`[JOB ${id}] Mutex locked`);
guard.set(guard.get() + 1);
console.log(`[JOB ${id}] Mutex value changed to ${guard.get()}`);
console.log(`[JOB ${id}] Sending mutex value to a api`);
const res = await fetch(`https://postman-echo.com/get?value=${guard.get()}`);
const data = await res.json();
console.log(`[JOB ${id}] ${Number(data.args.value) === guard.get() ? 'Mutex value matches api value' : 'Mutex value doesn\'t matches api value'}`);
guard.unlock();
console.log(`[JOB ${id}] Mutex unlocked`);
}
for (let i=0; i<8; i++)
doJob(i + 1);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment