diff --git a/scripts/vitest-process-group.mjs b/scripts/vitest-process-group.mjs index e0c242668dc..0675ca7139e 100644 --- a/scripts/vitest-process-group.mjs +++ b/scripts/vitest-process-group.mjs @@ -34,6 +34,26 @@ export function forwardSignalToVitestProcessGroup(params) { } } +function ensureProcessListenerCapacity(processObject, eventName, additionalListeners = 1) { + if ( + typeof processObject.getMaxListeners !== "function" || + typeof processObject.setMaxListeners !== "function" || + typeof processObject.listenerCount !== "function" + ) { + return; + } + + const currentLimit = processObject.getMaxListeners(); + if (currentLimit === 0) { + return; + } + + const neededLimit = processObject.listenerCount(eventName) + additionalListeners + 1; + if (neededLimit > currentLimit) { + processObject.setMaxListeners(neededLimit); + } +} + export function installVitestProcessGroupCleanup(params) { const processObject = params.processObject ?? process; const platform = params.platform ?? process.platform; @@ -62,12 +82,14 @@ export function installVitestProcessGroupCleanup(params) { forward(signal); }; signalHandlers.set(signal, handler); + ensureProcessListenerCapacity(processObject, signal); processObject.on(signal, handler); } const exitHandler = () => { forward(cleanupSignal); }; + ensureProcessListenerCapacity(processObject, "exit"); processObject.on("exit", exitHandler); return () => { diff --git a/test/scripts/vitest-process-group.test.ts b/test/scripts/vitest-process-group.test.ts index 012f2261acd..75791bdac0c 100644 --- a/test/scripts/vitest-process-group.test.ts +++ b/test/scripts/vitest-process-group.test.ts @@ -96,4 +96,46 @@ describe("vitest process group helpers", () => { expect(listeners.get("SIGTERM")?.size ?? 0).toBe(0); expect(listeners.get("exit")?.size ?? 0).toBe(0); }); + + it("raises process listener limits for highly parallel cleanup handlers", () => { + const listeners = new Map void>>(); + let maxListeners = 10; + const fakeProcess = { + getMaxListeners: () => maxListeners, + setMaxListeners: vi.fn((value: number) => { + maxListeners = value; + return fakeProcess; + }), + listenerCount(event: string) { + return listeners.get(event)?.size ?? 0; + }, + on(event: string, handler: () => void) { + const set = listeners.get(event) ?? new Set(); + set.add(handler); + listeners.set(event, set); + }, + off(event: string, handler: () => void) { + listeners.get(event)?.delete(handler); + }, + }; + + const teardowns = Array.from({ length: 12 }, (_, index) => + installVitestProcessGroupCleanup({ + child: { pid: 4200 + index }, + processObject: fakeProcess as unknown as NodeJS.Process, + platform: "darwin", + kill: vi.fn(), + }), + ); + + expect(maxListeners).toBeGreaterThan(10); + expect(fakeProcess.setMaxListeners).toHaveBeenCalled(); + + for (const teardown of teardowns) { + teardown(); + } + expect(listeners.get("SIGINT")?.size ?? 0).toBe(0); + expect(listeners.get("SIGTERM")?.size ?? 0).toBe(0); + expect(listeners.get("exit")?.size ?? 0).toBe(0); + }); });