Node.jsが終了する直前にクリーンアップアクションを実行する


326

Node.jsに、何らかの理由(Ctrl+ C、例外、またはその他の理由)で、終了する直前に常に何かを実行するように指示します。

私はこれを試しました:

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

私はプロセスを始めて、それを殺しました、そして何も起こりませんでした。私はそれをもう一度始め、Ctrl+を押しましたCが、それでも何も起こりませんでした...


回答:


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と通常の出口の両方を同じ場所で処理する方法はありますか、または2つの別個のハンドラーを作成する必要がありますか?未処理の例外など、他のタイプの出口についてはどうですか?その場合には特定のハンドラーがありますが、同じハンドラーの3番目のコピーでこれを処理する必要がありますか?
— Erel Segal-Halevi 2012

1
@RobFox resume()は読み取りプロセスを初期化します。標準入力はデフォルトで一時停止されています。あなたは上の続きを読むことができます。github.com/joyent/node/blob/...
— エミルCondrea

65
ハンドラーで操作をmust only実行することに注意してくださいsynchronousexit
— ルイス

2
@KesemDavid beforeExit代わりにイベントを使用する必要があると思います。
— ルイス

22
このソリューションには多くの問題があります。(1)親プロセスにシグナルを報告しません。(2)終了コードを親プロセスに伝えません。(3)Ctrl-C SIGINTを無視するEmacsのような子は許可されません。(4)非同期クリーンアップは許可されません。(5)stderr複数のクリーンアップハンドラ間で単一のメッセージを調整しません。私はこれをすべて行うモジュールgithub.com/jtlapp/node-cleanupを書きました。元は以下のcleanup.jsソリューションに基づいていますが、フィードバックに基づいて大幅に変更されています。お役に立てば幸いです。
— Joe Lapp、2016

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および通常の終了イベントをインターセプトします。次に、終了する前に単一のオプションのユーザークリーンアップコールバック関数を呼び出し、単一のオブジェクトですべての終了条件を処理します。

このモジュールは、別のイベントエミッターを定義するのではなく、単にプロセスオブジェクトを拡張します。アプリ固有のコールバックがない場合、クリーンアップはデフォルトでno op関数になります。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 2016年

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で
— LDP /

5
npmjs.com/package/node-cleanupを書き直して、@ Banjocatのリンクに従って、SIGINT処理が他のプロセスでうまく機能するようにしました。また、を呼び出す代わりに、シグナルを親プロセスに正しくリレーするようになりましたprocess.exit()。クリーンアップハンドラーは、終了コードまたはシグナルの関数として動作する柔軟性を備え、非同期クリーンアップをサポートしたり、周期的なクリーンアップを防止したりするために、必要に応じてクリーンアップハンドラーをアンインストールできます。これは、上記のコードにほとんど似ていません。
— Joe Lapp、2016

3
私が(うまくいけば)包括的なテストスイートも作成したことを言及するのを忘れていました。
— Joe Lapp、2016

29

これは、処理できるすべての終了イベントをキャッチします。これまでのところ非常に信頼性が高く、クリーンなようです。

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

これはすごい!
— Andranik Hovesyan

すばらしい仕事、私は今それを生産で使用しています!本当にありがとう!
— ランディ

20

「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()を呼び出す必要があります。


1
Ctrl + Cと通常の終了の両方を同じ場所で処理する方法はありますか?または、2つの同一のハンドラーを作成する必要がありますか?
— Erel Segal-Halevi 2012

ちょうど注意として、killコマンドでノードを終了する必要がある場合はkill -2、SIGINTコードを渡します。ノードにtxtファイルへのロギングがあるため、Ctrl + Cを使用できないため、このようにする必要があります。
— オースト、2014

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
この答えはすべての栄光に値しますが、説明がないため、残念ながら賛成票もありません。どのような重要なこの答えはについて、DOCは言う、「リスナー関数は、同期の操作のみを実行する必要があります。Node.jsのプロセスはまだイベントループにキューイング任意の追加作業を放棄させる『終了』イベントリスナーを呼び出した後にすぐに終了します。 「そして、この答えはその制限を克服しています!
— XPT

7

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()でプログラムを明示的に終了する必要があるようです。これは私をつまずかせました。
— Nick Manning



0

他の答えで遊んだ後、これがこのタスクの私の解決策です。この方法を実装すると、クリーンアップを1か所に集中して、クリーンアップの二重処理を防ぐことができます。

  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!');
});
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.