fix: make Grok process lifecycle tests full-suite safe (#2289)
## Summary Full Suite after #2288 still red on shard 4: `process-lifecycle.test.ts` times out at 5s under shard transform load even after reducing reimports. - Move Symbol.for `process.exit` reaper onto `process-manager` (lifecycle owner) - Stress test reimports that module (not the full plugin graph) - Explicit 15s timeout for the cold-transform bound stress test ## Test plan - [x] Local process-lifecycle green (~2s) - [ ] PR merge gate - [ ] Post-merge Full Suite green <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit - **Bug Fixes** - Improved Grok runtime process cleanup during application shutdown. - Prevented duplicate cleanup handlers from accumulating during repeated module loading. - Ensured managed processes are reliably terminated when the process exits. - **Tests** - Expanded lifecycle coverage to validate repeated loading scenarios. - Increased test timeouts for more reliable stress-test execution. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
7
.changeset/grok-process-lifecycle-owner.md
Normal file
7
.changeset/grok-process-lifecycle-owner.md
Normal file
@@ -0,0 +1,7 @@
|
||||
---
|
||||
"@runfusion/fusion": patch
|
||||
---
|
||||
|
||||
summary: Keep Grok ACP process cleanup armed once per process, without listener growth.
|
||||
category: fix
|
||||
dev: Move Symbol.for process.exit reaper onto process-manager; lifecycle tests reimport that module instead of the full plugin graph under full-suite load.
|
||||
@@ -15,22 +15,22 @@ describe("Grok plugin process lifecycle", () => {
|
||||
});
|
||||
|
||||
/*
|
||||
FNXC:GrokRuntimeTests 2026-07-18-07:25:
|
||||
Symbol.for exit-hook guard is proven by two re-evaluations after the baseline
|
||||
import. Full-suite shard transform load made 5–15 dynamic imports of the full
|
||||
plugin graph hit the default 5s testTimeout; keep the bound assertion, not the
|
||||
iteration marathon.
|
||||
FNXC:GrokRuntimeTests 2026-07-18-07:40:
|
||||
Prove the process-manager Symbol.for exit-hook guard by re-importing the
|
||||
registry module (lifecycle owner), not the full plugin graph. Full-suite
|
||||
shard transform of @fusion/core via process-manager can still exceed the
|
||||
default 5s budget on cold workers — give the bound stress test 15s.
|
||||
*/
|
||||
it("keeps its process cleanup owner bounded across repeated module evaluation", async () => {
|
||||
it("keeps its process cleanup owner bounded across repeated module evaluation", { timeout: 15_000 }, async () => {
|
||||
const baseline = listenerCounts();
|
||||
const warnings: Error[] = [];
|
||||
const onWarning = (warning: Error) => warnings.push(warning);
|
||||
process.on("warning", onWarning);
|
||||
|
||||
try {
|
||||
for (let iteration = 0; iteration < 2; iteration += 1) {
|
||||
for (let iteration = 0; iteration < 5; iteration += 1) {
|
||||
vi.resetModules();
|
||||
await import("../index.js");
|
||||
await import("../acp/process-manager.js");
|
||||
}
|
||||
await new Promise<void>((resolve) => setImmediate(resolve));
|
||||
} finally {
|
||||
|
||||
@@ -84,6 +84,21 @@ export function killAllProcesses(): void {
|
||||
activeProcesses.clear();
|
||||
}
|
||||
|
||||
/*
|
||||
FNXC:ProcessLifecycle 2026-07-18-07:40:
|
||||
Install the process.exit reaper here (not only from index.ts) so lifecycle
|
||||
ownership lives with the registry module. Full-suite shards previously timed
|
||||
out process-lifecycle.test while repeatedly importing the full plugin graph
|
||||
just to exercise Symbol.for; re-importing this module is enough to prove the
|
||||
bound and still runs when the plugin entry loads killAllProcesses.
|
||||
*/
|
||||
const PROCESS_EXIT_HOOK_KEY = Symbol.for("fusion.plugin.grok-runtime.exitCleanup");
|
||||
const processWithExitHook = process as typeof process & { [key: symbol]: boolean | undefined };
|
||||
if (!processWithExitHook[PROCESS_EXIT_HOOK_KEY]) {
|
||||
process.on("exit", killAllProcesses);
|
||||
processWithExitHook[PROCESS_EXIT_HOOK_KEY] = true;
|
||||
}
|
||||
|
||||
export class MissingAcpEnvError extends Error {
|
||||
readonly code = "ACP_MISSING_ENV";
|
||||
constructor(readonly missingKeys: string[]) {
|
||||
|
||||
@@ -1,6 +1,12 @@
|
||||
import { definePlugin } from "@fusion/plugin-sdk";
|
||||
import type { FusionPlugin } from "@fusion/plugin-sdk";
|
||||
import { killAllProcesses } from "./acp/index.js";
|
||||
/*
|
||||
FNXC:ProcessLifecycle 2026-07-16-07:00 / 2026-07-18-07:40:
|
||||
Exit-hook ownership lives in ./acp/process-manager (Symbol.for guard + shared
|
||||
registry). Import that module from the plugin entry so plugin load still arms
|
||||
the process.exit reaper; re-evaluation stays bounded by the Symbol.for guard.
|
||||
*/
|
||||
import "./acp/process-manager.js";
|
||||
import { probeGrokBinary } from "./probe.js";
|
||||
import { discoverGrokProviderModels } from "./provider.js";
|
||||
import { GrokRuntimeAdapter } from "./runtime-adapter.js";
|
||||
@@ -25,21 +31,6 @@ fusion-plugin-acp-runtime, so bundled Grok does not depend on the experimental
|
||||
ACP example plugin package.
|
||||
*/
|
||||
|
||||
/*
|
||||
FNXC:ProcessLifecycle 2026-07-16-07:00:
|
||||
The dashboard backfill worker repeatedly evaluates this plugin through
|
||||
`vi.resetModules()` while retaining the process singleton. Install one exit
|
||||
listener per Grok lifecycle owner and use the process-shared registry in the
|
||||
ACP manager so it reaps children from every evaluation. Do not appease this
|
||||
with `setMaxListeners`; the listener must stay bounded.
|
||||
*/
|
||||
const PROCESS_EXIT_HOOK_KEY = Symbol.for("fusion.plugin.grok-runtime.exitCleanup");
|
||||
const processWithExitHook = process as typeof process & { [key: symbol]: boolean | undefined };
|
||||
if (!processWithExitHook[PROCESS_EXIT_HOOK_KEY]) {
|
||||
process.on("exit", killAllProcesses);
|
||||
processWithExitHook[PROCESS_EXIT_HOOK_KEY] = true;
|
||||
}
|
||||
|
||||
const plugin: FusionPlugin = definePlugin({
|
||||
manifest: {
|
||||
id: "fusion-plugin-grok-runtime",
|
||||
|
||||
Reference in New Issue
Block a user