Effectuer une action de nettoyage juste avant la fermeture de Node.js


327

Je veux dire à Node.js de toujours faire quelque chose juste avant sa sortie, pour une raison quelconque - Ctrl+ C, une exception ou toute autre raison.

J'ai essayé ceci:

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

J'ai commencé le processus, je l'ai tué et rien ne s'est produit. Je l'ai recommencé, j'ai appuyé sur Ctrl+ C, et il ne s'est toujours rien passé ...


Réponses:


511

METTRE À JOUR:

Vous pouvez enregistrer un gestionnaire pour process.on('exit')et dans tout autre cas ( SIGINTou exception non gérée) appelerprocess.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-t-il un moyen de gérer à la fois Ctrl + C et une sortie habituelle au même endroit, ou dois-je écrire deux gestionnaires distincts? Qu'en est-il des autres types de sortie, comme l'exception non gérée - il existe un gestionnaire spécifique pour ce cas, mais dois-je gérer cela avec une troisième copie du même gestionnaire?
Erel Segal-Halevi

1
@RobFox resume () initialise le processus de lecture. Stdin est suspendu par défaut. Vous pouvez en savoir plus sur: github.com/joyent/node/blob/…
Emil Condrea

65
Notez que vous must onlyeffectuez des synchronousopérations dans le exitgestionnaire
Lewis

2
@KesemDavid Je pense que vous devriez utiliser l' beforeExitévénement à la place.
Lewis

22
Cette solution présente de nombreux problèmes. (1) Il ne signale pas de signaux aux processus parents. (2) Il ne transmet pas le code de sortie au processus parent. (3) Il ne permet pas aux enfants de type Emacs qui ignorent Ctrl-C SIGINT. (4) Il ne permet pas le nettoyage asynchrone. (5) Il ne coordonne pas un seul stderrmessage entre plusieurs gestionnaires de nettoyage. J'ai écrit un module qui fait tout cela, github.com/jtlapp/node-cleanup , basé à l'origine sur la solution cleanup.js ci-dessous, mais considérablement révisé en fonction des commentaires. J'espère que cela s'avère utile.
Joe Lapp

180

Le script ci-dessous permet d'avoir un seul gestionnaire pour toutes les conditions de sortie. Il utilise une fonction de rappel spécifique à l'application pour effectuer un code de nettoyage personnalisé.

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

Ce code intercepte les exceptions non capturées, Ctrl+ Cet les événements de sortie normaux. Il appelle ensuite une seule fonction de rappel de nettoyage utilisateur facultatif avant de quitter, gérant toutes les conditions de sortie avec un seul objet.

Le module étend simplement l'objet de processus au lieu de définir un autre émetteur d'événement. Sans rappel spécifique à l'application, le nettoyage par défaut est une fonction no op. Cela était suffisant pour mon utilisation où les processus enfants étaient laissés en cours d'exécution lors de la fermeture par Ctrl+ C.

Vous pouvez facilement ajouter d'autres événements de sortie tels que SIGHUP comme vous le souhaitez. Remarque: selon le manuel NodeJS, SIGKILL ne peut pas avoir d'écouteur. Le code de test ci-dessous illustre différentes façons d'utiliser 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 où va ce code spécifique?
hownowbrowncow

11
J'ai trouvé ce code indispensable et créé un package de noeud pour lui, avec des modifications, en vous créditant et cette réponse SO. J'espère que ça va, @CanyonCasa. Je vous remercie! npmjs.com/package/node-cleanup
Joe Lapp

3
J'adore le nettoyage. Mais je n'aime pas le process.exit (0); cons.org/cracauer/sigint.html Je pense que vous devriez laisser le noyau gérer la destruction. Vous ne sortez pas de la même manière qu'un SIGINT. SIGINT ne se termine pas avec 2. Vous confondez le SIGINT avec un code d'erreur. Ce ne sont pas les mêmes. En fait, Ctrl + C existe avec 130. Pas 2. tldp.org/LDP/abs/html/exitcodes.html
Banjocat

5
J'ai réécrit npmjs.com/package/node-cleanup pour que la gestion SIGINT fonctionne bien avec d'autres processus, par le lien de @ Banjocat. Il relaie également correctement les signaux au processus parent au lieu d'appeler process.exit(). Les gestionnaires de nettoyage peuvent désormais se comporter en fonction du code ou du signal de sortie, et les gestionnaires de nettoyage peuvent être désinstallés, si nécessaire pour prendre en charge le nettoyage asynchrone ou empêcher le nettoyage cyclique. Il a maintenant peu de ressemblance avec le code ci-dessus.
Joe Lapp

3
J'ai oublié de mentionner que j'ai également créé une suite de tests complète (espérons-le).
Joe Lapp

30

Cela intercepte tous les événements de sortie que je peux trouver qui peuvent être gérés. Semble assez fiable et propre jusqu'à présent.

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

C'est génial!
Andranik Hovesyan

Excellent travail, je l'utilise en production en ce moment! Merci beaucoup!
randy

20

"exit" est un événement qui se déclenche lorsque le nœud termine sa boucle d'événements en interne, il n'est pas déclenché lorsque vous terminez le processus en externe.

Ce que vous cherchez, c'est exécuter quelque chose sur un SIGINT.

Les documents sur http://nodejs.org/api/process.html#process_signal_events donnent un exemple:

Exemple d'écoute de 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.');
});

Remarque: cela semble interrompre la signature et vous devrez appeler process.exit () lorsque vous aurez terminé avec votre code.


1
Existe-t-il un moyen de gérer à la fois Ctrl + C et une sortie habituelle au même endroit? Ou dois-je écrire deux gestionnaires identiques?
Erel Segal-Halevi

Tout comme une note, si vous devez terminer le nœud par une commande kill kill -2, le SIGINTcode passera . Nous devons le faire de cette façon car nous avons la journalisation des nœuds dans un fichier txt, donc Ctrl + C n'est pas possible.
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
Cette réponse mérite toute la gloire, mais comme il n'y a pas d'explication, il n'y a pas non plus de vote positif malheureusement. Ce qui est significatif à propos de cette réponse est, dit le doc , "Les fonctions d'écoute doivent uniquement effectuer des opérations synchrones. Le processus Node.js se terminera immédiatement après avoir appelé les écouteurs d'événement" exit ", ce qui entraînera l'abandon de tout travail supplémentaire encore en file d'attente dans la boucle d'événements. " , et cette réponse surmonte cette limitation!
xpt

7

Je voulais juste mentionner death package ici: https://github.com/jprichardson/node-death

Exemple:

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

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

Il semble que vous devez quitter explicitement le programme avec process.exit () dans le rappel. Cela m'a fait trébucher.
Nick Manning


0

Voici un bon hack pour Windows

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

0

Après avoir joué avec une autre réponse, voici ma solution pour cette tâche. L'implémentation de cette manière m'aide à centraliser le nettoyage en un seul endroit, évitant une double manipulation du nettoyage.

  1. Je voudrais acheminer tous les autres codes sortants vers le code «exit».
const others = [`SIGINT`, `SIGUSR1`, `SIGUSR2`, `uncaughtException`, `SIGTERM`]
others.forEach((eventType) => {
    process.on(eventType, exitRouter.bind(null, { exit: true }));
})
  1. Ce que fait exitRouter, c'est appeler process.exit ()
function exitRouter(options, exitCode) {
   if (exitCode || exitCode === 0) console.log(`ExitCode ${exitCode}`);
   if (options.exit) process.exit();
}
  1. À la «sortie», gérez le nettoyage avec une nouvelle fonction
function exitHandler(exitCode) {
  console.log(`ExitCode ${exitCode}`);
  console.log('Exiting finally...')
}

process.on('exit', exitHandler)

Aux fins de démonstration, ceci est un lien vers mon essentiel. Dans le fichier, j'ajoute un setTimeout pour simuler le processus en cours.

Si vous exécutez node node-exit-demo.jset ne faites rien, après 2 secondes, vous voyez le journal:

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

Sinon, si avant la fin du service, vous terminez par ctrl+C, vous verrez:

^CExitCode SIGINT
ExitCode 0
Exiting finally...

Ce qui s'est passé, c'est que le processus Node est sorti initialement avec le code SIGINT, puis il est dirigé vers process.exit () et finalement quitté avec le code de sortie 0.


-1

Dans le cas où le processus a été généré par un autre processus de noeud, comme:

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

Et vous essayez de le tuer plus tard, via:

child.kill();

Voici comment vous gérez l'événement [sur l'enfant]:

process.on('SIGTERM', function() {
    console.log('Goodbye!');
});
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.