Führen Sie eine Bereinigungsaktion aus, kurz bevor Node.js beendet wird


325

Ich möchte Node.js anweisen, immer etwas zu tun, bevor es beendet wird, aus welchem ​​Grund auch immer - Ctrl+ C, eine Ausnahme oder einen anderen Grund.

Ich habe es versucht:

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

Ich habe den Prozess gestartet, ihn getötet und nichts ist passiert. Ich habe es wieder gestartet, Ctrl+ gedrückt Cund trotzdem ist nichts passiert ...


Antworten:


511

AKTUALISIEREN:

Sie können einen Handler für process.on('exit')und in jedem anderen Fall ( SIGINToder für eine nicht behandelte Ausnahme) zum Aufrufen registrierenprocess.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
Gibt es eine Möglichkeit, sowohl Strg + C als auch einen normalen Exit an derselben Stelle zu handhaben, oder muss ich zwei separate Handler schreiben? Was ist mit anderen Arten von Exits, wie z. B. nicht behandelten Ausnahmen - es gibt einen bestimmten Handler für diesen Fall, aber sollte ich dies mit einer dritten Kopie desselben Handlers behandeln?
Erel Segal-Halevi

1
@RobFox resume () initialisiert den Lesevorgang. Stdin ist standardmäßig angehalten. Sie können mehr lesen auf: github.com/joyent/node/blob/…
Emil Condrea

65
Beachten Sie must only, dass Sie synchronousOperationen in exitHandler
Lewis

2
@KesemDavid Ich denke, Sie sollten beforeExitstattdessen das Ereignis verwenden.
Lewis

22
Diese Lösung weist zahlreiche Probleme auf. (1) Es werden keine Signale an übergeordnete Prozesse gemeldet. (2) Der Exit-Code wird nicht an den übergeordneten Prozess übertragen. (3) Emacs-ähnliche Kinder, die Strg-C-Zeichen ignorieren, sind nicht zulässig. (4) Eine asynchrone Bereinigung ist nicht möglich. (5) Es wird keine einzelne stderrNachricht über mehrere Bereinigungshandler hinweg koordiniert . Ich habe ein Modul geschrieben, das all dies tut, github.com/jtlapp/node-cleanup , das ursprünglich auf der folgenden Lösung von cleanup.js basiert, aber aufgrund von Rückmeldungen stark überarbeitet wurde. Ich hoffe es erweist sich als hilfreich.
Joe Lapp

179

Das folgende Skript ermöglicht einen einzigen Handler für alle Beendigungsbedingungen. Es verwendet eine app-spezifische Rückruffunktion, um benutzerdefinierten Bereinigungscode auszuführen.

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);
  });
};

Dieser Code fängt nicht erfasste Ausnahmen Ctrl+ Cund normale Exit-Ereignisse ab. Anschließend wird vor dem Beenden eine einzelne optionale Rückruffunktion zur Benutzerbereinigung aufgerufen, die alle Beendigungsbedingungen mit einem einzigen Objekt behandelt.

Das Modul erweitert einfach das Prozessobjekt, anstatt einen anderen Ereignisemitter zu definieren. Ohne einen app-spezifischen Rückruf ist die Bereinigung standardmäßig eine No-Op-Funktion. Dies war ausreichend für meine Verwendung, bei der untergeordnete Prozesse beim Beenden von Ctrl+ ausgeführt wurden C.

Sie können nach Bedarf problemlos andere Exit-Ereignisse wie SIGHUP hinzufügen. Hinweis: Gemäß NodeJS-Handbuch kann SIGKILL keinen Listener haben. Der folgende Testcode zeigt verschiedene Möglichkeiten zur Verwendung von 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 Wohin geht dieser spezielle Code?
Hownowbrowncow

10
Ich habe diesen Code für unverzichtbar befunden und ein Knotenpaket dafür erstellt, mit Änderungen, die Ihnen und dieser SO-Antwort gutgeschrieben werden. Hoffe das ist okay, @CanyonCasa. Vielen Dank! npmjs.com/package/node-cleanup
Joe Lapp

3
Ich liebe die Aufräumarbeiten. Aber ich mag den process.exit (0) nicht; cons.org/cracauer/sigint.html Meiner Meinung nach sollten Sie den Kernel mit der Zerstörung umgehen lassen. Sie beenden nicht auf die gleiche Weise wie ein SIGINT. SIGINT wird nicht mit 2 beendet. Sie verwechseln SIGINT mit Fehlercode. Sie sind nicht gleich. Tatsächlich gibt es Strg + C mit 130. Nicht 2. tldp.org/LDP/abs/html/exitcodes.html
Banjocat

5
Ich habe npmjs.com/package/node-cleanup neu geschrieben, damit die SIGINT-Behandlung gemäß dem Link von @ Banjocat gut mit anderen Prozessen funktioniert. Es leitet jetzt auch Signale korrekt an den übergeordneten Prozess weiter, anstatt sie aufzurufen process.exit(). Bereinigungshandler haben jetzt die Flexibilität, sich in Abhängigkeit von Exit-Code oder Signal zu verhalten, und Bereinigungshandler können nach Bedarf deinstalliert werden, um die asynchrone Bereinigung zu unterstützen oder eine zyklische Bereinigung zu verhindern. Es hat jetzt wenig Ähnlichkeit mit dem obigen Code.
Joe Lapp

3
Ich habe vergessen zu erwähnen, dass ich auch eine (hoffentlich) umfassende Testsuite erstellt habe.
Joe Lapp

28

Dies fängt jedes Exit-Ereignis ab, das behandelt werden kann. Scheint bisher recht zuverlässig und sauber zu sein.

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

Das ist fantastisch!
Andranik Hovesyan

Tolle Arbeit, ich benutze sie gerade in der Produktion! Vielen Dank!
Randy

20

"exit" ist ein Ereignis, das ausgelöst wird, wenn der Knoten seine Ereignisschleife intern beendet. Es wird nicht ausgelöst, wenn Sie den Prozess extern beenden.

Was Sie suchen, ist etwas auf einem SIGINT auszuführen.

Die Dokumente unter http://nodejs.org/api/process.html#process_signal_events geben ein Beispiel:

Beispiel für das Hören auf 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.');
});

Hinweis: Dies scheint die Sigint zu unterbrechen und Sie müssten process.exit () aufrufen, wenn Sie mit Ihrem Code fertig sind.


1
Gibt es eine Möglichkeit, sowohl Strg + C als auch einen normalen Ausgang an derselben Stelle zu handhaben? Oder muss ich zwei identische Handler schreiben?
Erel Segal-Halevi

Nur als Hinweis, wenn Sie den Knoten durch einen Kill-Befehl beenden müssen, kill -2wird der SIGINTCode übergeben. Wir müssen dies auf diese Weise tun, da wir eine Knotenprotokollierung in einer txt-Datei haben, sodass Strg + C nicht möglich ist.
Aust

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
Diese Antwort verdient alle Ehre, aber da es keine Erklärung gibt, gibt es leider auch keine Abstimmung. Das Wichtigste an dieser Antwort ist, dass das Dokument sagt : "Listener-Funktionen dürfen nur synchrone Operationen ausführen. Der Node.js-Prozess wird sofort nach dem Aufruf der 'exit'-Ereignis-Listener beendet, wodurch zusätzliche Arbeiten, die sich noch in der Ereignisschleife befinden, abgebrochen werden. " , und diese Antwort überwindet diese Einschränkung!
xpt

7

Ich wollte deathhier nur das Paket erwähnen : https://github.com/jprichardson/node-death

Beispiel:

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

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

Anscheinend müssen Sie das Programm explizit mit process.exit () innerhalb des Rückrufs beenden. Das hat mich gestolpert.
Nick Manning


0

Hier ist ein netter Hack für Windows

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

0

Nachdem ich mit anderen Antworten herumgespielt habe, ist hier meine Lösung für diese Aufgabe. Durch die Implementierung auf diese Weise kann ich die Bereinigung an einem Ort zentralisieren und verhindern, dass die Bereinigung doppelt behandelt wird.

  1. Ich möchte alle anderen Exit-Codes an den Exit-Code weiterleiten.
const others = [`SIGINT`, `SIGUSR1`, `SIGUSR2`, `uncaughtException`, `SIGTERM`]
others.forEach((eventType) => {
    process.on(eventType, exitRouter.bind(null, { exit: true }));
})
  1. Der exitRouter ruft process.exit () auf.
function exitRouter(options, exitCode) {
   if (exitCode || exitCode === 0) console.log(`ExitCode ${exitCode}`);
   if (options.exit) process.exit();
}
  1. Führen Sie beim Beenden die Bereinigung mit einer neuen Funktion durch
function exitHandler(exitCode) {
  console.log(`ExitCode ${exitCode}`);
  console.log('Exiting finally...')
}

process.on('exit', exitHandler)

Für den Demo-Zweck ist dies ein Link zu meinem Kern. In der Datei füge ich ein setTimeout hinzu, um den laufenden Prozess zu fälschen.

Wenn Sie laufen node node-exit-demo.jsund nichts tun, sehen Sie nach 2 Sekunden das Protokoll:

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

Andernfalls sehen Sie Folgendes ctrl+C, wenn Sie vor Beendigung des Dienstes mit beenden :

^CExitCode SIGINT
ExitCode 0
Exiting finally...

Was passiert ist, ist, dass der Knotenprozess zunächst mit dem Code SIGINT beendet wurde, dann zu process.exit () weitergeleitet und schließlich mit dem Beendigungscode 0 beendet wird.


-1

In dem Fall, in dem der Prozess von einem anderen Knotenprozess erzeugt wurde, wie z.

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

Und Sie versuchen es später zu töten, über:

child.kill();

So behandeln Sie das Ereignis [für das Kind]:

process.on('SIGTERM', function() {
    console.log('Goodbye!');
});
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.