Выполнение действия очистки перед выходом Node.js


327

Я хочу сказать Node.js всегда делать что-то перед самым выходом, по любой причине - Ctrl+ C, исключение или любая другая причина.

Я попробовал это:

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

Я начал процесс, убил его, и ничего не произошло. Я начал это снова, нажал Ctrl+ C, и все еще ничего не случилось ...


Ответы:


511

ОБНОВИТЬ:

Вы можете зарегистрировать обработчик для process.on('exit')и в любом другом случае ( SIGINTили необработанное исключение) для вызова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
Есть ли способ обработать и Ctrl + C и обычный выход в одном месте, или мне нужно написать два отдельных обработчика? А как насчет других типов выхода, таких как необработанное исключение - для этого случая есть специальный обработчик, но я должен обработать это с третьей копией того же обработчика?
Эрель Сегал-Халеви

1
@RobFox resume () инициализирует процесс чтения. Stdin приостановлен по умолчанию. Вы можете прочитать больше на: github.com/joyent/node/blob/…
Эмиль Кондреа

65
Обратите внимание, что вы must onlyвыполняете synchronousоперации в exitобработчике
Льюис

2
@KesemDavid Я думаю, что вы должны использовать beforeExitсобытие вместо этого.
Льюис

22
Это решение имеет множество проблем. (1) Он не сообщает сигналы родительским процессам. (2) Он не передает код завершения родительскому процессу. (3) Он не позволяет Emacs-подобным дочерним элементам игнорировать Ctrl-C SIGINT. (4) Это не позволяет асинхронную очистку. (5) Он не координирует одно stderrсообщение между несколькими обработчиками очистки. Я написал модуль, который делает все это, github.com/jtlapp/node-cleanup , первоначально основанный на решении cleanup.js ниже, но сильно пересмотренный на основе отзывов. Я надеюсь, что это окажется полезным.
Джо Лапп

180

Сценарий ниже позволяет иметь один обработчик для всех условий выхода. Для выполнения пользовательского кода очистки используется функция обратного вызова, специфичная для приложения.

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

Этот код перехватывает неперехваченные исключения Ctrl+ Cи обычные события выхода. Затем он вызывает одну необязательную функцию обратного вызова очистки пользователя перед выходом, обрабатывая все условия выхода одним объектом.

Модуль просто расширяет объект процесса вместо определения другого источника событий. Без обратного вызова, специфичного для приложения, для очистки по умолчанию используется функция no op. Этого было достаточно для моего использования, когда дочерние процессы оставались запущенными при выходе из Ctrl+ C.

При желании вы можете легко добавить другие события выхода, такие как SIGHUP. Примечание: согласно руководству NodeJS, SIGKILL не может иметь слушателя. Тестовый код ниже демонстрирует различные способы использования 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, где этот конкретный код идет?
Hownowbrowncow

11
Я нашел этот код незаменимым и создал для него пакет узлов, с изменениями, благодарностью вам и этим SO ответом. Надеюсь, что все в порядке, @CanyonCasa. Спасибо! npmjs.com/package/node-cleanup
Джо Лапп,

3
Я люблю уборку. Но мне не нравится процесс. Exit (0); cons.org/cracauer/sigint.html Мне кажется, вы должны позволить ядру справиться с уничтожением. Вы не выходите так же, как SIGINT. SIGINT не завершается с 2. Вы ошибаетесь SIGINT с кодом ошибки. Они не одинаковы. На самом деле Ctrl + C существует с 130. Не 2. tldp.org/LDP/abs/html/exitcodes.html
Banjocat

5
Я переписал npmjs.com/package/node-cleanup, чтобы обработка SIGINT работала хорошо с другими процессами, по ссылке @ Banjocat. Теперь он также корректно передает сигналы родительскому процессу вместо вызова process.exit(). Обработчики очистки теперь могут работать как функция кода выхода или сигнала, а обработчики очистки могут быть удалены, если это необходимо для поддержки асинхронной очистки или предотвращения циклической очистки. Теперь он мало похож на приведенный выше код.
Джо Лапп

3
Забыл упомянуть, что я также сделал (надеюсь) комплексный набор тестов.
Джо Лапп

30

Это ловит каждое событие выхода, которое я могу найти, которое может быть обработано. Кажется довольно надежным и чистым до сих пор.

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

Это круто!
Андраник Овесян

Отличная работа, я использую ее прямо сейчас! Огромное спасибо!
Рэнди

20

«exit» - это событие, которое запускается, когда узел завершает свой цикл обработки событий внутри, оно не запускается, когда вы завершаете процесс извне.

То, что вы ищете, это выполнение чего-то на SIGINT.

Документы на http://nodejs.org/api/process.html#process_signal_events приводят пример:

Пример прослушивания для 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.');
});

Примечание: кажется, что это прерывает sigint, и вам нужно будет вызвать process.exit (), когда вы закончите с вашим кодом.


1
Есть ли способ обработать и Ctrl + C и обычный выход в одном месте? Или мне нужно написать два одинаковых обработчика?
Эрель Сегал-Халеви

Как примечание, если вам нужно завершить узел командой kill, команда kill -2передаст SIGINTкод. Мы должны сделать это так, потому что у нас есть регистрация узлов в текстовый файл, поэтому Ctrl + C невозможен.
Ост

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
Этот ответ заслуживает всей славы, но, поскольку нет никаких объяснений, нет, к сожалению, и голосования. Что существенным об этом ответ, док говорит , «функция Слушатель должна выполнять только синхронные операции. Процесс Node.js будет выходить сразу после вызова слушателей„выхода“событий вызывают какие - либо дополнительные работы еще в очереди в цикле обработки событий , чтобы отказаться. " , и этот ответ преодолевает это ограничение!
XPT

7

Просто хотел упомянуть deathпакет здесь: https://github.com/jprichardson/node-death

Пример:

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

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

Кажется, вы должны явно выйти из программы с помощью process.exit () в обратном вызове. Это сбило меня с толку.
Ник Мэннинг



0

Поиграв с другим ответом, вот мое решение для этой задачи. Реализация этого способа помогает мне централизовать очистку в одном месте, предотвращая двойную обработку очистки.

  1. Я хотел бы направить все остальные коды выхода на код выхода.
const others = [`SIGINT`, `SIGUSR1`, `SIGUSR2`, `uncaughtException`, `SIGTERM`]
others.forEach((eventType) => {
    process.on(eventType, exitRouter.bind(null, { exit: true }));
})
  1. Что делает exitRouter, так это вызывает process.exit ()
function exitRouter(options, exitCode) {
   if (exitCode || exitCode === 0) console.log(`ExitCode ${exitCode}`);
   if (options.exit) process.exit();
}
  1. На «выходе» обработайте очистку с помощью новой функции
function exitHandler(exitCode) {
  console.log(`ExitCode ${exitCode}`);
  console.log('Exiting finally...')
}

process.on('exit', exitHandler)

Для демонстрации, это ссылка на мою суть. В этом файле я добавляю setTimeout для фальсификации запуска процесса.

Если вы запускаете node node-exit-demo.jsи ничего не делаете, то через 2 секунды вы видите журнал:

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

Иначе, если до окончания обслуживания вы прекратите ctrl+C, вы увидите:

^CExitCode SIGINT
ExitCode 0
Exiting finally...

Произошло то, что процесс Node изначально был закрыт с кодом SIGINT, затем он перенаправился в process.exit () и, наконец, вышел с кодом выхода 0.


-1

В случае, когда процесс был порожден другим процессом узла, как:

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

И вы пытаетесь убить его позже, через:

child.kill();

Вот как вы обрабатываете событие [на дочернем элементе]:

process.on('SIGTERM', function() {
    console.log('Goodbye!');
});
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.