From a4693888a2833b1c4a88eda048c935b32f43bd3e Mon Sep 17 00:00:00 2001 From: Paul Smith Date: Tue, 28 Mar 2023 17:39:45 -0500 Subject: [PATCH] Add --kill-signal option (#402) --- README.md | 2 ++ bin/concurrently.ts | 10 +++++++++- src/concurrently.ts | 5 +++++ src/defaults.ts | 7 +++++++ src/flow-control/kill-others.spec.ts | 29 +++++++++++++++++++++++++--- src/flow-control/kill-others.ts | 10 ++++++++-- src/index.ts | 1 + 7 files changed, 58 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index a979765a..e25f9419 100644 --- a/README.md +++ b/README.md @@ -219,6 +219,8 @@ Killing other processes -k, --kill-others Kill other processes if one exits or dies.[boolean] --kill-others-on-fail Kill other processes if one exits with non zero status code. [boolean] + --kill-signal Signal to send to other processes if one exits or dies. + (SIGTERM/SIGKILL, defaults to SIGTERM) [string] Restarting --restart-tries How many times a process that died should restart. diff --git a/bin/concurrently.ts b/bin/concurrently.ts index 08f5f6a6..6c47f7a6 100755 --- a/bin/concurrently.ts +++ b/bin/concurrently.ts @@ -110,6 +110,13 @@ const args = yargs(argsBeforeSep) describe: 'Kill other processes if one exits with non zero status code.', type: 'boolean', }, + 'kill-signal': { + alias: 'ks', + describe: + 'Signal to send to other processes if one exits or dies. (SIGTERM/SIGKILL, defaults to SIGTERM)', + type: 'string', + default: defaults.killSignal, + }, // Prefix prefix: { @@ -185,7 +192,7 @@ const args = yargs(argsBeforeSep) ) .group(['p', 'c', 'l', 't'], 'Prefix styling') .group(['i', 'default-input-target'], 'Input handling') - .group(['k', 'kill-others-on-fail'], 'Killing other processes') + .group(['k', 'kill-others-on-fail', 'kill-signal'], 'Killing other processes') .group(['restart-tries', 'restart-after'], 'Restarting') .epilogue(epilogue) .parseSync(); @@ -208,6 +215,7 @@ concurrently( : args.killOthersOnFail ? ['failure'] : [], + killSignal: args.killSignal, maxProcesses: args.maxProcesses, raw: args.raw, hide: args.hide.split(','), diff --git a/src/concurrently.ts b/src/concurrently.ts index ba269e6f..fbe832cb 100644 --- a/src/concurrently.ts +++ b/src/concurrently.ts @@ -110,6 +110,11 @@ export type ConcurrentlyOptions = { */ kill: KillProcess; + /** + * Signal to send to killed processes. + */ + killSignal?: string; + /** * List of additional arguments passed that will get replaced in each command. * If not defined, no argument replacing will happen. diff --git a/src/defaults.ts b/src/defaults.ts index 9f74abed..ddefbf4f 100644 --- a/src/defaults.ts +++ b/src/defaults.ts @@ -80,3 +80,10 @@ export const timings = false; * Passthrough additional arguments to commands (accessible via placeholders) instead of treating them as commands. */ export const passthroughArguments = false; + +/** + * Signal to send to other processes if one exits or dies. + * + * Defaults to OS specific signal. (SIGTERM on Linux/MacOS) + */ +export const killSignal: string | undefined = undefined; diff --git a/src/flow-control/kill-others.spec.ts b/src/flow-control/kill-others.spec.ts index 70931c45..a41e3e7f 100644 --- a/src/flow-control/kill-others.spec.ts +++ b/src/flow-control/kill-others.spec.ts @@ -19,10 +19,11 @@ beforeEach(() => { logger = createMockInstance(Logger); }); -const createWithConditions = (conditions: ProcessCloseCondition[]) => +const createWithConditions = (conditions: ProcessCloseCondition[], killSignal?: string) => new KillOthers({ logger, conditions, + killSignal, }); it('returns same commands', () => { @@ -48,7 +49,18 @@ it('kills other killable processes on success', () => { expect(logger.logGlobalEvent).toHaveBeenCalledTimes(1); expect(logger.logGlobalEvent).toHaveBeenCalledWith('Sending SIGTERM to other processes..'); expect(commands[0].kill).not.toHaveBeenCalled(); - expect(commands[1].kill).toHaveBeenCalled(); + expect(commands[1].kill).toHaveBeenCalledWith(undefined); +}); + +it('kills other killable processes on success, with specified signal', () => { + createWithConditions(['success'], 'SIGKILL').handle(commands); + commands[1].isKillable = true; + commands[0].close.next(createFakeCloseEvent({ exitCode: 0 })); + + expect(logger.logGlobalEvent).toHaveBeenCalledTimes(1); + expect(logger.logGlobalEvent).toHaveBeenCalledWith('Sending SIGKILL to other processes..'); + expect(commands[0].kill).not.toHaveBeenCalled(); + expect(commands[1].kill).toHaveBeenCalledWith('SIGKILL'); }); it('does nothing if called without conditions', () => { @@ -69,7 +81,18 @@ it('kills other killable processes on failure', () => { expect(logger.logGlobalEvent).toHaveBeenCalledTimes(1); expect(logger.logGlobalEvent).toHaveBeenCalledWith('Sending SIGTERM to other processes..'); expect(commands[0].kill).not.toHaveBeenCalled(); - expect(commands[1].kill).toHaveBeenCalled(); + expect(commands[1].kill).toHaveBeenCalledWith(undefined); +}); + +it('kills other killable processes on failure, with specified signal', () => { + createWithConditions(['failure'], 'SIGKILL').handle(commands); + commands[1].isKillable = true; + commands[0].close.next(createFakeCloseEvent({ exitCode: 1 })); + + expect(logger.logGlobalEvent).toHaveBeenCalledTimes(1); + expect(logger.logGlobalEvent).toHaveBeenCalledWith('Sending SIGKILL to other processes..'); + expect(commands[0].kill).not.toHaveBeenCalled(); + expect(commands[1].kill).toHaveBeenCalledWith('SIGKILL'); }); it('does not try to kill processes already dead', () => { diff --git a/src/flow-control/kill-others.ts b/src/flow-control/kill-others.ts index 2aba2a81..733f66c3 100644 --- a/src/flow-control/kill-others.ts +++ b/src/flow-control/kill-others.ts @@ -13,16 +13,20 @@ export type ProcessCloseCondition = 'failure' | 'success'; export class KillOthers implements FlowController { private readonly logger: Logger; private readonly conditions: ProcessCloseCondition[]; + private readonly killSignal: string | undefined; constructor({ logger, conditions, + killSignal, }: { logger: Logger; conditions: ProcessCloseCondition | ProcessCloseCondition[]; + killSignal: string | undefined; }) { this.logger = logger; this.conditions = _.castArray(conditions); + this.killSignal = killSignal; } handle(commands: Command[]) { @@ -47,8 +51,10 @@ export class KillOthers implements FlowController { closeState.subscribe(() => { const killableCommands = commands.filter((command) => Command.canKill(command)); if (killableCommands.length) { - this.logger.logGlobalEvent('Sending SIGTERM to other processes..'); - killableCommands.forEach((command) => command.kill()); + this.logger.logGlobalEvent( + `Sending ${this.killSignal || 'SIGTERM'} to other processes..` + ); + killableCommands.forEach((command) => command.kill(this.killSignal)); } }) ); diff --git a/src/index.ts b/src/index.ts index 43c22224..76c3ab11 100644 --- a/src/index.ts +++ b/src/index.ts @@ -131,6 +131,7 @@ export default ( new KillOthers({ logger, conditions: options.killOthers || [], + killSignal: options.killSignal, }), new LogTimings({ logger: options.timings ? logger : undefined,