fix(eval): teardown PGLite engines so bun run eval:run exits 0

The multi-adapter runner left PGLite engines alive after each run.
GbrainAfterAdapter and HybridNoGraphAdapter both instantiate a
PGLiteEngine in init() but never disconnect it; Bun's shutdown path
exits with code 99 when embedded-Postgres workers outlive main().

Added optional `teardown?(state)` to the Adapter interface, implemented
it on both engine-backed adapters, and call it from scoreOneRun after
the N=5 loop. ripgrep-bm25 and vector-only hold no DB resources and
don't need a teardown.

Verified: gbrain-after, hybrid-nograph, ripgrep-bm25, vector-only all
exit 0 at N=1. Full test:eval passes (57 tests). No metric change.
This commit is contained in:
Garry Tan
2026-04-19 08:34:09 +08:00
parent b81373d4c2
commit c6b308a1c8
3 changed files with 19 additions and 0 deletions
+5
View File
@@ -76,6 +76,11 @@ export class HybridNoGraphAdapter implements Adapter {
return { engine } satisfies HybridNoGraphState;
}
async teardown(state: BrainState): Promise<void> {
const s = state as HybridNoGraphState;
await s.engine.disconnect();
}
/** Build a markdown string importFromContent can parse.
* Format: YAML frontmatter then body; matches what gbrain import expects. */
private buildContentMarkdown(p: Page): string {
+6
View File
@@ -217,6 +217,11 @@ class GbrainAfterAdapter implements Adapter {
rank: i + 1,
}));
}
async teardown(state: unknown): Promise<void> {
const { engine } = state as { engine: PGLiteEngine };
await engine.disconnect();
}
}
/**
@@ -340,6 +345,7 @@ async function scoreOneRun(
for (const r of topK) if (relevant.has(r.page_id)) totalCorrect++;
totalExpected += relevant.size;
}
if (adapter.teardown) await adapter.teardown(state);
return {
mean_precision_at_k: queries.length > 0 ? totalP / queries.length : 0,
mean_recall_at_k: queries.length > 0 ? totalR / queries.length : 0,
+8
View File
@@ -179,6 +179,14 @@ export interface Adapter {
* Adapters that don't have a poison path return an empty map.
*/
getPoisonDisposition?(state: BrainState): Record<string, PoisonDisposition>;
/**
* Release any resources held by `state` (DB connections, file locks,
* worker threads). Called once per run after scoring completes.
* Adapters that hold no resources can omit this. Without it, PGLite-backed
* adapters leak engine workers and Bun exits 99 at the end of the run.
*/
teardown?(state: BrainState): Promise<void>;
}
// ─── Scorer helpers ─────────────────────────────────────────────────