Skip to content

Commit 44f3b66

Browse files
committed
Exit with 130 after receiving SIGINT
pnpm should exit with non-zero code when aborting abnormally due to SIGINT Fixes: pnpm/pnpm#9626
1 parent 06ddfa6 commit 44f3b66

File tree

4 files changed

+85
-5
lines changed

4 files changed

+85
-5
lines changed

index.js

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -318,8 +318,9 @@ function runCmd_ (cmd, pkg, env, wd, opts, stage, unsafe, uid, gid, cb_) {
318318
er.pkgname = pkg.name
319319
}
320320
process.removeListener('SIGTERM', procKill)
321-
process.removeListener('SIGTERM', procInterrupt)
322321
process.removeListener('SIGINT', procKill)
322+
process.removeListener('SIGINT', procInterrupt)
323+
process.removeListener('exit', procKill)
323324
return cb(er)
324325
}
325326
let called = false
@@ -329,10 +330,8 @@ function runCmd_ (cmd, pkg, env, wd, opts, stage, unsafe, uid, gid, cb_) {
329330
proc.kill()
330331
}
331332
function procInterrupt () {
333+
proc.on('close', () => process.exit(130))
332334
proc.kill('SIGINT')
333-
proc.on('exit', () => {
334-
process.exit()
335-
})
336335
process.once('SIGINT', procKill)
337336
}
338337
}

test/fixtures/count-to-10/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
"version": "1.0.0",
44
"scripts": {
55
"postinstall": "node postinstall",
6-
"signal-abrt": "echo 'signal-exit script' && kill -s ABRT $$",
6+
"signal-abrt": "echo 'signal-abrt script' && kill -s ABRT $$",
77
"signal-int": "echo 'signal-int script' && kill -s INT $$"
88
}
99
}

test/fixtures/sleep/package.json

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
{
2+
"name": "sleep",
3+
"version": "1.0.0",
4+
"//": [
5+
"Notes about the sleep-notify script below:",
6+
"1) The complexity of the script is necessary because we're signalling",
7+
" sh only, rather than what Posix shells do on Ctrl-C, which is to",
8+
" send SIGINT to the entire foreground process group.",
9+
"2) We trap SIGINT and exit with a code rather. This avoids the",
10+
" special handling of SIGINT exits which are treated as a success.",
11+
"3) The trap also seems to be necessary to make sh exit promptly.",
12+
"4) We don't want the parent process to send SIGINT to child before it",
13+
" has full started, so we send a signal to parent to coordinate the test."
14+
],
15+
"scripts": {
16+
"sleep-notify": "trap 'exit 130' SIGINT; kill -USR1 $PPID; for i in {1..100}; do sleep .01; done"
17+
}
18+
}

test/index.js

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -265,3 +265,66 @@ test('no error on INT signal from child', async function (t) {
265265
'INT signal reported'
266266
)
267267
})
268+
269+
test('node exits 130 when it receives SIGINT', async function (t) {
270+
if (isWindows()) {
271+
// On Windows there is no way to get the INT signal
272+
return
273+
}
274+
const fixture = path.join(__dirname, 'fixtures', 'sleep')
275+
276+
const verbose = sinon.spy()
277+
const silly = sinon.spy()
278+
const info = sinon.spy()
279+
280+
const stubProcessExit = sinon.stub(process, 'exit').callsFake(noop)
281+
282+
const log = {
283+
level: 'silent',
284+
info: noop,
285+
warn: noop,
286+
silly,
287+
verbose,
288+
pause: noop,
289+
resume: noop,
290+
clearProgress: noop,
291+
showProgress: noop
292+
}
293+
294+
const dir = fixture
295+
const pkg = require(path.join(fixture, 'package.json'))
296+
297+
// On Ctrl-C, posix shells send SIGINT to the foreground process group.
298+
// But here we aren't necessarily a process group leader, nor is the
299+
// script running as a separate process group. So the best we can do
300+
// is send SIGINT and let sh handle it in a slightly different way,
301+
// where it waits for the active command to finish...
302+
// This is annoying, because it means the lifecycle script sleep has
303+
// to finish, so it's set to a relatively low value.
304+
process.once('SIGUSR1', () => process.kill(process.pid, 'SIGINT'))
305+
306+
// We're trapping SIGINT in the script, to avoid the special cased
307+
// handling of an exit with an unhandled SIGINT (which is ignored).
308+
await t.rejects(async () =>
309+
lifecycle(pkg, 'sleep-notify', fixture, {
310+
stdio: 'pipe',
311+
log,
312+
dir,
313+
config: {}
314+
})
315+
)
316+
317+
stubProcessExit.restore()
318+
stubProcessExit.calledOnceWith(130)
319+
t.ok(
320+
silly.calledWithMatch(
321+
'lifecycle',
322+
'undefined~sleep-notify:',
323+
'Returned: code:',
324+
130,
325+
' signal:',
326+
null
327+
),
328+
'lifecycle script exited with 130'
329+
)
330+
})

0 commit comments

Comments
 (0)