Files
fusion/packages/dashboard/src/process-lifecycle.ts
gsxdsm d08ec053a6 FN-6488: centralize dashboard beforeExit cleanup
Centralize dashboard cleanup registration to prevent repeated module imports from accumulating beforeExit listeners.

- Add a Symbol.for-backed dashboard process lifecycle registry with one shared beforeExit listener.
- Register existing cleanup intervals through the shared lifecycle helper across dashboard modules.
- Cover repeated module evaluation and multi-cleanup dispatch with Vitest regression tests.

Files changed:
 .../src/__tests__/process-lifecycle.test.ts        | 76 ++++++++++++++++++++++
 packages/dashboard/src/agent-generation.ts         |  3 +-
 packages/dashboard/src/ai-refine.ts                |  3 +-
 .../dashboard/src/milestone-slice-interview.ts     |  3 +-
 packages/dashboard/src/mission-interview.ts        |  3 +-
 packages/dashboard/src/planning.ts                 |  3 +-
 packages/dashboard/src/process-lifecycle.ts        | 63 ++++++++++++++++++
 packages/dashboard/src/server.ts                   |  3 +-
 packages/dashboard/src/subtask-breakdown.ts        |  3 +-
 9 files changed, 153 insertions(+), 7 deletions(-)

Fusion-Task-Id: FN-6488
Fusion-Task-Lineage: 3ec47f17-ae38-4ee3-a3fa-3132d23a2b12
2026-06-16 15:08:24 -07:00

64 lines
2.1 KiB
TypeScript

type BeforeExitCleanup = () => void;
type BeforeExitRegistry = {
cleanups: Set<BeforeExitCleanup>;
listener?: () => void;
};
const BEFORE_EXIT_REGISTRY_SYMBOL = Symbol.for("fusion.dashboard.beforeExit");
function getBeforeExitRegistry(): BeforeExitRegistry {
const globalWithRegistry = globalThis as typeof globalThis & {
[BEFORE_EXIT_REGISTRY_SYMBOL]?: BeforeExitRegistry;
};
globalWithRegistry[BEFORE_EXIT_REGISTRY_SYMBOL] ??= {
cleanups: new Set<BeforeExitCleanup>(),
};
return globalWithRegistry[BEFORE_EXIT_REGISTRY_SYMBOL];
}
function runBeforeExitCleanups(registry: BeforeExitRegistry): void {
for (const cleanup of Array.from(registry.cleanups)) {
cleanup();
}
}
/**
* FNXC:ProcessLifecycle 2026-06-15-08:09:
* Dashboard modules create unref'd cleanup intervals at import time, and Vitest can re-evaluate those modules while the process singleton survives.
* Register cleanup callbacks behind one Symbol.for-backed beforeExit listener so repeated imports do not accumulate EventEmitter listeners or hide the leak with setMaxListeners appeasement.
*/
export function registerBeforeExitCleanup(cleanup: BeforeExitCleanup): void {
const registry = getBeforeExitRegistry();
registry.cleanups.add(cleanup);
if (registry.listener) {
return;
}
registry.listener = () => runBeforeExitCleanups(registry);
process.on("beforeExit", registry.listener);
}
/** @internal Test-only helper for deterministic process-lifecycle assertions. */
export function __getBeforeExitCleanupCount(): number {
return getBeforeExitRegistry().cleanups.size;
}
/** @internal Test-only helper for deterministic process-lifecycle assertions. */
export function __runBeforeExitCleanupsForTests(): void {
runBeforeExitCleanups(getBeforeExitRegistry());
}
/** @internal Test-only helper for deterministic process-lifecycle assertions. */
export function __resetBeforeExitRegistryForTests(): void {
const registry = getBeforeExitRegistry();
if (registry.listener) {
process.off("beforeExit", registry.listener);
}
registry.cleanups.clear();
registry.listener = undefined;
}