diff --git a/src/flow-control/kill-on-signal.spec.ts b/src/flow-control/kill-on-signal.spec.ts index 020e3ca8..08518cdd 100644 --- a/src/flow-control/kill-on-signal.spec.ts +++ b/src/flow-control/kill-on-signal.spec.ts @@ -32,7 +32,7 @@ it('returns commands that map SIGINT to exit code 0', () => { const callback = jest.fn(); newCommands[0].close.subscribe(callback); - process.emit('SIGINT'); + process.emit('SIGINT', 'SIGINT'); // A fake command's .kill() call won't trigger a close event automatically... commands[0].close.next(createFakeCloseEvent({ exitCode: 1 })); @@ -56,7 +56,7 @@ it('returns commands that keep non-SIGINT exit codes', () => { describe.each(['SIGINT', 'SIGTERM', 'SIGHUP'])('on %s', (signal) => { it('kills all commands', () => { controller.handle(commands); - process.emit(signal); + process.emit(signal, signal); expect(process.listenerCount(signal)).toBe(1); expect(commands[0].kill).toHaveBeenCalledWith(signal); @@ -65,8 +65,14 @@ describe.each(['SIGINT', 'SIGTERM', 'SIGHUP'])('on %s', (signal) => { it('sends abort signal', () => { controller.handle(commands); - process.emit(signal); + process.emit(signal, signal); expect(abortController.signal.aborted).toBe(true); }); + + it('removes event listener on finish', () => { + const { onFinish } = controller.handle(commands); + onFinish(); + expect(process.listenerCount(signal)).toBe(0); + }); }); diff --git a/src/flow-control/kill-on-signal.ts b/src/flow-control/kill-on-signal.ts index eff7eca9..5dcbb5ef 100644 --- a/src/flow-control/kill-on-signal.ts +++ b/src/flow-control/kill-on-signal.ts @@ -4,6 +4,8 @@ import { map } from 'rxjs/operators'; import { Command } from '../command'; import { FlowController } from './flow-controller'; +const SIGNALS = ['SIGINT', 'SIGTERM', 'SIGHUP'] as const; + /** * Watches the main concurrently process for signals and sends the same signal down to each spawned * command. @@ -25,13 +27,12 @@ export class KillOnSignal implements FlowController { handle(commands: Command[]) { let caughtSignal: NodeJS.Signals; - (['SIGINT', 'SIGTERM', 'SIGHUP'] as NodeJS.Signals[]).forEach((signal) => { - this.process.on(signal, () => { - caughtSignal = signal; - this.abortController?.abort(); - commands.forEach((command) => command.kill(signal)); - }); - }); + const signalListener = (signal: NodeJS.Signals) => { + caughtSignal = signal; + this.abortController?.abort(); + commands.forEach((command) => command.kill(signal)); + }; + SIGNALS.forEach((signal) => this.process.on(signal, signalListener)); return { commands: commands.map((command) => { @@ -50,6 +51,10 @@ export class KillOnSignal implements FlowController { }, }); }), + onFinish: () => { + // Avoids MaxListenersExceededWarning when running programmatically + SIGNALS.forEach((signal) => this.process.off(signal, signalListener)); + }, }; } }