Executando uma ação de limpeza imediatamente antes da saída do Node.js.


326

Quero dizer ao Node.js para sempre fazer algo antes de sair, por qualquer motivo - Ctrl+ C, uma exceção ou qualquer outro motivo.

Eu tentei isso:

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

Comecei o processo, o matei e nada aconteceu. Comecei novamente, pressionei Ctrl+ Ce ainda nada aconteceu ...


Respostas:


511

ATUALIZAR:

Você pode registrar um manipulador para process.on('exit')e em qualquer outro caso ( SIGINTou exceção não tratada) para chamarprocess.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
Existe uma maneira de lidar com Ctrl + C e uma saída normal no mesmo local ou preciso escrever dois manipuladores separados? E quanto a outros tipos de saída, como exceção não tratada - existe um manipulador específico para esse caso, mas devo lidar com isso com uma terceira cópia do mesmo manipulador?
Erel Segal-Halevi

1
O @RobFox resume () inicializa o processo de leitura. Stdin é pausado por padrão. Você pode ler mais em: github.com/joyent/node/blob/…
Emil Condrea

65
Observe que você must onlyexecuta synchronousoperações no exitmanipulador
Lewis

2
@KesemDavid Acho que você deveria usar o beforeExitevento.
Lewis

22
Esta solução tem vários problemas. (1) Não relata sinais para processos-pai. (2) Não transmite o código de saída para o processo pai. (3) Não permite crianças do tipo Emacs que ignoram Ctrl-C SIGINT. (4) Não permite limpeza assíncrona. (5) Ele não coordena uma única stderrmensagem entre vários manipuladores de limpeza. Eu escrevi um módulo que faz tudo isso, github.com/jtlapp/node-cleanup , originalmente baseado na solução cleanup.js abaixo, mas muito revisado com base nos comentários. Espero que seja útil.
quer

180

O script abaixo permite ter um único manipulador para todas as condições de saída. Ele usa uma função de retorno de chamada específica do aplicativo para executar o código de limpeza 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);
  });
};

Esse código intercepta exceções não capturadas, Ctrl+ Ce eventos de saída normais. Em seguida, ele chama uma única função opcional de retorno de chamada de limpeza do usuário antes de sair, manipulando todas as condições de saída com um único objeto.

O módulo simplesmente estende o objeto de processo em vez de definir outro emissor de evento. Sem um retorno de chamada específico do aplicativo, a limpeza é padronizada como uma função sem operação. Isso foi suficiente para meu uso em que os processos filhos foram deixados em execução ao sair por Ctrl+ C.

Você pode adicionar facilmente outros eventos de saída, como SIGHUP, conforme desejado. Nota: pelo manual do NodeJS, o SIGKILL não pode ter um ouvinte. O código de teste abaixo demonstra várias maneiras de usar o 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, para onde vai esse código específico?
hownowbrowncow

11
Eu achei esse código indispensável e criei um pacote de nós para ele, com modificações, creditando a você e a essa resposta do SO. Espero que esteja tudo bem, @CanyonCasa. Obrigado! npmjs.com/package/node-cleanup
Joe Lapp

3
Eu amo a limpeza. Mas não gosto do processo.exit (0); cons.org/cracauer/sigint.html Meu sentimento é que você deve deixar o kernel lidar com a destruição. Você não está saindo da mesma maneira que um SIGINT. O SIGINT não sai com 2. Você está confundindo o SIGINT com o código de erro. Eles não são os mesmos. Na verdade, Ctrl + C existe com 130. Não 2. tldp.org/LDP/abs/html/exitcodes.html
Banjocat 18/11/16

5
Tenho reescrito npmjs.com/package/node-cleanup de modo que a manipulação SIGINT funciona bem com outros processos, por @ ligação de Banjocat. Agora também retransmite corretamente os sinais para o processo pai em vez de chamar process.exit(). Os manipuladores de limpeza agora têm a flexibilidade de se comportar em função do código ou sinal de saída, e os manipuladores de limpeza podem ser desinstalados, conforme necessário para oferecer suporte à limpeza assíncrona ou impedir a limpeza cíclica. Agora ele tem pouca semelhança com o código acima.
quer

3
Esqueci de mencionar que também fiz um conjunto de testes (espero) abrangente.
quer

29

Isso captura todos os eventos de saída que posso encontrar que podem ser manipulados. Parece bastante confiável e limpo até agora.

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

Isso é incrível!
Andranik Hovesyan

Bom trabalho, estou usando-o agora em produção! Muitíssimo obrigado!
randy

20

"exit" é um evento que é acionado quando o nó conclui seu loop de eventos internamente, não é acionado quando você encerra o processo externamente.

O que você está procurando é executar algo em um SIGINT.

Os documentos em http://nodejs.org/api/process.html#process_signal_events dão um exemplo:

Exemplo de escuta para 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: isso parece interromper o sigint e você precisaria chamar process.exit () quando terminar o seu código.


1
Existe uma maneira de lidar com Ctrl + C e uma saída usual no mesmo lugar? Ou tenho que escrever dois manipuladores idênticos?
Erel Segal-Halevi

Apenas como uma observação, se você precisar finalizar o nó por um comando kill kill -2, passará o SIGINTcódigo. Temos que fazer isso dessa maneira, porque temos o registro de nó em um arquivo txt para que Ctrl + C não seja possível.
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
Essa resposta merece toda a glória, mas como não há explicação, infelizmente não há voto positivo. O significado dessa resposta é importante, o documento diz : "As funções do ouvinte devem executar apenas operações síncronas. O processo Node.js. será encerrado imediatamente após chamar os ouvintes de evento 'exit', fazendo com que qualquer trabalho adicional ainda enfileirado no loop de eventos seja abandonado. " , e esta resposta supera essa limitação!
xpt

7

Só queria mencionar o deathpacote aqui: https://github.com/jprichardson/node-death

Exemplo:

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

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

Parece que você deve sair explicitamente do programa com process.exit () no retorno de chamada. Isso me tropeçou.
Nick Manning


0

Aqui está um belo truque para janelas

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

0

Depois de brincar com outra resposta, aqui está a minha solução para esta tarefa. A implementação dessa maneira me ajuda a centralizar a limpeza em um só lugar, evitando o manuseio duplo da limpeza.

  1. Gostaria de encaminhar todos os outros códigos existentes para o código 'exit'.
const others = [`SIGINT`, `SIGUSR1`, `SIGUSR2`, `uncaughtException`, `SIGTERM`]
others.forEach((eventType) => {
    process.on(eventType, exitRouter.bind(null, { exit: true }));
})
  1. O que o exitRouter faz é chamar process.exit ()
function exitRouter(options, exitCode) {
   if (exitCode || exitCode === 0) console.log(`ExitCode ${exitCode}`);
   if (options.exit) process.exit();
}
  1. Em 'exit', lide com a limpeza com uma nova função
function exitHandler(exitCode) {
  console.log(`ExitCode ${exitCode}`);
  console.log('Exiting finally...')
}

process.on('exit', exitHandler)

Para fins de demonstração, este é um link para minha essência. No arquivo, adiciono um setTimeout para falsificar o processo em execução.

Se você executar node node-exit-demo.jse não fizer nada, depois de 2 segundos, verá o log:

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

Caso contrário, se antes do término do serviço, você terminar ctrl+C, verá:

^CExitCode SIGINT
ExitCode 0
Exiting finally...

O que aconteceu foi que o processo do Nó saiu inicialmente com o código SIGINT, em seguida, roteia para process.exit () e finalmente saiu com o código de saída 0.


-1

No caso em que o processo foi gerado por outro processo do nó, como:

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

E você tenta matá-lo mais tarde, via:

child.kill();

É assim que você lida com o evento [no filho]:

process.on('SIGTERM', function() {
    console.log('Goodbye!');
});
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.