mirror of
https://github.com/abhigyanpatwari/GitNexus.git
synced 2026-10-09 03:17:54 +00:00
* fix(storage): guard stale file-lock reclamation * fix(storage): close lock recovery failure paths * Address PR review feedback (#3234) - Flush the lock-child stderr diagnostic before process.exit - Treat explicit NaN timeouts as the default ceiling - Document non-retryable guard timeouts on the worker IPC contract Co-authored-by: Cursor <cursoragent@cursor.com> * fix(storage): stop mislabeling live lock waits as orphan recovery A brief peer inspect must not attach guardPath or send operators to RUNBOOK delete steps. Refuse lock-free embeddings sync, stop --watch only on a true guard timeout, and drop an unreadable self-created guard before failing closed. Co-authored-by: Cursor <cursoragent@cursor.com> * chore(autofix): apply prettier + eslint fixes via /autofix command * Address PR review feedback (#3234) - Verify lock/guard absence before degrading a denied main-lock create - Launch the third contender from unlinkSync, not the dead rename path - Document group-lock timeouts for unrecoverable guard leftovers Co-authored-by: Cursor <cursoragent@cursor.com> * style: prettier index-lock reclaim guard tests Co-authored-by: Cursor <cursoragent@cursor.com> * fix(storage): treat O_EXCL as the lock-file presence check CodeQL flagged existsSync-then-wx on analyze.lock. Create with wx first and only reclaim unreadable leftovers after grace, so a successor is never unlinked from a lost race. Co-authored-by: Cursor <cursoragent@cursor.com> --------- Co-authored-by: Gergő Magyar <gergomagyar@icloud.com> Co-authored-by: Gergo Magyar <gergomagyar0@gmail.com> Co-authored-by: Cursor <cursoragent@cursor.com> Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
67 lines
2.8 KiB
JavaScript
67 lines
2.8 KiB
JavaScript
/**
|
|
* Child process for the cross-process index-lock tests (#2658), using the BUILT
|
|
* module (LOCK_MODULE). Two modes:
|
|
*
|
|
* - default (HOLD): acquire the lock on LOCK_DIR, write MARKER once held, then
|
|
* hold until killed. Proves real cross-process exclusion and SIGKILL
|
|
* kill-recovery against a parent that uses the source module.
|
|
*
|
|
* - MODE=EXCLUSIVE (SENTINEL set): acquire, then enter a critical section
|
|
* guarded by an O_EXCL sentinel create — if the sentinel already exists,
|
|
* another process holds the lock at the same time, which is the exact
|
|
* single-writer violation the test hunts. Hold briefly, remove the sentinel,
|
|
* release, exit 0. Exit 3 only if the sentinel was already present (overlap),
|
|
* exit 4 for other sentinel-create failures, with stderr diagnostics.
|
|
* Used by the multi-reclaimer test where ≥2 children reclaim one dead holder.
|
|
*/
|
|
import { writeFileSync, writeSync, openSync, closeSync, unlinkSync } from 'node:fs';
|
|
import { pathToFileURL } from 'node:url';
|
|
|
|
// LOCK_MODULE is an absolute path. On Windows `import('C:\\…')` throws
|
|
// ERR_UNSUPPORTED_ESM_URL_SCHEME (a bare drive path is read as a URL scheme), so
|
|
// convert to a file:// URL — required on Windows, harmless on POSIX.
|
|
const { acquireIndexLock } = await import(pathToFileURL(process.env.LOCK_MODULE).href);
|
|
|
|
if (process.env.MODE === 'EXCLUSIVE') {
|
|
const lock = await acquireIndexLock(process.env.LOCK_DIR, { timeoutMs: 30_000, pollMs: 25 });
|
|
try {
|
|
// O_EXCL create fails if any other process is simultaneously in its own
|
|
// critical section — that is a broken single-writer invariant.
|
|
let fd;
|
|
try {
|
|
fd = openSync(process.env.SENTINEL, 'wx');
|
|
} catch (error) {
|
|
// writeSync(2) flushes before process.exit; console.error on a piped
|
|
// stderr can be truncated when the child is spawned with stdio: pipe.
|
|
writeSync(
|
|
2,
|
|
`Sentinel create failed: pid=${process.pid} path=${process.env.SENTINEL} ` +
|
|
`code=${error.code ?? 'unknown'} message=${error.message}\n`,
|
|
);
|
|
lock.release();
|
|
process.exit(error.code === 'EEXIST' ? 3 : 4);
|
|
}
|
|
closeSync(fd);
|
|
// Hold the section briefly so concurrent reclaimers would collide here.
|
|
await new Promise((r) => setTimeout(r, 150));
|
|
unlinkSync(process.env.SENTINEL);
|
|
} finally {
|
|
lock.release();
|
|
}
|
|
process.exit(0);
|
|
} else {
|
|
const lock = await acquireIndexLock(process.env.LOCK_DIR, { timeoutMs: 30_000, pollMs: 25 });
|
|
writeFileSync(process.env.MARKER, String(process.pid));
|
|
// Hold the lock until the parent kills us.
|
|
setInterval(() => {}, 1000);
|
|
// Release on a graceful signal (the SIGKILL path in the test never reaches this).
|
|
const release = () => {
|
|
try {
|
|
lock.release();
|
|
} finally {
|
|
process.exit(0);
|
|
}
|
|
};
|
|
process.on('SIGTERM', release);
|
|
process.on('SIGINT', release);
|
|
}
|