mirror of
https://github.com/colbymchenry/codegraph.git
synced 2026-10-18 13:53:01 +08:00
Keep legacy graph owners protected and root reads lightweight
This commit is contained in:
@@ -183,6 +183,12 @@ if (process.argv[2] === '--child') {
|
||||
await run(conflict); clean(conflict); assert.equal(JSON.parse(config(conflict)).resolvedByUser, true);
|
||||
receipt({ action: 'conflicting plugin edit', boundary, decision: 'no overwrite; actionable repair then recovery', passed: true });
|
||||
}
|
||||
const graphLocked = await project(true), graphLock = path.join(graphLocked, '.codegraph/codegraph.lock');
|
||||
fs.writeFileSync(graphLock, String(process.pid)); fs.utimesSync(graphLock, 1, 1);
|
||||
assert.match((await run(graphLocked, 'update', 1)).stderr, /legacy graph owner/);
|
||||
assert.equal(fs.readFileSync(graphLock, 'utf8'), String(process.pid)); fs.unlinkSync(graphLock);
|
||||
fs.writeFileSync(graphLock, '{'); assert.match((await run(graphLocked, 'index', 1)).stderr, /unknown legacy graph lock/); fs.unlinkSync(graphLock);
|
||||
receipt({ action: 'live aged/torn legacy graph lock', decision: 'no age-based stealing or guessing', passed: true });
|
||||
const settings = await project(true); await killAt(settings, 'update', 'config_written');
|
||||
const changedSettings = JSON.parse(config(settings)); changedSettings.exclude = ['handlers.py'];
|
||||
const changedRaw = JSON.stringify(changedSettings); fs.writeFileSync(path.join(settings, 'codegraph.json'), changedRaw);
|
||||
|
||||
+1
-2
@@ -274,7 +274,7 @@ export class CodeGraph {
|
||||
* file can't be unlinked there, and st_ino is unreliable).
|
||||
*/
|
||||
reopenIfReplaced(): boolean {
|
||||
this.ensureExtensionState();
|
||||
recoverExtensions(this.projectRoot);
|
||||
if (!this.db.isReplacedOnDisk()) return false;
|
||||
const dbPath = this.db.getPath();
|
||||
// Open the live file FIRST — if that throws (e.g. mid-recreate), the old
|
||||
@@ -503,7 +503,6 @@ export class CodeGraph {
|
||||
* Get the project root directory
|
||||
*/
|
||||
getProjectRoot(): string {
|
||||
this.ensureExtensionState();
|
||||
return this.projectRoot;
|
||||
}
|
||||
|
||||
|
||||
+14
-2
@@ -101,6 +101,17 @@ function checkOwner(root: string, journal?: RecordV1): void {
|
||||
}
|
||||
fail(root, 'torn/unknown owner record without a valid transaction; confirm no legacy installer is running before archiving the owner record');
|
||||
}
|
||||
function checkGraphOwner(root: string, transactionPid?: number): void {
|
||||
const lock = path.join(root, '.codegraph', 'codegraph.lock'), raw = read(lock)?.trim();
|
||||
if (raw === undefined) return;
|
||||
if (transactionPid && raw === String(transactionPid)) return; // orphan proved by the coordinator
|
||||
if (/^[1-9]\d*$/.test(raw)) {
|
||||
try { process.kill(Number(raw), 0); }
|
||||
catch (error) { if ((error as NodeJS.ErrnoException).code === 'ESRCH') { remove(lock); return; } }
|
||||
fail(root, `legacy graph owner ${raw} may be live (or PID reused); wait for it to finish before retrying, regardless of lock age`);
|
||||
}
|
||||
fail(root, 'unknown legacy graph lock; confirm its owner has stopped before archiving codegraph.lock');
|
||||
}
|
||||
function marker(root: string, graphExisted: boolean): string | null {
|
||||
const file = path.join(root, '.codegraph', 'codegraph.db');
|
||||
if (!fs.existsSync(file)) return null;
|
||||
@@ -153,6 +164,7 @@ function verifyPackages(root: string, config: string | null, trust: string | nul
|
||||
function reconcile(root: string): boolean {
|
||||
const r = loadRecord(root); checkOwner(root, r);
|
||||
if (!r) { remove(ownerPath(root)); return false; }
|
||||
checkGraphOwner(root, r.pid);
|
||||
const committed = marker(root, r.graphExisted) === r.id;
|
||||
const configFile = path.join(root, 'codegraph.json'), trustFile = path.join(dir(root), 'trust.json');
|
||||
const configRaw = read(configFile), trustRaw = read(trustFile);
|
||||
@@ -202,7 +214,7 @@ export function withExtensionGuardSync<T>(root: string, work: () => T): T {
|
||||
root = fs.realpathSync(root);
|
||||
if (context.getStore() === root) return work();
|
||||
const release = acquire(root);
|
||||
try { return context.run(root, () => { reconcile(root); return work(); }); }
|
||||
try { return context.run(root, () => { reconcile(root); checkGraphOwner(root); return work(); }); }
|
||||
finally { release(); }
|
||||
}
|
||||
/** Indexers and lifecycle writers share this OS-released lock for their full run. */
|
||||
@@ -210,7 +222,7 @@ export async function withExtensionGuard<T>(root: string, work: () => Promise<T>
|
||||
root = fs.realpathSync(root);
|
||||
if (context.getStore() === root) return work();
|
||||
const release = acquire(root);
|
||||
try { return await context.run(root, async () => { reconcile(root); return work(); }); }
|
||||
try { return await context.run(root, async () => { reconcile(root); checkGraphOwner(root); return work(); }); }
|
||||
finally { release(); }
|
||||
}
|
||||
export class ExtensionTransaction {
|
||||
|
||||
Reference in New Issue
Block a user