在Node.js退出之前进行清理操作


326

我想告诉Node.js无论出于何种原因(Ctrl+ C,异常或任何其他原因)总是在退出之前总是做一些事情。

我尝试了这个:

process.on('exit', function (){
    console.log('Goodbye!');
});

我开始了该过程,将其杀死,但没有任何反应。我再次启动它,按Ctrl+ C,仍然没有任何反应...


Answers:


511

更新:

您可以注册一个处理程序,process.on('exit')并在任何其他情况下(SIGINT或未处理的异常)进行调用process.exit()

process.stdin.resume();//so the program will not close instantly

function exitHandler(options, exitCode) {
    if (options.cleanup) console.log('clean');
    if (exitCode || exitCode === 0) console.log(exitCode);
    if (options.exit) process.exit();
}

//do something when app is closing
process.on('exit', exitHandler.bind(null,{cleanup:true}));

//catches ctrl+c event
process.on('SIGINT', exitHandler.bind(null, {exit:true}));

// catches "kill pid" (for example: nodemon restart)
process.on('SIGUSR1', exitHandler.bind(null, {exit:true}));
process.on('SIGUSR2', exitHandler.bind(null, {exit:true}));

//catches uncaught exceptions
process.on('uncaughtException', exitHandler.bind(null, {exit:true}));

4
有没有办法在同一位置处理Ctrl + C和通常的退出,还是必须编写两个单独的处理程序?那么其他类型的退出(例如未处理的异常)如何处理呢?对于这种情况,有一个特定的处理程序,但是我应该使用同一处理程序的第三副本来处理吗?
Erel Segal-Halevi

1
@RobFox resume()初始化读取过程。默认情况下,Stdin暂停。:您可以了解更多关于github.com/joyent/node/blob/...
周华健Condrea

65
请注意,您是在处理程序中must only执行synchronous操作exit
Lewis Lewis

2
@KesemDavid我认为您应该改用该beforeExit事件。
刘易斯

22
该解决方案存在许多问题。(1)它不向父进程报告信号。(2)它不会将退出代码传达给父进程。(3)不允许忽略Ctrl-C SIGINT的类似Emacs的子级。(4)不允许异步清理。(5)它不能stderr在多个清除处理程序之间协调单个消息。我编写了一个模块来完成所有这一切,github.com/jtlapp/node-cleanup,最初是基于下面的cleanup.js解决方案,但是根据反馈进行了很大的修改。希望对您有所帮助。
Joe Lapp

180

下面的脚本允许所有退出条件都使用一个处理程序。它使用应用程序特定的回调函数来执行自定义清理代码。

cleanup.js

// Object to capture process exits and call app specific cleanup function

function noOp() {};

exports.Cleanup = function Cleanup(callback) {

  // attach user callback to the process event emitter
  // if no callback, it will still exit gracefully on Ctrl-C
  callback = callback || noOp;
  process.on('cleanup',callback);

  // do app specific cleaning before exiting
  process.on('exit', function () {
    process.emit('cleanup');
  });

  // catch ctrl+c event and exit normally
  process.on('SIGINT', function () {
    console.log('Ctrl-C...');
    process.exit(2);
  });

  //catch uncaught exceptions, trace, then exit normally
  process.on('uncaughtException', function(e) {
    console.log('Uncaught Exception...');
    console.log(e.stack);
    process.exit(99);
  });
};

此代码拦截未捕获的异常,Ctrl+ C和正常退出事件。然后,它在退出之前调用一个可选的用户清除回调函数,用一个对象处理所有退出条件。

该模块只是扩展了过程对象,而不是定义另一个事件发射器。如果没有应用特定的回调,则清理默认为无操作功能。这足以供我使用Ctrl+ 退出时仍在运行子进程的地方使用C

您可以根据需要轻松添加其他退出事件,例如SIGHUP。注意:根据NodeJS手册,SIGKILL不能具有侦听器。下面的测试代码演示了使用cleanup.js的各种方法

// test cleanup.js on version 0.10.21

// loads module and registers app specific cleanup callback...
var cleanup = require('./cleanup').Cleanup(myCleanup);
//var cleanup = require('./cleanup').Cleanup(); // will call noOp

// defines app specific callback...
function myCleanup() {
  console.log('App specific cleanup code...');
};

// All of the following code is only needed for test demo

// Prevents the program from closing instantly
process.stdin.resume();

// Emits an uncaught exception when called because module does not exist
function error() {
  console.log('error');
  var x = require('');
};

// Try each of the following one at a time:

// Uncomment the next line to test exiting on an uncaught exception
//setTimeout(error,2000);

// Uncomment the next line to test exiting normally
//setTimeout(function(){process.exit(3)}, 2000);

// Type Ctrl-C to test forced exit 

@ Pier-LucGendreau这个特定代码在哪里?
hownowbrowncow

11
我发现这段代码是必不可少的,并为其创建了一个节点包,并对其进行了修改,并将其归功于您和这个SO答案。希望没事,@ CanyonCasa。谢谢!npmjs.com/package/node-cleanup
Joe Lapp

3
我喜欢清理。但是我不喜欢process.exit(0); cons.org/cracauer/sigint.html我的感觉是您应该让内核来处理破坏。您退出的方式与SIGINT不同。SIGINT不会以2退出。您误认为SIGINT带有错误代码。他们不一样。实际上Ctrl + C与130存在。不是2。tldp.org/LDP/abs/html/exitcodes.html
Banjocat

5
我改写了 npmjs.com/package/node-cleanup,以便每个@Banjocat的链接可以使SIGINT处理与其他进程很好地配合。现在,它也可以正确地将信号中继到父进程,而不是调用process.exit()。现在,清理处理程序可以灵活地充当退出代码或信号的函数,可以根据需要卸载清理处理程序,以支持异步清理或防止循环清理。现在,它与上面的代码几乎没有相似之处。
Joe Lapp

3
忘了提到我也做了一个(希望)全面的测试套件。
乔·拉普

29

这捕获了我发现可以处理的每个退出事件。到目前为止看起来还算可靠和干净。

[`exit`, `SIGINT`, `SIGUSR1`, `SIGUSR2`, `uncaughtException`, `SIGTERM`].forEach((eventType) => {
  process.on(eventType, cleanUpServer.bind(null, eventType));
})

这太棒了!
安德拉尼克·霍夫森

很棒的工作,我现在正在生产中使用它!谢谢一群!
兰迪

20

“退出”是在节点内部完成事件循环时触发的事件,而在外部终止进程时不会触发。

您正在寻找的是在SIGINT上执行某些操作。

http://nodejs.org/api/process.html#process_signal_events上的文档给出了一个示例:

收听SIGINT的示例:

// Start reading from stdin so we don't exit.
process.stdin.resume();

process.on('SIGINT', function () {
  console.log('Got SIGINT.  Press Control-D to exit.');
});

注意:这似乎会中断sigint,并且在完成代码后需要调用process.exit()。


1
有没有办法在同一个地方同时处理Ctrl + C和正常退出?还是我必须编写两个相同的处理程序?
Erel Segal-Halevi 2012年

就像要注意的那样,如果必须通过kill命令结束节点,kill -2则将传递SIGINT代码。我们必须这样做,因为我们将节点记录到txt文件中,因此无法使用Ctrl +C。
2013年

9
function fnAsyncTest(callback) {
    require('fs').writeFile('async.txt', 'bye!', callback);
}

function fnSyncTest() {
    for (var i = 0; i < 10; i++) {}
}

function killProcess() {

    if (process.exitTimeoutId) {
        return;
    }

    process.exitTimeoutId = setTimeout(() => process.exit, 5000);
    console.log('process will exit in 5 seconds');

    fnAsyncTest(function() {
        console.log('async op. done', arguments);
    });

    if (!fnSyncTest()) {
        console.log('sync op. done');
    }
}

// https://nodejs.org/api/process.html#process_signal_events
process.on('SIGTERM', killProcess);
process.on('SIGINT', killProcess);

process.on('uncaughtException', function(e) {

    console.log('[uncaughtException] app will be terminated: ', e.stack);

    killProcess();
    /**
     * @https://nodejs.org/api/process.html#process_event_uncaughtexception
     *  
     * 'uncaughtException' should be used to perform synchronous cleanup before shutting down the process. 
     * It is not safe to resume normal operation after 'uncaughtException'. 
     * If you do use it, restart your application after every unhandled exception!
     * 
     * You have been warned.
     */
});

console.log('App is running...');
console.log('Try to press CTRL+C or SIGNAL the process with PID: ', process.pid);

process.stdin.resume();
// just for testing

4
这个答案值得所有荣耀,但是由于没有解释,很不幸,也没有投票。什么显著这个答案是,该医生说“监听功能只能执行同步操作。Node.js的过程中会调用‘退出’事件侦听器导致事件循环仍在排队任何额外的工作被抛弃后立即退出。 ”,此答案克服了该限制!
xpt




0

在尝试其他答案之后,这是我针对该任务的解决方案。实施这种方式有助于我将清理集中在一个地方,从而避免了双重处理清理。

  1. 我想将所有其他退出代码路由到“退出”代码。
const others = [`SIGINT`, `SIGUSR1`, `SIGUSR2`, `uncaughtException`, `SIGTERM`]
others.forEach((eventType) => {
    process.on(eventType, exitRouter.bind(null, { exit: true }));
})
  1. exitRouter的工作是调用process.exit()
function exitRouter(options, exitCode) {
   if (exitCode || exitCode === 0) console.log(`ExitCode ${exitCode}`);
   if (options.exit) process.exit();
}
  1. 在“退出”时,使用新功能处理清理
function exitHandler(exitCode) {
  console.log(`ExitCode ${exitCode}`);
  console.log('Exiting finally...')
}

process.on('exit', exitHandler)

出于演示目的,这是我的要旨。在文件中,我添加了setTimeout来伪造正在运行的进程。

如果您node node-exit-demo.js不执行任何操作,则在2秒钟后,您将看到日志:

The service is finish after a while.
ExitCode 0
Exiting finally...

否则,如果在服务完成之前,您以终止ctrl+C,您将看到:

^CExitCode SIGINT
ExitCode 0
Exiting finally...

发生的事情是Node进程最初以代码SIGINT退出,然后路由到process.exit(),最后以退出代码0退出。


-1

如果该进程是由另一个节点进程生成的,例如:

var child = spawn('gulp', ['watch'], {
    stdio: 'inherit',
});

然后您尝试通过以下方式将其杀死:

child.kill();

这是处理事件的方式(在孩子身上):

process.on('SIGTERM', function() {
    console.log('Goodbye!');
});
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.