Realizar una acción de limpieza justo antes de que salga Node.js


327

Quiero decirle a Node.js que siempre haga algo justo antes de salir, por cualquier razón, Ctrl+ C, una excepción o cualquier otra razón.

Intenté esto:

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

Comencé el proceso, lo maté y no pasó nada. Lo comencé de nuevo, presioné Ctrl+ C, y todavía no pasó nada ...


Respuestas:


511

ACTUALIZAR:

Puede registrar un controlador para process.on('exit')y en cualquier otro caso ( SIGINTo excepción no controlada) para llamarprocess.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}));

44
¿Hay alguna manera de manejar Ctrl + C y una salida habitual en el mismo lugar, o tengo que escribir dos controladores separados? ¿Qué pasa con otros tipos de salida, como la excepción no controlada? Hay un controlador específico para ese caso, pero ¿debo manejar esto con una tercera copia del mismo controlador?
Erel Segal-Halevi

1
@RobFox resume () inicializa el proceso de lectura. Stdin está en pausa de forma predeterminada. Puede leer más en: github.com/joyent/node/blob/…
Emil Condrea

65
Tenga en cuenta que must onlyrealiza synchronousoperaciones en el exitcontrolador
Lewis

2
@KesemDavid Creo que deberías usar el beforeExitevento en su lugar.
Lewis

22
Esta solución tiene numerosos problemas. (1) No informa las señales a los procesos principales. (2) No transmite el código de salida al proceso padre. (3) No permite que los niños parecidos a Emacs ignoren Ctrl-C SIGINT. (4) No permite la limpieza asincrónica. (5) No coordina un solo stderrmensaje en varios manejadores de limpieza. He escrito un módulo que hace todo esto, github.com/jtlapp/node-cleanup , originalmente basado en la solución cleanup.js a continuación, pero revisado en gran medida según los comentarios. Espero que sea útil.
Joe Lapp

180

El siguiente script permite tener un único controlador para todas las condiciones de salida. Utiliza una función de devolución de llamada específica de la aplicación para realizar un código de limpieza personalizado.

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

Este código intercepta excepciones no capturadas, Ctrl+ Cy eventos de salida normales. Luego llama a una única función opcional de devolución de llamada de limpieza del usuario antes de salir, manejando todas las condiciones de salida con un solo objeto.

El módulo simplemente extiende el objeto del proceso en lugar de definir otro emisor de eventos. Sin una devolución de llamada específica de la aplicación, la limpieza por defecto es una función no operativa. Esto fue suficiente para mi uso donde los procesos secundarios se dejaron en ejecución al salir por Ctrl+ C.

Puede agregar fácilmente otros eventos de salida, como SIGHUP, según lo desee. Nota: según el manual de NodeJS, SIGKILL no puede tener un oyente. El siguiente código de prueba muestra varias formas de usar 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, ¿a dónde va este código específico?
hownowbrowncow

11
He encontrado este código indispensable y he creado un paquete de nodos para él, con modificaciones, acreditándote a ti y a esta respuesta SO. Espero que esté bien, @CanyonCasa. ¡Gracias! npmjs.com/package/node-cleanup
Joe Lapp

3
Me encanta la limpieza Pero no me gusta el proceso.exit (0); cons.org/cracauer/sigint.html Creo que debería dejar que el núcleo maneje la destrucción. No está saliendo de la misma manera que un SIGINT. SIGINT no sale con 2. Está confundiendo SIGINT con el código de error. No son lo mismo En realidad, Ctrl + C existe con 130. No 2. tldp.org/LDP/abs/html/exitcodes.html
Banjocat

55
He reescrito npmjs.com/package/node-cleanup para que el manejo de SIGINT funcione bien con otros procesos, según el enlace de @ Banjocat. Ahora también transmite correctamente señales al proceso padre en lugar de llamar process.exit(). Los controladores de limpieza ahora tienen la flexibilidad de comportarse como una función del código de salida o señal, y los controladores de limpieza se pueden desinstalar, según sea necesario para admitir la limpieza asincrónica o evitar la limpieza cíclica. Ahora tiene poco parecido con el código anterior.
Joe Lapp

3
Olvidé mencionar que también hice un (con suerte) conjunto de pruebas completo.
Joe Lapp

30

Esto atrapa todos los eventos de salida que puedo encontrar que se pueden manejar. Parece bastante confiable y limpio hasta ahora.

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

¡Esto es asombroso!
Andranik Hovesyan

¡Buen trabajo, lo estoy usando en producción ahora mismo! ¡Gracias un montón!
Randy

20

"salir" es un evento que se activa cuando el nodo finaliza su ciclo de eventos internamente, no se activa cuando finaliza el proceso externamente.

Lo que estás buscando es ejecutar algo en un SIGINT.

Los documentos en http://nodejs.org/api/process.html#process_signal_events dan un ejemplo:

Ejemplo de escuchar 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: esto parece interrumpir la señalización y deberá llamar a process.exit () cuando termine con su código.


1
¿Hay alguna manera de manejar Ctrl + C y una salida habitual en el mismo lugar? ¿O tengo que escribir dos controladores idénticos?
Erel Segal-Halevi

Solo como una nota, si tiene que terminar el nodo con un comando kill kill -2, pasará el SIGINTcódigo. Tenemos que hacerlo de esta manera porque tenemos registro de nodos en un archivo txt, por lo que Ctrl + C no es posible.
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

44
Esta respuesta merece toda la gloria, pero desafortunadamente, como no hay explicación, tampoco hay voto positivo. Lo significativo de esta respuesta es que, según el documento , "las funciones de escucha solo deben realizar operaciones sincrónicas. El proceso Node.js se cerrará inmediatamente después de llamar a los oyentes de eventos 'exit', lo que provocará que se abandone cualquier trabajo adicional en cola en el bucle de eventos. " , y esta respuesta supera esa limitación!
xpt

7

Solo quería mencionar el deathpaquete aquí: https://github.com/jprichardson/node-death

Ejemplo:

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

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

Parece que tiene que salir explícitamente del programa con process.exit () dentro de la devolución de llamada. Esto me hizo tropezar.
Nick Manning


0

Aquí hay un buen truco para Windows

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

0

Después de jugar con otra respuesta, aquí está mi solución para esta tarea. La implementación de esta manera me ayuda a centralizar la limpieza en un solo lugar, evitando el doble manejo de la limpieza.

  1. Me gustaría enrutar todos los demás códigos de salida al código de 'salida'.
const others = [`SIGINT`, `SIGUSR1`, `SIGUSR2`, `uncaughtException`, `SIGTERM`]
others.forEach((eventType) => {
    process.on(eventType, exitRouter.bind(null, { exit: true }));
})
  1. Lo que hace el exitRouter es llamar a process.exit ()
function exitRouter(options, exitCode) {
   if (exitCode || exitCode === 0) console.log(`ExitCode ${exitCode}`);
   if (options.exit) process.exit();
}
  1. En 'salir', maneje la limpieza con una nueva función
function exitHandler(exitCode) {
  console.log(`ExitCode ${exitCode}`);
  console.log('Exiting finally...')
}

process.on('exit', exitHandler)

Para el propósito de demostración, este es un enlace a mi esencia. En el archivo, agrego un setTimeout para falsificar el proceso en ejecución.

Si corre node node-exit-demo.jsy no hace nada, luego de 2 segundos, verá el registro:

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

De lo contrario, si antes de que finalice el servicio, termina ctrl+C, verá:

^CExitCode SIGINT
ExitCode 0
Exiting finally...

Lo que sucedió es que el proceso Node salió inicialmente con el código SIGINT, luego se enruta a process.exit () y finalmente salió con el código de salida 0.


-1

En el caso donde el proceso fue generado por otro proceso de nodo, como:

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

E intentas matarlo más tarde, a través de:

child.kill();

Así es como manejas el evento [en el niño]:

process.on('SIGTERM', function() {
    console.log('Goodbye!');
});
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.