mirror of
https://github.com/colbymchenry/codegraph.git
synced 2026-10-24 11:24:49 +08:00
test(daemon): the OS process lookup tests pass on a loaded Windows machine (#2842)
daemon-lock-holder.test.ts > "reads a running process from the OS" failed under load on Windows, and so did the seven CLI tests in the file that look a process up. describeProcess gives each OS query 5 s: on a busy machine PowerShell takes longer than that to start and run its CIM query, so the lookup falls back to tasklist (the image name only, or nothing once tasklist runs out too) and the command line is null. The in-process test's toMatch(/ping/) then fails, and the CLI the other tests spawn sees a Node holder it can't classify, keeps the lock, and says so. In production that fallback is the intended fail-safe: an undescribed holder stays "unknown" and its lock is kept. The per-query budget is still 5 s unless CODEGRAPH_PROCESS_LOOKUP_TIMEOUT_MS says otherwise, and nothing outside the tests sets it. The test file sets it to 60 s for itself and for every CLI it spawns (an env var reaches both), gives the lookup tests time to match (240 s per CLI run, 30 s for a killed holder to go), and adds a Windows-only test that a lookup that runs out says nothing, so the holder stays "unknown". Validation (Windows, 16 logical CPUs, a 48-thread CPU burn running): - describeProcess on a live PING.EXE: the 5 s budget answered nothing in 8 of 8 lookups (about 11 s each); a 60 s budget read the command line in 6 of 6 (median 12.6 s, max 13.6 s). - The reported test alone: main 0 of 4, each "expected '' to match /ping/i"; this branch 2 of 2, and 6 of 6 with an earlier revision that passed the same 60 s budget as an argument. - The whole file: main 3 of 11, the 8 failures being exactly the tests that look a process up; this branch 12 of 12, twice. - No load: 12 of 12 in 20 s. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
f406fe0064
commit
ef05970bb4
@@ -12,7 +12,7 @@
|
||||
* CodeGraph server keeps its lock, and the message names that process and a
|
||||
* command that stops it.
|
||||
*/
|
||||
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
|
||||
import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, it } from 'vitest';
|
||||
import { spawn, spawnSync } from 'child_process';
|
||||
import * as fs from 'fs';
|
||||
import * as os from 'os';
|
||||
@@ -32,6 +32,23 @@ const windows = process.platform === 'win32';
|
||||
const holders: number[] = [];
|
||||
let root: string;
|
||||
|
||||
/**
|
||||
* Each OS lookup gets a minute here, in this process and in the CLIs it runs,
|
||||
* not the 5 s CodeGraph gives one: on a loaded machine PowerShell can take
|
||||
* longer than that to start, and a Windows lookup that runs out falls back to
|
||||
* tasklist, which has no command line to show. The tests that look a process
|
||||
* up allow for it.
|
||||
*/
|
||||
const LOOKUP_ENV = 'CODEGRAPH_PROCESS_LOOKUP_TIMEOUT_MS';
|
||||
const savedLookupTimeout = process.env[LOOKUP_ENV];
|
||||
beforeAll(() => {
|
||||
process.env[LOOKUP_ENV] = '60000';
|
||||
});
|
||||
afterAll(() => {
|
||||
if (savedLookupTimeout === undefined) delete process.env[LOOKUP_ENV];
|
||||
else process.env[LOOKUP_ENV] = savedLookupTimeout;
|
||||
});
|
||||
|
||||
/**
|
||||
* Start a long-lived process to be named by the lock: `native` is no
|
||||
* JavaScript runtime, `node` is Node running something else, and
|
||||
@@ -57,7 +74,7 @@ function runCli(args: string[]) {
|
||||
return spawnSync(process.execPath, [BIN, ...args], {
|
||||
cwd: root,
|
||||
encoding: 'utf8',
|
||||
timeout: 60_000,
|
||||
timeout: 240_000,
|
||||
env: { ...process.env, CODEGRAPH_NO_DAEMON: '1', CODEGRAPH_TELEMETRY: '0', DO_NOT_TRACK: '1', CODEGRAPH_NO_PROMPT_HOOK: '1' },
|
||||
});
|
||||
}
|
||||
@@ -132,9 +149,22 @@ describe('what a lock holder is (#2770)', () => {
|
||||
expect(classifyLockHolder(nativeInfo)).toBe('other');
|
||||
expect(classifyLockHolder(await describeProcess(server))).toBe('codegraph-server');
|
||||
process.kill(native, 'SIGKILL');
|
||||
const end = Date.now() + 10_000;
|
||||
const end = Date.now() + 30_000;
|
||||
while (isProcessAlive(native) && Date.now() < end) await new Promise((r) => setTimeout(r, 50));
|
||||
expect(classifyLockHolder(await describeProcess(native))).toBe('unknown');
|
||||
}, 240_000);
|
||||
|
||||
// Windows starts no process within a millisecond, so both of its queries run out.
|
||||
it.runIf(windows)('a lookup that runs out of time says nothing, so the holder stays unknown', async () => {
|
||||
const native = startHolder('native');
|
||||
process.env[LOOKUP_ENV] = '1';
|
||||
try {
|
||||
const info = await describeProcess(native);
|
||||
expect(info).toEqual({ name: null, commandLine: null });
|
||||
expect(classifyLockHolder(info)).toBe('unknown');
|
||||
} finally {
|
||||
process.env[LOOKUP_ENV] = '60000';
|
||||
}
|
||||
}, 30_000);
|
||||
});
|
||||
|
||||
@@ -157,7 +187,7 @@ describe('codegraph index and unlock with a daemon lock naming a live process (#
|
||||
const cg = CodeGraph.openSync(root);
|
||||
try { expect(cg.searchNodes('replacementSymbol').length).toBeGreaterThan(0); }
|
||||
finally { cg.close(); }
|
||||
}, 60_000);
|
||||
}, 300_000);
|
||||
|
||||
it('unlock clears an old-format lock naming a process that is not CodeGraph, leaving the process alone', () => {
|
||||
const holder = startHolder('native');
|
||||
@@ -169,7 +199,7 @@ describe('codegraph index and unlock with a daemon lock naming a live process (#
|
||||
expect(unlock.stdout).toContain('Removed stale lock artifacts');
|
||||
expect(fs.existsSync(lockPath)).toBe(false);
|
||||
expect(isProcessAlive(holder)).toBe(true);
|
||||
}, 60_000);
|
||||
}, 300_000);
|
||||
|
||||
it.each([
|
||||
['an old-format lock', writeLegacyLock],
|
||||
@@ -190,11 +220,11 @@ describe('codegraph index and unlock with a daemon lock naming a live process (#
|
||||
|
||||
// The advice: stop that process, then retry `codegraph index`.
|
||||
process.kill(holder, 'SIGKILL');
|
||||
const end = Date.now() + 10_000;
|
||||
const end = Date.now() + 30_000;
|
||||
while (isProcessAlive(holder) && Date.now() < end) await new Promise((r) => setTimeout(r, 50));
|
||||
const retry = runCli(['index', '--quiet', root]);
|
||||
expect(retry.status, retry.stderr).toBe(0);
|
||||
}, 90_000);
|
||||
}, 600_000);
|
||||
|
||||
it('names the stale writer lock when the same process that is not CodeGraph holds it too', () => {
|
||||
const holder = startHolder('native');
|
||||
@@ -212,7 +242,7 @@ describe('codegraph index and unlock with a daemon lock naming a live process (#
|
||||
const retry = runCli(['index', '--quiet', root]);
|
||||
expect(retry.status, retry.stderr).toBe(0);
|
||||
expect(isProcessAlive(holder)).toBe(true);
|
||||
}, 90_000);
|
||||
}, 600_000);
|
||||
|
||||
it('the writer-lock message names `codegraph daemon`, a command the CLI accepts', () => {
|
||||
expect(tryAcquireWriterLock(root, 'daemon').kind).toBe('acquired');
|
||||
|
||||
@@ -35,11 +35,22 @@ export type LockHolderKind = 'codegraph-server' | 'other' | 'unknown';
|
||||
|
||||
const LOOKUP_TIMEOUT_MS = 5_000;
|
||||
|
||||
/**
|
||||
* How long one OS query may run: 5 s, unless `CODEGRAPH_PROCESS_LOOKUP_TIMEOUT_MS`
|
||||
* says otherwise. Tests set it: on a loaded machine PowerShell can take longer
|
||||
* than 5 s to start, and a Windows lookup that runs out falls back to tasklist,
|
||||
* which has no command line to show.
|
||||
*/
|
||||
function lookupTimeoutMs(): number {
|
||||
const fromEnv = Number(process.env.CODEGRAPH_PROCESS_LOOKUP_TIMEOUT_MS);
|
||||
return Number.isFinite(fromEnv) && fromEnv > 0 ? fromEnv : LOOKUP_TIMEOUT_MS;
|
||||
}
|
||||
|
||||
/** Runs a short OS query and resolves its stdout, or null when it fails or times out. */
|
||||
function query(file: string, args: string[]): Promise<string | null> {
|
||||
return new Promise((resolve) => {
|
||||
try {
|
||||
execFile(file, args, { encoding: 'utf8', timeout: LOOKUP_TIMEOUT_MS, windowsHide: true, maxBuffer: 1 << 20 },
|
||||
execFile(file, args, { encoding: 'utf8', timeout: lookupTimeoutMs(), windowsHide: true, maxBuffer: 1 << 20 },
|
||||
(error, stdout) => resolve(error ? null : stdout));
|
||||
} catch {
|
||||
resolve(null);
|
||||
|
||||
Reference in New Issue
Block a user