@@ -30,12 +30,29 @@ interface Terminable {
3030 terminate(this: Terminable): any
3131}
3232
33- // Terminates the workers, empties the workers array, and exits.
33+ // Terminates the workers, empties the workers array, and possibly exits.
3434const onSignal = (workers: Terminable[], signal: string) => {
3535 // worker.terminate() might return a Promise or might be synchronous. This async helper function
3636 // creates a consistent interface.
3737 const terminate = async (worker: Terminable) => worker.terminate()
38- Promise.all(workers.map(worker => terminate(worker).catch(() => {}))).then(() => process.exit(1))
38+ Promise.all(workers.map(worker => terminate(worker).catch(() => {}))).then(() => {
39+ // Adding a signal listener suppresses the default signal handling behavior. That default
40+ // behavior must be replicated here, but only if the default behavior isn't intentionally
41+ // suppressed by another signal listener. Unfortunately there is no robust way to determine
42+ // whether the default behavior was intentionally suppressed, so a heuristic is used. (Note: The
43+ // 'exit' event is not suitable for terminating workers because it is not emitted when the
44+ // default signal handler terminates the process.)
45+ if (process.listenerCount(signal) > 1) {
46+ // Assume that one of the other signal listeners will take care of calling process.exit().
47+ // This assumption breaks down if all of the other listeners are making the same assumption.
48+ return
49+ }
50+ // Right now this is the only signal listener, so assume that this listener is to blame for
51+ // inhibiting the default signal handler. (This assumption fails if the number of listeners
52+ // changes during signal handling. This can happen if a listener was added by process.once().)
53+ // Mimic the default behavior, which is to exit with a non-0 code.
54+ process.exit(1)
55+ })
3956 workers.length = 0
4057}
4158
0 commit comments