Created
June 26, 2025 04:43
-
-
Save nandordudas/cd59a8fda46ba22d72152a686873d1f1 to your computer and use it in GitHub Desktop.
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
// ============================================================================= | |
// 1. MODERN MUTEX IMPLEMENTATION WITH NATIVE PRIVATE FIELDS & Promise.withResolvers | |
// ============================================================================= | |
class Mutex { | |
#locked = false; | |
#waitQueue: Array<() => void> = []; | |
async lock(): Promise<void> { | |
if (!this.#locked) { | |
this.#locked = true; | |
return; | |
} | |
const { promise, resolve } = Promise.withResolvers<void>(); | |
this.#waitQueue.push(resolve); | |
return promise; | |
} | |
unlock(): void { | |
if (this.#waitQueue.length > 0) { | |
const next = this.#waitQueue.shift()!; | |
next(); | |
} else { | |
this.#locked = false; | |
} | |
} | |
async withLock<T>(fn: () => Promise<T>): Promise<T> { | |
await this.lock(); | |
try { | |
return await fn(); | |
} finally { | |
this.unlock(); | |
} | |
} | |
get isLocked(): boolean { | |
return this.#locked; | |
} | |
get queueLength(): number { | |
return this.#waitQueue.length; | |
} | |
} | |
// ============================================================================= | |
// 1.1. ADVANCED MUTEX MANAGER USING MAP FOR MULTIPLE NAMED LOCKS | |
// ============================================================================= | |
class MutexManager { | |
#mutexes = new Map<string, Mutex>(); | |
getMutex(key: string): Mutex { | |
if (!this.#mutexes.has(key)) { | |
this.#mutexes.set(key, new Mutex()); | |
} | |
return this.#mutexes.get(key)!; | |
} | |
async withLock<T>(key: string, fn: () => Promise<T>): Promise<T> { | |
const mutex = this.getMutex(key); | |
return await mutex.withLock(fn); | |
} | |
deleteMutex(key: string): boolean { | |
return this.#mutexes.delete(key); | |
} | |
clear(): void { | |
this.#mutexes.clear(); | |
} | |
get size(): number { | |
return this.#mutexes.size; | |
} | |
keys(): IterableIterator<string> { | |
return this.#mutexes.keys(); | |
} | |
} | |
// ============================================================================= | |
// 1.2. RESOURCE-BASED MUTEX USING WEAKMAP FOR AUTOMATIC CLEANUP | |
// ============================================================================= | |
class ResourceMutex { | |
static #mutexMap = new WeakMap<object, Mutex>(); | |
static async withLock<T>(resource: object, fn: () => Promise<T>): Promise<T> { | |
let mutex = ResourceMutex.#mutexMap.get(resource); | |
if (!mutex) { | |
mutex = new Mutex(); | |
ResourceMutex.#mutexMap.set(resource, mutex); | |
} | |
return await mutex.withLock(fn); | |
} | |
static getMutex(resource: object): Mutex { | |
let mutex = ResourceMutex.#mutexMap.get(resource); | |
if (!mutex) { | |
mutex = new Mutex(); | |
ResourceMutex.#mutexMap.set(resource, mutex); | |
} | |
return mutex; | |
} | |
} | |
// ============================================================================= | |
// 2. RACE CONDITION EXAMPLE (PROBLEMATIC CODE) | |
// ============================================================================= | |
class BankAccount { | |
private balance = 0; | |
// PROBLEMATIC: Race condition possible | |
async withdrawUnsafe(amount: number): Promise<boolean> { | |
console.log(`Attempting to withdraw $${amount}, current balance: $${this.balance}`); | |
// Simulate async operation (database check, etc.) | |
await new Promise(resolve => setTimeout(resolve, 10)); | |
if (this.balance >= amount) { | |
// Another withdrawal could happen here before we subtract! | |
await new Promise(resolve => setTimeout(resolve, 5)); | |
this.balance -= amount; | |
console.log(`Withdrawal successful. New balance: $${this.balance}`); | |
return true; | |
} | |
console.log(`Insufficient funds. Balance: $${this.balance}`); | |
return false; | |
} | |
deposit(amount: number): void { | |
this.balance += amount; | |
} | |
getBalance(): number { | |
return this.balance; | |
} | |
} | |
// ============================================================================= | |
// 3. RACE CONDITION DEMONSTRATION | |
// ============================================================================= | |
async function demonstrateRaceCondition() { | |
console.log("=== RACE CONDITION DEMO ==="); | |
const account = new BankAccount(); | |
account.deposit(100); | |
console.log(`Initial balance: $${account.getBalance()}`); | |
// Two concurrent withdrawals of $60 each | |
// Both might see balance of $100 and both might succeed! | |
const withdrawal1 = account.withdrawUnsafe(60); | |
const withdrawal2 = account.withdrawUnsafe(60); | |
const [result1, result2] = await Promise.all([withdrawal1, withdrawal2]); | |
console.log(`Withdrawal 1 result: ${result1}`); | |
console.log(`Withdrawal 2 result: ${result2}`); | |
console.log(`Final balance: $${account.getBalance()}`); | |
console.log("Problem: Balance might be negative!\n"); | |
} | |
// ============================================================================= | |
// 4. THREAD-SAFE VERSION USING MUTEX | |
// ============================================================================= | |
class SafeBankAccount { | |
#balance = 0; | |
#mutex = new Mutex(); | |
// SAFE: Using mutex to prevent race conditions | |
async withdraw(amount: number): Promise<boolean> { | |
return await this.#mutex.withLock(async () => { | |
console.log(`Attempting to withdraw $${amount}, current balance: $${this.#balance}`); | |
// Simulate async operation | |
await new Promise(resolve => setTimeout(resolve, 10)); | |
if (this.#balance >= amount) { | |
await new Promise(resolve => setTimeout(resolve, 5)); | |
this.#balance -= amount; | |
console.log(`Withdrawal successful. New balance: $${this.#balance}`); | |
return true; | |
} | |
console.log(`Insufficient funds. Balance: $${this.#balance}`); | |
return false; | |
}); | |
} | |
async deposit(amount: number): Promise<void> { | |
await this.#mutex.withLock(async () => { | |
this.#balance += amount; | |
console.log(`Deposited $${amount}. New balance: $${this.#balance}`); | |
}); | |
} | |
getBalance(): number { | |
return this.#balance; | |
} | |
} | |
// ============================================================================= | |
// 5. SAFE VERSION DEMONSTRATION | |
// ============================================================================= | |
async function demonstrateMutexSolution() { | |
console.log("=== MUTEX SOLUTION DEMO ==="); | |
const account = new SafeBankAccount(); | |
await account.deposit(100); | |
console.log(`Initial balance: $${account.getBalance()}`); | |
// Two concurrent withdrawals - mutex ensures they happen sequentially | |
const withdrawal1 = account.withdraw(60); | |
const withdrawal2 = account.withdraw(60); | |
const [result1, result2] = await Promise.all([withdrawal1, withdrawal2]); | |
console.log(`Withdrawal 1 result: ${result1}`); | |
console.log(`Withdrawal 2 result: ${result2}`); | |
console.log(`Final balance: $${account.getBalance()}`); | |
console.log("Solution: Only one withdrawal succeeded!\n"); | |
} | |
// ============================================================================= | |
// 6. ADVANCED TIMED MUTEX WITH MODERN FEATURES | |
// ============================================================================= | |
class TimedMutex { | |
#locked = false; | |
#waitQueue = new Map<number, { | |
resolve: () => void; | |
reject: (error: Error) => void; | |
timeout: NodeJS.Timeout; | |
}>(); | |
#nextId = 0; | |
async lock(timeoutMs = 5000): Promise<void> { | |
if (!this.#locked) { | |
this.#locked = true; | |
return; | |
} | |
const { promise, resolve, reject } = Promise.withResolvers<void>(); | |
const id = this.#nextId++; | |
const timeout = setTimeout(() => { | |
if (this.#waitQueue.has(id)) { | |
this.#waitQueue.delete(id); | |
reject(new Error(`Mutex lock timeout after ${timeoutMs}ms`)); | |
} | |
}, timeoutMs); | |
this.#waitQueue.set(id, { resolve, reject, timeout }); | |
return promise; | |
} | |
unlock(): void { | |
if (this.#waitQueue.size > 0) { | |
const [id, waiter] = this.#waitQueue.entries().next().value; | |
this.#waitQueue.delete(id); | |
clearTimeout(waiter.timeout); | |
this.#locked = true; // Keep locked for next waiter | |
waiter.resolve(); | |
} else { | |
this.#locked = false; | |
} | |
} | |
async withLock<T>(fn: () => Promise<T>, timeoutMs = 5000): Promise<T> { | |
await this.lock(timeoutMs); | |
try { | |
return await fn(); | |
} finally { | |
this.unlock(); | |
} | |
} | |
get isLocked(): boolean { | |
return this.#locked; | |
} | |
get queueLength(): number { | |
return this.#waitQueue.size; | |
} | |
} | |
// ============================================================================= | |
// 7. REAL-WORLD EXAMPLE: COUNTER WITH CONCURRENT INCREMENTS | |
// ============================================================================= | |
class CounterExample { | |
#count = 0; | |
#mutex = new Mutex(); | |
// Unsafe increment | |
async incrementUnsafe(): Promise<number> { | |
const current = this.#count; | |
// Simulate some async work | |
await new Promise(resolve => setTimeout(resolve, 1)); | |
this.#count = current + 1; | |
return this.#count; | |
} | |
// Safe increment using mutex | |
async incrementSafe(): Promise<number> { | |
return await this.#mutex.withLock(async () => { | |
const current = this.#count; | |
await new Promise(resolve => setTimeout(resolve, 1)); | |
this.#count = current + 1; | |
return this.#count; | |
}); | |
} | |
getCount(): number { | |
return this.#count; | |
} | |
reset(): void { | |
this.#count = 0; | |
} | |
} | |
// ============================================================================= | |
// 8. DEMONSTRATION FUNCTIONS | |
// ============================================================================= | |
async function demonstrateCounterRaceCondition() { | |
console.log("=== COUNTER RACE CONDITION ==="); | |
const counter = new CounterExample(); | |
// Launch 10 concurrent unsafe increments | |
const promises = Array.from({ length: 10 }, () => counter.incrementUnsafe()); | |
await Promise.all(promises); | |
console.log(`Expected count: 10, Actual count: ${counter.getCount()}`); | |
console.log("Race condition likely caused lost updates!\n"); | |
} | |
async function demonstrateCounterMutexSolution() { | |
console.log("=== COUNTER MUTEX SOLUTION ==="); | |
const counter = new CounterExample(); | |
// Launch 10 concurrent safe increments | |
const promises = Array.from({ length: 10 }, () => counter.incrementSafe()); | |
await Promise.all(promises); | |
console.log(`Expected count: 10, Actual count: ${counter.getCount()}`); | |
console.log("Mutex ensures all increments are applied!\n"); | |
} | |
async function demonstrateMutexManager() { | |
console.log("=== MUTEX MANAGER WITH MAP ==="); | |
const manager = new MutexManager(); | |
let sharedCounter = 0; | |
// Multiple operations on different named locks | |
const operations = [ | |
manager.withLock("counter-a", async () => { | |
const temp = sharedCounter; | |
await new Promise(resolve => setTimeout(resolve, 10)); | |
sharedCounter = temp + 1; | |
console.log(`Lock A: counter = ${sharedCounter}`); | |
}), | |
manager.withLock("counter-b", async () => { | |
console.log(`Lock B: independent operation, counter = ${sharedCounter}`); | |
}), | |
manager.withLock("counter-a", async () => { | |
const temp = sharedCounter; | |
await new Promise(resolve => setTimeout(resolve, 5)); | |
sharedCounter = temp + 1; | |
console.log(`Lock A: counter = ${sharedCounter}`); | |
}) | |
]; | |
await Promise.all(operations); | |
console.log(`Manager has ${manager.size} active mutexes`); | |
console.log(`Keys: ${Array.from(manager.keys()).join(', ')}\n`); | |
} | |
async function demonstrateResourceMutex() { | |
console.log("=== RESOURCE MUTEX WITH WEAKMAP ==="); | |
const resourceA = { name: "Resource A" }; | |
const resourceB = { name: "Resource B" }; | |
let globalCounter = 0; | |
// Operations on different resources can run concurrently | |
// Operations on same resource are serialized | |
const operations = [ | |
ResourceMutex.withLock(resourceA, async () => { | |
const temp = globalCounter; | |
await new Promise(resolve => setTimeout(resolve, 10)); | |
globalCounter = temp + 1; | |
console.log(`Resource A operation 1: counter = ${globalCounter}`); | |
}), | |
ResourceMutex.withLock(resourceB, async () => { | |
console.log(`Resource B operation: independent, counter = ${globalCounter}`); | |
}), | |
ResourceMutex.withLock(resourceA, async () => { | |
const temp = globalCounter; | |
await new Promise(resolve => setTimeout(resolve, 5)); | |
globalCounter = temp + 1; | |
console.log(`Resource A operation 2: counter = ${globalCounter}`); | |
}) | |
]; | |
await Promise.all(operations); | |
console.log("WeakMap automatically cleans up when resources are garbage collected\n"); | |
} | |
async function demonstrateTimedMutex() { | |
console.log("=== TIMED MUTEX DEMO ==="); | |
const mutex = new TimedMutex(); | |
try { | |
// First operation gets the lock | |
const operation1 = mutex.withLock(async () => { | |
console.log("Operation 1: Got lock, working for 2 seconds..."); | |
await new Promise(resolve => setTimeout(resolve, 2000)); | |
console.log("Operation 1: Finished"); | |
}, 3000); | |
// Second operation should timeout after 1 second | |
const operation2 = mutex.withLock(async () => { | |
console.log("Operation 2: This shouldn't run"); | |
}, 1000); | |
await Promise.all([operation1, operation2]); | |
} catch (error) { | |
console.log(`Expected timeout error: ${error.message}`); | |
} | |
console.log(""); | |
} | |
// ============================================================================= | |
// 9. USAGE EXAMPLES | |
// ============================================================================= | |
async function runAllExamples() { | |
await demonstrateRaceCondition(); | |
await demonstrateMutexSolution(); | |
await demonstrateCounterRaceCondition(); | |
await demonstrateCounterMutexSolution(); | |
await demonstrateMutexManager(); | |
await demonstrateResourceMutex(); | |
await demonstrateTimedMutex(); | |
} | |
// ============================================================================= | |
// 10. ALTERNATIVE APPROACHES | |
// ============================================================================= | |
// Using Atomic-like operations with proper async patterns | |
class AtomicCounter { | |
#count = 0; | |
#pending = Promise.resolve(); | |
async increment(): Promise<number> { | |
this.#pending = this.#pending.then(async () => { | |
await new Promise(resolve => setTimeout(resolve, 1)); | |
return ++this.#count; | |
}); | |
return this.#pending; | |
} | |
getCount(): number { | |
return this.#count; | |
} | |
} | |
// Using semaphore for limiting concurrent access | |
class Semaphore { | |
#permits: number; | |
#waitQueue: Array<() => void> = []; | |
constructor(permits: number) { | |
this.#permits = permits; | |
} | |
async acquire(): Promise<void> { | |
if (this.#permits > 0) { | |
this.#permits--; | |
return; | |
} | |
const { promise, resolve } = Promise.withResolvers<void>(); | |
this.#waitQueue.push(resolve); | |
return promise; | |
} | |
release(): void { | |
if (this.#waitQueue.length > 0) { | |
const next = this.#waitQueue.shift()!; | |
next(); | |
} else { | |
this.#permits++; | |
} | |
} | |
get availablePermits(): number { | |
return this.#permits; | |
} | |
get queueLength(): number { | |
return this.#waitQueue.length; | |
} | |
} | |
// Export for use | |
export { | |
Mutex, | |
TimedMutex, | |
BankAccount, | |
SafeBankAccount, | |
CounterExample, | |
AtomicCounter, | |
Semaphore, | |
runAllExamples | |
}; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment