Skip to content

Commit

Permalink
Remove signal event listeners from process on finish (#512)
Browse files Browse the repository at this point in the history
  • Loading branch information
gustavohenke authored Nov 4, 2024
1 parent 79b3290 commit 3bcc9c9
Show file tree
Hide file tree
Showing 2 changed files with 21 additions and 10 deletions.
12 changes: 9 additions & 3 deletions src/flow-control/kill-on-signal.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 }));
Expand All @@ -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);
Expand All @@ -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);
});
});
19 changes: 12 additions & 7 deletions src/flow-control/kill-on-signal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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) => {
Expand All @@ -50,6 +51,10 @@ export class KillOnSignal implements FlowController {
},
});
}),
onFinish: () => {
// Avoids MaxListenersExceededWarning when running programmatically
SIGNALS.forEach((signal) => this.process.off(signal, signalListener));
},
};
}
}

0 comments on commit 3bcc9c9

Please sign in to comment.