@juspay/neurolink
Version:
Universal AI Development Platform with working MCP integration, multi-provider support, voice (TTS/STT/realtime), and professional CLI. 58+ external MCP servers discoverable, multimodal file processing, RAG pipelines. Build, test, and deploy AI applicatio
61 lines (60 loc) • 2.06 kB
JavaScript
/**
* Promise-based async mutex for serializing concurrent async operations.
* No external dependencies — uses a simple FIFO queue of pending resolvers.
*/
export class AsyncMutex {
/** Default timeout for lock-held operations: 30 seconds */
static DEFAULT_TIMEOUT_MS = 30_000;
_locked = false;
_queue = [];
isLocked() {
return this._locked;
}
async runExclusive(fn, timeoutMs = AsyncMutex.DEFAULT_TIMEOUT_MS) {
await this._acquire();
let timer;
// Store fn()'s promise so we can ensure it settles before releasing the lock,
// even when the timeout fires first.
const fnPromise = fn();
try {
return await Promise.race([
fnPromise,
new Promise((_resolve, reject) => {
timer = setTimeout(() => reject(new Error(`AsyncMutex: lock-held operation timed out after ${timeoutMs}ms`)), timeoutMs);
}),
]);
}
finally {
if (timer !== undefined) {
clearTimeout(timer);
}
// Wait for fn() to settle before releasing the lock.
// If the timeout fired, fn() may still be running — we must not
// let another caller acquire the lock until fn() completes.
await fnPromise.catch((_settleErr) => {
// Intentionally swallowed — we only await settlement to keep
// the lock held until fn() finishes. The caller already received
// the timeout rejection from Promise.race above.
});
this._release();
}
}
_acquire() {
if (!this._locked) {
this._locked = true;
return Promise.resolve();
}
return new Promise((resolve) => {
this._queue.push(resolve);
});
}
_release() {
const next = this._queue.shift();
if (next) {
next();
}
else {
this._locked = false;
}
}
}