@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
JavaScript
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
};