UNPKG

@stackmemoryai/stackmemory

Version:

Lossless, project-scoped memory for AI coding tools. Durable context across sessions with 56 MCP tools, FTS5 search, conductor orchestrator, loop/watch monitoring, snapshot capture, pre-flight overlap checks, Claude/Codex/OpenCode wrappers, Linear sync, a

114 lines (113 loc) 2.87 kB
import { fileURLToPath as __fileURLToPath } from 'url'; import { dirname as __pathDirname } from 'path'; const __filename = __fileURLToPath(import.meta.url); const __dirname = __pathDirname(__filename); class AsyncMutex { locked = false; waiting = []; lockHolder = null; lockAcquiredAt = 0; lockTimeout; constructor(lockTimeoutMs = 3e5) { this.lockTimeout = lockTimeoutMs; } /** * Acquire the lock. Waits if already locked. * Returns a release function that MUST be called when done. */ async acquire(holder) { if (this.locked && this.lockAcquiredAt > 0) { const elapsed = Date.now() - this.lockAcquiredAt; if (elapsed > this.lockTimeout) { console.warn( `[AsyncMutex] Stale lock detected (held by ${this.lockHolder} for ${elapsed}ms), forcing release` ); this.forceRelease(); } } if (!this.locked) { this.locked = true; this.lockHolder = holder || "unknown"; this.lockAcquiredAt = Date.now(); return () => this.release(); } return new Promise((resolve) => { this.waiting.push(() => { this.locked = true; this.lockHolder = holder || "unknown"; this.lockAcquiredAt = Date.now(); resolve(() => this.release()); }); }); } /** * Try to acquire the lock without waiting * Returns release function if acquired, null if already locked */ tryAcquire(holder) { if (this.locked && this.lockAcquiredAt > 0) { const elapsed = Date.now() - this.lockAcquiredAt; if (elapsed > this.lockTimeout) { console.warn( `[AsyncMutex] Stale lock detected (held by ${this.lockHolder} for ${elapsed}ms), forcing release` ); this.forceRelease(); } } if (!this.locked) { this.locked = true; this.lockHolder = holder || "unknown"; this.lockAcquiredAt = Date.now(); return () => this.release(); } return null; } release() { const next = this.waiting.shift(); if (next) { next(); } else { this.locked = false; this.lockHolder = null; this.lockAcquiredAt = 0; } } forceRelease() { this.locked = false; this.lockHolder = null; this.lockAcquiredAt = 0; } /** * Execute a function while holding the lock */ async withLock(fn, holder) { const release = await this.acquire(holder); try { return await fn(); } finally { release(); } } /** * Check if currently locked */ isLocked() { return this.locked; } /** * Get current lock status */ getStatus() { return { locked: this.locked, holder: this.lockHolder, acquiredAt: this.lockAcquiredAt, waitingCount: this.waiting.length }; } } const syncMutex = new AsyncMutex(3e5); export { AsyncMutex, syncMutex };