Esecuzione di un'azione di pulizia poco prima della chiusura di Node.js


327

Voglio dire a Node.js di fare sempre qualcosa prima che esca, per qualsiasi motivo - Ctrl+ C, un'eccezione o qualsiasi altro motivo.

Ho provato questo:

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

Ho iniziato il processo, l'ho ucciso e non è successo niente. L'ho riavviato, ho premuto Ctrl+ Ce ancora non è successo nulla ...


Risposte:


511

AGGIORNARE:

È possibile registrare un gestore per process.on('exit')e in qualsiasi altro caso ( SIGINTo eccezione non gestita) da chiamareprocess.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
C'è un modo per gestire sia Ctrl + C che una normale uscita nello stesso posto, o devo scrivere due gestori separati? Che dire di altri tipi di uscita, come l'eccezione non gestita: esiste un gestore specifico per quel caso, ma dovrei gestirlo con una terza copia dello stesso gestore?
Erel Segal-Halevi,

1
@RobFox resume () inizializza il processo di lettura. Stdin è in pausa per impostazione predefinita. Puoi leggere di più su: github.com/joyent/node/blob/…
Emil Condrea,

65
Si noti che si must onlyeseguono synchronousoperazioni nel exitgestore
Lewis

2
@KesemDavid Penso che dovresti usare beforeExitinvece l' evento.
Lewis,

22
Questa soluzione presenta numerosi problemi. (1) Non segnala segnali ai processi parent. (2) Non trasmette il codice di uscita al processo padre. (3) Non consente ai bambini simili a Emacs che ignorano Ctrl-C SIGINT. (4) Non consente la pulizia asincrona. (5) Non coordina un singolo stderrmessaggio tra più gestori di pulizia. Ho scritto un modulo che fa tutto questo, github.com/jtlapp/node-cleanup , originariamente basato sulla soluzione cleanup.js di seguito, ma ampiamente rivisto in base al feedback. Spero che si riveli utile.
Joe Lapp,

180

Lo script seguente consente di avere un unico gestore per tutte le condizioni di uscita. Utilizza una funzione di richiamata specifica dell'app per eseguire codice di pulizia personalizzato.

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

Questo codice intercetta le eccezioni non rilevate, Ctrl+ Ce gli eventi di uscita normali. Quindi chiama una singola funzione di callback di pulizia utente opzionale prima di uscire, gestendo tutte le condizioni di uscita con un singolo oggetto.

Il modulo estende semplicemente l'oggetto di processo invece di definire un altro emettitore di eventi. Senza una callback specifica per l'app, la pulizia viene automaticamente impostata su una funzione no op. Questo era sufficiente per il mio uso in cui i processi figlio venivano lasciati in esecuzione all'uscita da Ctrl+ C.

Puoi facilmente aggiungere altri eventi di uscita come SIGHUP, se lo desideri. Nota: secondo il manuale di NodeJS, SIGKILL non può avere un ascoltatore. Il codice di test seguente mostra vari modi di usare 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 dove va questo codice specifico?
hownowbrowncow,

11
Ho trovato questo codice indispensabile e ho creato un pacchetto nodo per esso, con modifiche, accreditando te e questa risposta SO. Spero che vada bene, @CanyonCasa. Grazie! npmjs.com/package/node-cleanup
Joe Lapp,

3
Adoro la pulizia. Ma non mi piace process.exit (0); cons.org/cracauer/sigint.html La mia sensazione è che dovresti lasciare che il kernel gestisca la distruzione. Non stai uscendo allo stesso modo di un SIGINT. SIGINT non termina con 2. Stai sbagliando SIGINT con un codice di errore. Non sono gli stessi. In realtà Ctrl + C esiste con 130. Non 2. tldp.org/LDP/abs/html/exitcodes.html
Banjocat,

5
Ho riscritto npmjs.com/package/node-cleanup in modo che la gestione di SIGINT funzioni bene con altri processi, tramite il link di @ Banjocat. Inoltre ora inoltra correttamente i segnali al processo genitore invece di chiamare process.exit(). I gestori della pulizia ora hanno la flessibilità di comportarsi come una funzione del codice o del segnale di uscita e i gestori della pulizia possono essere disinstallati, se necessario per supportare la pulizia asincrona o impedire la pulizia ciclica. Ora ha poca somiglianza con il codice sopra.
Joe Lapp,

3
Ho dimenticato di dire che ho anche creato una suite di test (speriamo) completa.
Joe Lapp,

30

Questo cattura ogni evento di uscita che posso trovare che può essere gestito. Sembra abbastanza affidabile e pulito finora.

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

Questo e spettacolare!
Andranik Hovesyan,

Ottimo lavoro, lo sto usando in produzione proprio ora! Grazie mille!
randy

20

"exit" è un evento che viene attivato quando il nodo termina il suo ciclo di eventi internamente, non viene attivato quando si termina il processo esternamente.

Quello che stai cercando è eseguire qualcosa su un SIGINT.

I documenti su http://nodejs.org/api/process.html#process_signal_events forniscono un esempio:

Esempio di ascolto per 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.');
});

Nota: questo sembra interrompere il sigint e dovresti chiamare process.exit () quando finisci con il tuo codice.


1
C'è un modo per gestire sia Ctrl + C sia un'uscita normale nello stesso posto? O devo scrivere due gestori identici?
Erel Segal-Halevi,

Proprio come una nota, se devi terminare il nodo con un comando kill kill -2, passerà il SIGINTcodice. Dobbiamo farlo in questo modo perché abbiamo la registrazione dei nodi in un file txt, quindi Ctrl + C non è possibile.
Agosto

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
Questa risposta merita tutta la gloria, ma poiché non ci sono spiegazioni, sfortunatamente non c'è nemmeno un voto positivo. La cosa significativa di questa risposta, afferma il documento , "Le funzioni del listener devono eseguire solo operazioni sincrone. Il processo Node.js si chiuderà immediatamente dopo aver chiamato i listener di eventi 'exit' causando l'abbandono di qualsiasi lavoro aggiuntivo ancora in coda nel loop degli eventi. " , e questa risposta supera questa limitazione!
xpt,

7

Volevo solo menzionare il deathpacchetto qui: https://github.com/jprichardson/node-death

Esempio:

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

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

Sembra che tu debba uscire esplicitamente dal programma con process.exit () all'interno del callback. Questo mi ha fatto inciampare.
Nick Manning,


0

Ecco un bel trucco per Windows

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

0

Dopo aver giocato con altre risposte, ecco la mia soluzione per questo compito. L'implementazione in questo modo mi aiuta a centralizzare la pulizia in un unico posto, evitando la doppia gestione della pulizia.

  1. Vorrei instradare tutti gli altri codici in uscita verso il codice "exit".
const others = [`SIGINT`, `SIGUSR1`, `SIGUSR2`, `uncaughtException`, `SIGTERM`]
others.forEach((eventType) => {
    process.on(eventType, exitRouter.bind(null, { exit: true }));
})
  1. Quello che fa exitRouter sta chiamando process.exit ()
function exitRouter(options, exitCode) {
   if (exitCode || exitCode === 0) console.log(`ExitCode ${exitCode}`);
   if (options.exit) process.exit();
}
  1. All'uscita, gestisci la pulizia con una nuova funzione
function exitHandler(exitCode) {
  console.log(`ExitCode ${exitCode}`);
  console.log('Exiting finally...')
}

process.on('exit', exitHandler)

Ai fini della demo, questo è il link al mio contenuto. Nel file, aggiungo un setTimeout per simulare il processo in esecuzione.

Se corri node node-exit-demo.jse non fai nulla, dopo 2 secondi viene visualizzato il registro:

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

Altrimenti se prima che il servizio finisca, si termina ctrl+C, vedrai:

^CExitCode SIGINT
ExitCode 0
Exiting finally...

Quello che è successo è il processo Node uscito inizialmente con il codice SIGINT, quindi instrada a process.exit () e infine è uscito con il codice di uscita 0.


-1

Nel caso in cui il processo sia stato generato da un altro processo del nodo, come:

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

E cerchi di ucciderlo in seguito, tramite:

child.kill();

Ecco come gestisci l'evento [sul figlio]:

process.on('SIGTERM', function() {
    console.log('Goodbye!');
});
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.