|
|
@@ -184,6 +184,9 @@ export class FileLock {
|
|
|
private lockPath: string;
|
|
|
private held = false;
|
|
|
|
|
|
+ /** Locks older than this are considered stale regardless of PID status */
|
|
|
+ private static readonly STALE_TIMEOUT_MS = 10 * 60 * 1000; // 10 minutes
|
|
|
+
|
|
|
constructor(lockPath: string) {
|
|
|
this.lockPath = lockPath;
|
|
|
}
|
|
|
@@ -197,15 +200,18 @@ export class FileLock {
|
|
|
try {
|
|
|
const content = fs.readFileSync(this.lockPath, 'utf-8').trim();
|
|
|
const pid = parseInt(content, 10);
|
|
|
+ const stat = fs.statSync(this.lockPath);
|
|
|
+ const lockAge = Date.now() - stat.mtimeMs;
|
|
|
|
|
|
- if (!isNaN(pid) && this.isProcessAlive(pid)) {
|
|
|
+ // Treat locks older than the timeout as stale, regardless of PID
|
|
|
+ if (lockAge < FileLock.STALE_TIMEOUT_MS && !isNaN(pid) && this.isProcessAlive(pid)) {
|
|
|
throw new Error(
|
|
|
`CodeGraph database is locked by another process (PID ${pid}). ` +
|
|
|
- `If this is stale, delete ${this.lockPath}`
|
|
|
+ `If this is stale, run 'codegraph unlock' or delete ${this.lockPath}`
|
|
|
);
|
|
|
}
|
|
|
|
|
|
- // Stale lock - remove it
|
|
|
+ // Stale lock (dead process or timed out) - remove it
|
|
|
fs.unlinkSync(this.lockPath);
|
|
|
} catch (err) {
|
|
|
if (err instanceof Error && err.message.includes('locked by another')) {
|
|
|
@@ -225,7 +231,7 @@ export class FileLock {
|
|
|
// Race condition: another process grabbed the lock between our check and write
|
|
|
throw new Error(
|
|
|
'CodeGraph database is locked by another process. ' +
|
|
|
- `If this is stale, delete ${this.lockPath}`
|
|
|
+ `If this is stale, run 'codegraph unlock' or delete ${this.lockPath}`
|
|
|
);
|
|
|
}
|
|
|
throw err;
|