Skip to content

Commit be7895c

Browse files
stelcheckblakeembrey
authored andcommitted
Forward signals correctly (#419)
1 parent 85c139a commit be7895c

File tree

3 files changed

+74
-10
lines changed

3 files changed

+74
-10
lines changed

src/bin.ts

Lines changed: 24 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { join } from 'path'
55
import v8flags = require('v8flags')
66

77
const argv = process.argv.slice(2)
8+
const signals: NodeJS.Signals[] = ['SIGINT', 'SIGTERM', 'SIGWINCH']
89

910
v8flags(function (err, v8flags) {
1011
if (err) {
@@ -53,16 +54,30 @@ v8flags(function (err, v8flags) {
5354
const proc = spawn(
5455
process.execPath,
5556
nodeArgs.concat(join(__dirname, '_bin.js'), scriptArgs),
56-
{ 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+
stdio: 'inherit'
66+
}
5767
)
5868

59-
proc.on('exit', function (code: number, signal: string) {
60-
process.on('exit', function () {
61-
if (signal) {
62-
process.kill(process.pid, signal)
63-
} else {
64-
process.exit(code)
65-
}
66-
})
69+
// Ignore signals, and instead forward them to the child process.
70+
signals.forEach(signal => process.on(signal, () => proc.kill(signal)))
71+
72+
// On spawned close, exit this process with the same code.
73+
proc.on('close', (code: number, signal: string) => {
74+
if (signal) {
75+
process.kill(process.pid, signal)
76+
} else {
77+
process.exit(code)
78+
}
6779
})
80+
81+
// If this process exits, kill the child first.
82+
process.on('exit', () => proc.kill())
6883
})

src/index.spec.ts

Lines changed: 38 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,43 @@ 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+
// We use `spawn` instead because apparently TravisCI
29+
// does not let subprocesses be killed when ran under `sh`
30+
//
31+
// See: https://github.com/travis-ci/travis-ci/issues/704#issuecomment-328278149
32+
const proc = spawn('node', [
33+
EXEC_PATH,
34+
'--project',
35+
testDir,
36+
'tests/signals'
37+
], {
38+
shell: '/bin/bash'
39+
})
40+
41+
let stdout = ''
42+
proc.stdout.on('data', (data) => stdout += data.toString())
43+
44+
let stderr = ''
45+
proc.stderr.on('data', (data) => stderr += data.toString())
46+
47+
proc.on('exit', function (code) {
48+
expect(stderr).to.equal('')
49+
expect(stdout).to.equal('exited fine')
50+
expect(code).to.equal(0)
51+
52+
return done()
53+
})
54+
55+
// Leave enough time for node to fully start
56+
// the process, then send a signal
57+
setTimeout(() => proc.kill('SIGINT'), 2000)
58+
})
59+
2360
it('should execute cli', function (done) {
2461
exec(`${BIN_EXEC} tests/hello-world`, function (err, stdout) {
2562
expect(err).to.equal(null)

tests/signals.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
process.on('SIGINT', () => {
2+
process.stdout.write('exited')
3+
setTimeout(() => {
4+
process.stdout.write(' fine')
5+
6+
// Needed to make sure what we wrote has time
7+
// to be written
8+
process.nextTick(() => process.exit())
9+
}, 500)
10+
})
11+
12+
setInterval(() => console.log('should not be reached'), 3000)

0 commit comments

Comments
 (0)