Last active
May 8, 2024 16:08
-
-
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.
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 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