Node and NPM Running script and Ctrl-C triggers SIGINT twice
Asked Answered
G

3

14

I've come across a problem on one of my Nodejs apps running with npm start (which just does node app.js).

My app contains a sigint handler as follows:

process.on('SIGINT', () => {
    db.disconnect().then({
        process.exit(0);
    }).catch(e => process.exit(1));
});

With corresponding logs. After some testing on other files, I've noticed that Ctrl-C when on an npm process triggers SIGINT twice if the first one takes too long to exit. (try adding a timeout on a sample app).

For now I've added a counter to check if the call is performed more than once, but I'm not sure that's "the way to go" concerning this issue. I'm guessing that a SIGINT on an npm process is expected to quit in a certain timeframe, which is why npm passes it on one more time (always only twice).

Has anyone come across this problem and found a viable solution?

Thanks!

Gemmagemmate answered 16/2, 2019 at 10:31 Comment(5)
can't reproduce. Can you add more of the code that's causing you this behavior?Hanser
hey @DanieleDellafiore, thanks for taking the time. Here's a gist with some instructions and an example: gist.github.com/jsmrcaga/c84236eece0c27baffec28e79896d4a4Gemmagemmate
does not happen.. node/npm version?Hanser
It also happens to me, but not on all environments. When I run npm->node locally with gitbash on Windows10 (and Windows7) and with GNOME terminal on Ubuntu it does not happen. When I run on centOS through ssh, with npm start and close with CTRL+C, and also with systemctl start/stop service the termination signal is sent twice, while after starting with node index.js it is sent only once. Node 8.15.0 and Npm 6.4.1 on all setups.Cause
if anyone reading this is running puppeteer in their node script, be sure to set handleSIGINT to false when launching the browser: await puppeteer.launch({ handleSIGINT: false, ...}) cause puppeteer calls process.exit() on SIGINT.Mediatorial
H
6

You might want to run your command's directly via node, rather than via npm start. NPM can be a cause for wierd trapping of signals, see https://lisk.io/blog/tutorial/why-we-stopped-using-npm-start-child-processes.

Your SIGINT handler can get called multiple times, you should write your code to protect against that.

Also if you are running parent/child processes, see https://github.com/jtlapp/node-cleanup

When you hit Ctrl-C, you send a SIGINT signal to each process in the current process group. A process group is set of processes that are all supposed to end together as a group instead of persisting independently. However, some programs, such as Emacs, intercept and repurpose SIGINT so that it does not end the process. In such cases, SIGINT should not end any processes of the group.

Additionally, calling process.exit is not necessary most of the time, see https://nodejs.org/api/process.html#process_process_exit_code

Instead set process.exitCode, remove your signal handlers and then re-raise the signal via process.kill(process.pid, signal)

Hercegovina answered 18/2, 2020 at 4:56 Comment(3)
Its working fine with recent npm version - 6.14.15Giannagianni
The solution is to use process.once() instead of process.on().Mages
@Mages apparently not, even if it was a good idea.Superabundant
S
5

I know this post has been around for a while but unfortunately I'm getting the same issue. When running just node <app.js> it fires just one signal. That signal comes twice if running npm start. Instead a counter, I suggest check if the server is still listening, if yes, proceed with your logic and internal termination (like db connections etc):

process.on('SIGINT', function(){
 if (server.listening) {
    server.close(function (err) {
      if (err) {
        console.error(err);
        process.exit(1)
      }
      process.exit(0);
    })
 }
})
Sisterinlaw answered 27/1, 2020 at 14:35 Comment(0)
C
2

A cleaner solution would be to wrap the handler for the disconnect in a promise and due to promise internal implementation the resolve will be called only once.

new Promise((resolve) => process.on('SIGINT', resolve))
   .then(() => db.disconnect())
   .then(() => process.exit(0))
   .catch(() => process.exit(1));

Note: I would suggest to avoid calling process.exit() explicitly and let the node to exit by itself. If from some reasons the process didn't exit it means that there is something that blocks it (ex: ports listening, open/pending tcp connections, uncleared setInterval or setTimeout references)

Chippy answered 2/2, 2021 at 10:52 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.