Merge pull request #3668 from Tibsfox/fix/stale-lockfile-auto-recovery
fix(gsd): recover from stale lockfile after crash or SIGKILL
This commit is contained in:
commit
d5dac9ec04
2 changed files with 53 additions and 1 deletions
|
|
@ -288,6 +288,20 @@ export function acquireSessionLock(basePath: string): SessionLockResult {
|
|||
const gsdDir = gsdRoot(basePath);
|
||||
const lockTarget = effectiveLockTarget(gsdDir);
|
||||
|
||||
// #3218: Pre-flight stale lock cleanup — if the .lock/ directory exists but
|
||||
// no auto.lock metadata is present (or the PID is dead), remove the lock
|
||||
// directory before attempting acquisition. This prevents the 30-min stale
|
||||
// window from blocking /gsd after crashes, SIGKILL, or laptop sleep.
|
||||
const lockDir = lockTarget + ".lock";
|
||||
if (existsSync(lockDir)) {
|
||||
const existingData = readExistingLockData(lp);
|
||||
const isOrphan = !existingData || (existingData.pid && !isPidAlive(existingData.pid));
|
||||
if (isOrphan) {
|
||||
try { rmSync(lockDir, { recursive: true, force: true }); } catch { /* best-effort */ }
|
||||
try { if (existsSync(lp)) unlinkSync(lp); } catch { /* best-effort */ }
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
// Try to acquire an exclusive OS-level lock on the lock target.
|
||||
// We lock a directory since proper-lockfile works best on directories,
|
||||
|
|
@ -344,9 +358,11 @@ export function acquireSessionLock(basePath: string): SessionLockResult {
|
|||
}
|
||||
}
|
||||
|
||||
// #3218: Provide actionable workaround when lock recovery fails
|
||||
const lockDirPath = lockTarget + ".lock";
|
||||
const reason = existingPid
|
||||
? `Another auto-mode session (PID ${existingPid}) appears to be running.\nStop it with \`kill ${existingPid}\` before starting a new session.`
|
||||
: `Another auto-mode session is already running on this project.`;
|
||||
: `Another auto-mode session lock is stuck on this project.\nRun: rm -rf "${lockDirPath}" && rm -f "${lp}"`;
|
||||
|
||||
return { acquired: false, reason, existingPid };
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,36 @@
|
|||
/**
|
||||
* stale-lockfile-recovery.test.ts — #3668
|
||||
*
|
||||
* Verify that session-lock.ts contains pre-flight stale lock cleanup logic
|
||||
* that removes orphaned lock directories when the owning PID is dead,
|
||||
* preventing the 30-min stale window from blocking /gsd after crashes.
|
||||
*/
|
||||
|
||||
import { describe, test } from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
import { readFileSync } from "node:fs";
|
||||
import { join, dirname } from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
|
||||
const __dirname = dirname(fileURLToPath(import.meta.url));
|
||||
const sourceFile = join(__dirname, "..", "session-lock.ts");
|
||||
|
||||
describe("stale lockfile auto-recovery (#3668)", () => {
|
||||
const source = readFileSync(sourceFile, "utf-8");
|
||||
|
||||
test("checks for orphan lock with isPidAlive", () => {
|
||||
assert.match(source, /isPidAlive\(existingData\.pid\)/);
|
||||
});
|
||||
|
||||
test("removes stale lock directory with rmSync", () => {
|
||||
assert.match(source, /rmSync\(lockDir,\s*\{\s*recursive:\s*true/);
|
||||
});
|
||||
|
||||
test("references issue #3218 in pre-flight cleanup comment", () => {
|
||||
assert.match(source, /#3218.*Pre-flight stale lock cleanup/);
|
||||
});
|
||||
|
||||
test("provides actionable rm -rf workaround in error message", () => {
|
||||
assert.match(source, /rm\s+-rf/);
|
||||
});
|
||||
});
|
||||
Loading…
Add table
Reference in a new issue