57dc91585d
Internal SmartGift build of a Claude Code monitoring dashboard. Lanes: a durable unit of parallel agent work, one per working directory, tracked across session restarts. Managed lanes are git worktrees the dashboard provisions and can reset or remove behind a three-check destroy guard and a counted preflight; adopted lanes are directories you already own and are never destroyable. Pipelines: a lane moves through pipeline stages. A stage the agent declares with evidence renders green; a stage inferred from the tool-event stream renders dashed amber and never counts as done. Detection is forward-only within a 30-minute window, and never writes the declared stage. Workspace: one page at /run with a lane grid, the selected lane's pipeline, and a full Claude console behind a disclosure.
41 lines
1.6 KiB
JavaScript
41 lines
1.6 KiB
JavaScript
/**
|
|
* @file Per-lane serialization lock. Ensures that only one async operation runs
|
|
* on a lane at a time, preventing concurrent git checkout races and other
|
|
* worktree collisions. Implemented as a chain of promises keyed by lane ID;
|
|
* acquiring a lock waits for the previous holder to settle (success or throw),
|
|
* then runs the new work, and releases for the next waiter.
|
|
* @author Nguyễn Ngọc Trí Vĩ <vinnt@smartgift.vn>
|
|
*/
|
|
|
|
const chains = new Map();
|
|
|
|
/**
|
|
* Acquire the per-lane lock, run fn, and release.
|
|
* The lock serialises work per lane_id: the next waiter runs only after the
|
|
* previous holder settles (success or error). If fn throws, the exception
|
|
* propagates to the caller; the chain is *not* poisoned — the next waiter
|
|
* still gets a fresh attempt.
|
|
*
|
|
* @param {number|string} id - Lane ID, converted to string for deduplication.
|
|
* @param {() => Promise<T>} fn - Async function to run while holding the lock.
|
|
* @returns {Promise<T>} The result of fn, or its thrown error.
|
|
*/
|
|
function withLaneLock(id, fn) {
|
|
const key = String(id);
|
|
const prev = chains.get(key) || Promise.resolve();
|
|
const run = prev.then(fn, fn); // run regardless of how the previous holder settled
|
|
// Keep the chain alive but never let a rejection poison the next waiter.
|
|
const settled = run.then(
|
|
() => {},
|
|
() => {}
|
|
);
|
|
chains.set(key, settled);
|
|
// Clean up the chain entry when it settles to prevent unbounded map growth.
|
|
settled.then(() => {
|
|
if (chains.get(key) === settled) chains.delete(key);
|
|
});
|
|
return run;
|
|
}
|
|
|
|
module.exports = { withLaneLock };
|