Робіть очищення безпосередньо перед виходом Node.js


326

Я хочу сказати 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 та нормальні події виходу. Потім він викликає одну необов'язкову функцію зворотного виклику користувача очищення перед виходом, обробляючи всі умови виходу одним об'єктом.

Модуль просто розширює об'єкт процесу замість визначення іншого випромінювача подій. Без конкретного додатка зворотний виклик очищення за замовчуванням функціонує без опції. Цього було достатньо для мого використання, коли дочірні процеси залишалися запущеними при виході через 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
Я вважав цей код незамінним і створив для нього пакет вузлів із модифікаціями, зарахувавши вас та цю відповідь ТАК. Сподіваюся, що це нормально, @CanyonCasa. Дякую! npmjs.com/package/node-cleanup
Джо Лапп

3
Я люблю прибирання. Але мені не подобається process.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
Забув згадати, що я також склав (сподіваюсь) комплексний набір тестів.
Джо Лапп

29

Це фіксує кожну подію виходу, яку я можу знайти, з якою можна впоратися. На сьогоднішній день здається досить надійним і чистим.

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


Чудова робота, я зараз її використовую у виробництві! Дякую купу!
randy

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

Примітка: це, здається, перериває підпис і вам потрібно буде викликати process.exit (), коли закінчите свій код.


1
Чи є спосіб обробляти і Ctrl + C, і звичайний вихід у тому самому місці? Або мені потрібно написати два однакові обробники?
Ерел Сегал-Халеві

Як і примітка, якщо вам доведеться закінчити вузол командою kill kill -2, передасть SIGINTкод. Ми повинні зробити це таким чином, оскільки у нас вхід у логічний файл у файл txt, тому 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.