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

Posted

技术标签:

【中文标题】在 Node.js 退出之前执行清理操作【英文标题】:Doing a cleanup action just before Node.js exits 【发布时间】:2012-12-11 11:46:39 【问题描述】:

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

我试过这个:

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

我启动了这个过程,然后终止了它,但什么也没发生。我又开始了,按了Ctrl+C,还是什么都没发生……

【问题讨论】:

见github.com/nodejs/node/issues/20804 【参考方案1】:

更新:

您可以为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));

【讨论】:

有没有办法在同一个地方同时处理 Ctrl+C 和通常的退出,或者我必须编写两个单独的处理程序?那么其他类型的退出呢,例如未处理的异常 - 这种情况有一个特定的处理程序,但我应该使用同一个处理程序的第三个副本来处理它吗? 注意你must onlyexit处理程序中执行synchronous操作 @KesemDavid 我认为你应该改用beforeExit 事件。 这个解决方案有很多问题。 (1) 它不向父进程报告信号。 (2) 它不向父进程传达退出代码。 (3) 它不允许忽略 Ctrl-C SIGINT 的类似 Emacs 的子级。 (4) 不允许异步清理。 (5) 它不会跨多个清理处理程序协调单个stderr 消息。我写了一个模块来完成这一切,github.com/jtlapp/node-cleanup,最初基于下面的 cleanup.js 解决方案,但根据反馈进行了很大的修改。我希望它会有所帮助。 你不能在on("exit", ...)处理程序中调用process.exit。这个处理程序应该在process.exit 调用nodejs.org/api/process.html#process_event_exit 之后调用,结果会很糟糕,例如,所有错误都会被忽略。【参考方案2】:

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

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 

【讨论】:

我发现这段代码不可或缺,并为它创建了一个节点包,并进行了修改,感谢您和这个 SO 答案。希望没关系,@CanyonCasa。谢谢! npmjs.com/package/node-cleanup 我喜欢清理工作。但我不喜欢 process.exit(0); cons.org/cracauer/sigint.html我的感觉是你应该让内核处理破坏。您退出的方式与 SIGINT 不同。 SIGINT 不会以 2 退出。您将 SIGINT 误认为是错误代码。他们不一样。实际上 Ctrl+C 与 130 存在。不是 2。tldp.org/LDP/abs/html/exitcodes.html 我已经重写了npmjs.com/package/node-cleanup,以便根据@Banjocat 的链接处理 SIGINT 与其他进程一起工作。它现在还可以正确地将信号中继到父进程,而不是调用process.exit()。清理处理程序现在可以灵活地作为退出代码或信号的函数运行,并且可以卸载清理处理程序,以支持异步清理或防止循环清理。它现在与上面的代码几乎没有相似之处。 忘了说我还做了一个(希望是)综合测试套件。 不确定您是否可以在退出回调中安全地执行process.emit('cleanup');。 nodejs 文档声明“侦听器函数只能执行同步操作。Node.js 进程将在调用 'exit' 事件侦听器后立即退出,从而导致事件循环中仍在排队的任何其他工作被放弃”【参考方案3】:

这会捕获我能找到的每个可以处理的退出事件。到目前为止看起来相当可靠和干净。

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

【讨论】:

这太棒了! 干得好,我现在正在生产中使用它!非常感谢! 很高兴获得这些活动的一些信息或资源链接 @Sagivb.g 过程信号记录在每个 *nix 发行版中。做man kill【参考方案4】:

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

您正在寻找的是在 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()。

【讨论】:

有没有办法在同一个地方同时处理 Ctrl+C 和通常的退出?还是我必须编写两个相同的处理程序? 请注意,如果您必须通过 kill 命令结束节点,则执行 kill -2 将传递 SIGINT 代码。我们必须这样做,因为我们有节点记录到一个 txt 文件,所以 Ctrl + C 是不可能的。【参考方案5】:
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

【讨论】:

这个答案值得所有荣耀,但由于没有解释,不幸的是也没有投票。关于这个答案的重要是,the doc says,“监听器函数只能执行同步操作。Node.js 进程将在调用 'exit' 事件监听器后立即退出,从而导致任何额外的工作仍然在事件循环中排队等待被放弃。“,这个答案克服了这个限制! 如果我没看错的话,退出总是需要固定的时间(五秒),如果异步操作需要更多时间,这可能太长或更糟,太短。跨度> 【参考方案6】:

只想在此处提及death 包:https://github.com/jprichardson/node-death

例子:

var ON_DEATH = require('death')(uncaughtException: true); //this is intentionally ugly

ON_DEATH(function(signal, err) 
  //clean up code here
)

【讨论】:

似乎您必须在回调中使用 process.exit() 显式退出程序。这让我大吃一惊。【参考方案7】:

async-exit-hook 似乎是处理此问题的最新解决方案。它是 exit-hook 的分叉/重写版本,支持退出前的异步代码。

【讨论】:

我最终使用了这个库,因为它允许我使用超时,这是我需要优雅地停止【参考方案8】:

我需要在退出时执行异步清理操作,这个问题中的答案都对我不起作用。

于是我自己试了一下,终于找到了这个:

process.once('uncaughtException', async () => 
  await cleanup()

  process.exit(0)
)

process.once('SIGINT', () =>  throw new Error() )

【讨论】:

【参考方案9】:

在玩弄了其他答案之后,这是我针对此任务的解决方案。实施这种方式有助于我将清理工作集中在一个地方,避免重复处理清理工作。

    我想将所有其他退出代码路由到“退出”代码。
const others = [`SIGINT`, `SIGUSR1`, `SIGUSR2`, `uncaughtException`, `SIGTERM`]
others.forEach((eventType) => 
    process.on(eventType, exitRouter.bind(null,  exit: true ));
)
    exitRouter 所做的是调用 process.exit()
function exitRouter(options, exitCode) 
   if (exitCode || exitCode === 0) console.log(`ExitCode $exitCode`);
   if (options.exit) process.exit();

    在“退出”时,使用新功能处理清理工作
function exitHandler(exitCode) 
  console.log(`ExitCode $exitCode`);
  console.log('Exiting finally...')


process.on('exit', exitHandler)

出于演示目的,this is link 我的要点。在文件中,我添加了一个 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 退出。

【讨论】:

【参考方案10】:

io.js 有一个 exit 和一个 beforeExit 事件,可以满足您的需求。

【讨论】:

【参考方案11】:

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

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

然后你试图通过以下方式杀死它:

child.kill();

这就是您处理事件的方式 [on the child]:

process.on('SIGTERM', function() 
    console.log('Goodbye!');
);

【讨论】:

【参考方案12】:

这是一个不错的 windows hack

process.on('exit', async () => 
    require('fs').writeFileSync('./tmp.js', 'crash', 'utf-8')
);

【讨论】:

如果你没有详细说明你的代码做了什么以及它实现了什么目标,这并不是一个很好的 hack。我只能猜这就是你投反对票的原因。

以上是关于在 Node.js 退出之前执行清理操作的主要内容,如果未能解决你的问题,请参考以下文章

node.js:程序要么意外退出,要么只是挂起

在 Node.js 中如何停止脚本执行,成功向控制台写入错误消息,然后退出脚本?

Node.js REPL(交互式解释器)

Node.js REPL(交互式解析器)

Node.js学习05:动手啦,基于node.js编写程序

如何在 node.js 中的“require”之后删除模块?