diff --git a/scripts/benchmark.ts b/scripts/benchmark.ts index e989a48b..aaacb65d 100644 --- a/scripts/benchmark.ts +++ b/scripts/benchmark.ts @@ -82,6 +82,13 @@ const { buildGraph } = await import(srcImport(srcDir, 'domain/graph/builder.js') const { fnDepsData, fnImpactData, pathData, rolesData, statsData } = await import( srcImport(srcDir, 'domain/queries.js') ); +// v3.9.5+ parses WASM in a worker_thread that keeps the event loop alive until +// disposed. Older releases don't export disposeParsers — fall back to a no-op. +let disposeParsers = async () => {}; +try { + const parser = await import(srcImport(srcDir, 'domain/parser.js')); + if (typeof parser.disposeParsers === 'function') disposeParsers = parser.disposeParsers; +} catch { /* older release — no worker pool to dispose */ } const INCREMENTAL_RUNS = 3; const QUERY_RUNS = 5; @@ -226,4 +233,6 @@ const workerResult = { console.log(JSON.stringify(workerResult)); +await disposeParsers(); cleanup(); +process.exit(0); diff --git a/scripts/incremental-benchmark.ts b/scripts/incremental-benchmark.ts index 8758fb17..f2f5a38c 100644 --- a/scripts/incremental-benchmark.ts +++ b/scripts/incremental-benchmark.ts @@ -143,6 +143,13 @@ const { srcDir, cleanup } = await resolveBenchmarkSource(); const dbPath = path.join(root, '.codegraph', 'graph.db'); const { buildGraph } = await import(srcImport(srcDir, 'domain/graph/builder.js')); +// v3.9.5+ parses WASM in a worker_thread that keeps the event loop alive until +// disposed. Older releases don't export disposeParsers — fall back to a no-op. +let disposeParsers = async () => {}; +try { + const parser = await import(srcImport(srcDir, 'domain/parser.js')); + if (typeof parser.disposeParsers === 'function') disposeParsers = parser.disposeParsers; +} catch { /* older release — no worker pool to dispose */ } // Redirect console.log to stderr so only JSON goes to stdout const origLog = console.log; @@ -218,4 +225,6 @@ console.log = origLog; const workerResult = { fullBuildMs, noopRebuildMs, oneFileRebuildMs, oneFilePhases }; console.log(JSON.stringify(workerResult)); +await disposeParsers(); cleanup(); +process.exit(0); diff --git a/scripts/lib/fork-engine.ts b/scripts/lib/fork-engine.ts index 12a704a5..945a9a66 100644 --- a/scripts/lib/fork-engine.ts +++ b/scripts/lib/fork-engine.ts @@ -93,7 +93,18 @@ export function forkWorker(scriptPath, envKey, workerName, argv = [], timeoutMs if (signal) { console.error(`[fork] ${workerName} worker killed by signal ${signal}`); - settle(null); + // A worker can finish its measurements and write valid JSON to + // stdout before hanging on a non-daemonized handle (e.g. an + // un-disposed worker_thread pool). SIGKILL from our timeout + // then discards the result. Try to parse captured stdout first + // so we don't lose real data to a lingering event-loop keepalive. + try { + const parsed = JSON.parse(stdout); + console.error(`[fork] ${workerName} worker produced results before being killed — salvaging`); + settle(parsed); + } catch { + settle(null); + } return; } diff --git a/scripts/query-benchmark.ts b/scripts/query-benchmark.ts index f3d9d1ad..18cdf65a 100644 --- a/scripts/query-benchmark.ts +++ b/scripts/query-benchmark.ts @@ -94,6 +94,13 @@ const { buildGraph } = await import(srcImport(srcDir, 'domain/graph/builder.js') const { fnDepsData, fnImpactData, diffImpactData } = await import( srcImport(srcDir, 'domain/queries.js') ); +// v3.9.5+ parses WASM in a worker_thread that keeps the event loop alive until +// disposed. Older releases don't export disposeParsers — fall back to a no-op. +let disposeParsers = async () => {}; +try { + const parser = await import(srcImport(srcDir, 'domain/parser.js')); + if (typeof parser.disposeParsers === 'function') disposeParsers = parser.disposeParsers; +} catch { /* older release — no worker pool to dispose */ } // Redirect console.log to stderr so only JSON goes to stdout const origLog = console.log; @@ -259,4 +266,6 @@ console.log = origLog; const workerResult = { targets, fnDeps, fnImpact, diffImpact }; console.log(JSON.stringify(workerResult)); +await disposeParsers(); cleanup(); +process.exit(0); diff --git a/scripts/resolution-benchmark.ts b/scripts/resolution-benchmark.ts index de50426a..9252028e 100644 --- a/scripts/resolution-benchmark.ts +++ b/scripts/resolution-benchmark.ts @@ -219,6 +219,14 @@ console.log = (...args) => console.error(...args); const { srcDir, cleanup } = await resolveBenchmarkSource(); +// v3.9.5+ parses WASM in a worker_thread that keeps the event loop alive until +// disposed. Older releases don't export disposeParsers — fall back to a no-op. +let disposeParsers = async () => {}; +try { + const parser = await import(srcImport(srcDir, 'domain/parser.js')); + if (typeof parser.disposeParsers === 'function') disposeParsers = parser.disposeParsers; +} catch { /* older release — no worker pool to dispose */ } + try { const { buildGraph } = await import(srcImport(srcDir, 'domain/graph/builder.js')); const { openReadonlyOrFail } = await import(srcImport(srcDir, 'db/index.js')); @@ -296,5 +304,7 @@ try { console.log(JSON.stringify(results, null, 2)); } finally { console.log = origLog; + await disposeParsers(); cleanup(); } +process.exit(0); diff --git a/src/domain/search/models.ts b/src/domain/search/models.ts index 67554de0..54ce5956 100644 --- a/src/domain/search/models.ts +++ b/src/domain/search/models.ts @@ -253,7 +253,7 @@ export async function embed( } if (texts.length > batchSize) { - process.stdout.write(` Embedded ${Math.min(i + batchSize, texts.length)}/${texts.length}\r`); + process.stderr.write(` Embedded ${Math.min(i + batchSize, texts.length)}/${texts.length}\r`); } }