Wykonanie akcji czyszczenia tuż przed zamknięciem Node.js


326

Chcę powiedzieć Node.js, aby zawsze robił coś tuż przed jego wyjściem, z jakiegokolwiek powodu - Ctrl+ C, wyjątku lub jakiegokolwiek innego powodu.

Próbowałem tego:

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

Rozpocząłem proces, zabiłem go i nic się nie stało. Uruchomiłem go ponownie, nacisnąłem Ctrl+ Ci nadal nic się nie wydarzyło ...


Odpowiedzi:


511

AKTUALIZACJA:

Możesz zarejestrować moduł obsługi process.on('exit')w dowolnym innym przypadku ( SIGINTlub nieobsługiwany wyjątek), aby zadzwonićprocess.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
Czy istnieje sposób obsługi Ctrl + C i zwykłego wyjścia w tym samym miejscu, czy też muszę napisać dwa osobne moduły obsługi? Co z innymi typami wyjścia, takimi jak nieobsługiwany wyjątek - dla tego przypadku istnieje konkretny moduł obsługi, ale czy powinienem to obsługiwać za pomocą trzeciej kopii tego samego modułu obsługi?
Erel Segal-Halevi

1
@RobFox resume () inicjuje proces odczytu. Stdin jest domyślnie wstrzymany. Możesz przeczytać więcej na: github.com/joyent/node/blob/…
Emil Condrea

65
Pamiętaj, że must onlywykonujesz synchronousoperacje w exitmodule obsługi
Lewis

2
@KesemDavid Myślę, że powinieneś beforeExitzamiast tego użyć wydarzenia.
Lewis,

22
To rozwiązanie ma wiele problemów. (1) Nie zgłasza sygnałów procesom macierzystym. (2) Nie przekazuje kodu wyjścia do procesu nadrzędnego. (3) Nie pozwala na to, aby dzieci podobne do Emacsa ignorowały Ctrl-C SIGINT. (4) Nie pozwala na asynchroniczne czyszczenie. (5) Nie koordynuje pojedynczej stderrwiadomości w wielu modułach czyszczących. Napisałem moduł, który to wszystko robi, github.com/jtlapp/node-cleanup , pierwotnie oparty na poniższym rozwiązaniu cleanup.js , ale znacznie poprawiony na podstawie opinii. Mam nadzieję, że okaże się pomocny.
Joe Lapp,

180

Poniższy skrypt pozwala mieć jeden moduł obsługi dla wszystkich warunków wyjścia. Używa funkcji wywołania zwrotnego specyficznej dla aplikacji, aby wykonać niestandardowy kod czyszczenia.

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

Ten kod przechwytuje nieprzechwycone wyjątki, Ctrl+ Ci normalne zdarzenia wyjściowe. Następnie wywołuje jedną opcjonalną funkcję wywołania zwrotnego czyszczenia użytkownika przed wyjściem, obsługując wszystkie warunki wyjścia za pomocą jednego obiektu.

Moduł po prostu rozszerza obiekt procesu zamiast definiować inny emiter zdarzeń. Bez wywołania zwrotnego specyficznego dla aplikacji czyszczenie domyślnie przyjmuje funkcję brak operacji. Było to wystarczające do mojego użytku, gdy procesy potomne pozostawały uruchomione podczas wychodzenia przez Ctrl+ C.

W razie potrzeby możesz łatwo dodać inne zdarzenia wyjściowe, takie jak SIGHUP. Uwaga: zgodnie z instrukcją NodeJS, SIGKILL nie może mieć detektora. Poniższy kod testowy pokazuje różne sposoby korzystania z 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, dokąd idzie ten konkretny kod?
hownowbrowncow

11
Uznałem ten kod za niezbędny i stworzyłem dla niego pakiet węzłów, z modyfikacjami, uznając ciebie i tę odpowiedź SO. Mam nadzieję, że w porządku, @CanyonCasa. Dziękuję Ci! npmjs.com/package/node-cleanup
Joe Lapp

3
Uwielbiam porządki. Ale nie podoba mi się proces.exit (0); cons.org/cracauer/sigint.html Mam wrażenie, że powinieneś pozwolić jądrze zająć się zniszczeniem. Nie wychodzisz w taki sam sposób jak SIGINT. SIGINT nie kończy się na 2. Mylnie SIGINT z kodem błędu. Nie są takie same. Właściwie Ctrl + C istnieje z 130. Nie 2. tldp.org/LDP/abs/html/exitcodes.html
Banjocat

5
Napisałem npmjs.com/package/node-cleanup, aby obsługa SIGINT działała dobrze z innymi procesami za pośrednictwem linku @ Banjocat. Teraz również poprawnie przekazuje sygnały do ​​procesu nadrzędnego zamiast wywoływać process.exit(). Programy obsługi czyszczenia mają teraz elastyczność działania w funkcji kodu wyjścia lub sygnału, a programy obsługi czyszczenia można odinstalować, jeśli jest to konieczne w celu obsługi czyszczenia asynchronicznego lub zapobiegania cyklicznemu czyszczeniu. Teraz ma niewielkie podobieństwo do powyższego kodu.
Joe Lapp,

3
Zapomniałem wspomnieć, że stworzyłem (mam nadzieję) kompleksowy zestaw testów.
Joe Lapp,

29

To łapie każde zdarzenie wyjściowe, które mogę znaleźć, które można obsłużyć. Jak dotąd wydaje się dość niezawodny i czysty.

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

To jest niesamowite!
Andranik Hovesyan

Świetna robota, teraz używam jej w produkcji! Wielkie dzięki!
randy

20

„exit” jest zdarzeniem, które jest wyzwalane, gdy węzeł kończy wewnętrzną pętlę zdarzeń, nie jest wyzwalane, gdy zakończysz proces zewnętrznie.

To, czego szukasz, to wykonanie czegoś na SIGINT.

Dokumenty na stronie http://nodejs.org/api/process.html#process_signal_events podają przykład:

Przykład odsłuchu dla 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.');
});

Uwaga: wydaje się, że to przerywa sigint i będziesz musiał wywołać process.exit () po zakończeniu pracy z kodem.


1
Czy istnieje sposób na obsługę zarówno Ctrl + C, jak i zwykłego wyjścia w tym samym miejscu? Czy też muszę napisać dwa identyczne programy obsługi?
Erel Segal-Halevi

Podobnie jak uwaga, jeśli musisz zakończyć węzeł za pomocą polecenia kill kill -2, przekazanie SIGINTkodu. Musimy to zrobić w ten sposób, ponieważ logujemy się do pliku txt, więc Ctrl + C nie jest możliwe.
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
Ta odpowiedź zasługuje na całą chwałę, ale ponieważ nie ma wyjaśnienia, nie ma też niestety głosowania w górę. Co istotne w tej odpowiedzi, dokument mówi : „Funkcje nasłuchiwania muszą wykonywać tylko operacje synchroniczne. Proces Node.js zakończy działanie natychmiast po wywołaniu detektorów zdarzeń„ exit ”, powodując porzucenie dodatkowej pracy w kolejce w pętli zdarzeń. ” , a ta odpowiedź pokonuje to ograniczenie!
xpt

7

Chciałem tylko wspomnieć o deathpakiecie tutaj: https://github.com/jprichardson/node-death

Przykład:

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

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

Wydaje się, że musisz jawnie wyjść z programu za pomocą process.exit () w ramach wywołania zwrotnego. To mnie potknęło.
Nick Manning


0

Oto fajny hack dla systemu Windows

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

0

Po przeanalizowaniu innej odpowiedzi, oto moje rozwiązanie tego zadania. Implementacja w ten sposób pomaga mi scentralizować czyszczenie w jednym miejscu, zapobiegając podwójnemu wykonywaniu czyszczenia.

  1. Chciałbym przekierować wszystkie pozostałe kody wyjścia do kodu „wyjścia”.
const others = [`SIGINT`, `SIGUSR1`, `SIGUSR2`, `uncaughtException`, `SIGTERM`]
others.forEach((eventType) => {
    process.on(eventType, exitRouter.bind(null, { exit: true }));
})
  1. To, co robi exitRouter, wywołuje proces.exit ()
function exitRouter(options, exitCode) {
   if (exitCode || exitCode === 0) console.log(`ExitCode ${exitCode}`);
   if (options.exit) process.exit();
}
  1. Przy „wyjściu” zajmuj się czyszczeniem za pomocą nowej funkcji
function exitHandler(exitCode) {
  console.log(`ExitCode ${exitCode}`);
  console.log('Exiting finally...')
}

process.on('exit', exitHandler)

Dla celów demonstracyjnych jest to link do mojej treści. W pliku dodaję setTimeout, aby sfałszować uruchomiony proces.

Jeśli biegniesz node node-exit-demo.jsi nic nie robisz, po 2 sekundach zobaczysz dziennik:

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

W przeciwnym razie, jeśli przed zakończeniem usługi zakończysz, ctrl+Czobaczysz:

^CExitCode SIGINT
ExitCode 0
Exiting finally...

To, co się wydarzyło, to proces węzła początkowo zakończony kodem SIGINT, następnie kieruje się do process.exit (), a na koniec kończy się kodem wyjścia 0.


-1

W przypadku, gdy proces został spawnowany przez inny proces węzłowy, taki jak:

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

I później próbujesz go zabić, poprzez:

child.kill();

Oto jak poradzisz sobie ze zdarzeniem [na dziecku]:

process.on('SIGTERM', function() {
    console.log('Goodbye!');
});
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.