Skip to content

Commit 88bac37

Browse files
committed
forward signals correctly
signals are passed to the spawned child process; this means that the main process should not react to them, and instead wait for the spawned process to exit as a result of receiving the forwarded signal.
1 parent 2ee9575 commit 88bac37

File tree

3 files changed

+72
-10
lines changed

3 files changed

+72
-10
lines changed

src/bin.ts

Lines changed: 36 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -54,16 +54,43 @@ v8flags(function (err, v8flags) {
5454
const proc = spawn(
5555
process.execPath,
5656
nodeArgs.concat(join(__dirname, '_bin.js'), scriptArgs),
57-
{ stdio: 'inherit' }
57+
{
58+
// We need to run in detached mode so to avoid
59+
// automatic propagation of signals to the child process.
60+
// This is necessary because by default, keyboard interrupts
61+
// are propagated to the process tree, but `kill` is not.
62+
//
63+
// See: https://nodejs.org/api/child_process.html#child_process_options_detached
64+
detached: true,
65+
66+
// Pipe all input and output to this process
67+
stdio: 'inherit'
68+
}
5869
)
5970

60-
proc.on('exit', function (code: number, signal: string) {
61-
process.on('exit', function () {
62-
if (signal) {
63-
process.kill(process.pid, signal)
64-
} else {
65-
process.exit(code)
66-
}
67-
})
71+
// Ignore signals, and instead forward them to the
72+
// child process
73+
const forward = (signal: string) => process.on(signal, () => proc.kill(signal))
74+
75+
// Interrupt (CTRL-C)
76+
forward('SIGINT')
77+
78+
// Termination (`kill` default signal)
79+
forward('SIGTERM')
80+
81+
// Terminal size change must be forwarded to the subprocess
82+
forward('SIGWINCH')
83+
84+
// On exit, exit this process with the same exit code
85+
proc.on('close', (code: number, signal: string) => {
86+
if (signal) {
87+
process.kill(process.pid, signal)
88+
} else if (code) {
89+
process.exit(code)
90+
}
6891
})
92+
93+
// If this process is exited with any other signals,
94+
// kill the subprocess with the same signal
95+
process.on('exit', (_code: number, signal: string) => proc.kill(signal))
6996
})

src/index.spec.ts

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { expect } from 'chai'
2-
import { exec } from 'child_process'
2+
import { exec, spawn } from 'child_process'
33
import { join } from 'path'
44
import semver = require('semver')
55
import ts = require('typescript')
@@ -20,6 +20,33 @@ describe('ts-node', function () {
2020
})
2121

2222
describe('cli', function () {
23+
this.slow(1000)
24+
25+
it('should forward signals to the child process', function (done) {
26+
this.slow(5000)
27+
28+
const proc = spawn('node', [
29+
EXEC_PATH,
30+
'tests/signals'
31+
], {
32+
shell: '/bin/bash'
33+
})
34+
35+
let stdout = ''
36+
proc.stdout.on('data', (data) => stdout += data.toString())
37+
38+
proc.on('exit', function (code) {
39+
expect(code).to.equal(0)
40+
expect(stdout).to.equal('exited\n')
41+
42+
return done()
43+
})
44+
45+
// Leave enough time for node to fully start
46+
// the process, then send a signal
47+
setTimeout(() => proc.kill('SIGINT'), 1000)
48+
})
49+
2350
it('should execute cli', function (done) {
2451
exec(`${BIN_EXEC} tests/hello-world`, function (err, stdout) {
2552
expect(err).to.equal(null)

tests/signals.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
process.on('SIGINT', () => {
2+
setTimeout(() => {
3+
console.log('exited')
4+
process.exit(0)
5+
}, 2000)
6+
})
7+
8+
setInterval(() => console.log('should not be reached'), 4000)

0 commit comments

Comments
 (0)