Node.js çıkmadan hemen önce bir temizleme eylemi gerçekleştirme


326

Node.js'ye her zaman çıkmadan hemen önce bir şey yapmasını söylemek istiyorum, herhangi bir nedenle - Ctrl+ C, bir istisna veya başka bir sebep.

Bunu denedim:

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

Süreci başlattım, öldürdüm ve hiçbir şey olmadı. Tekrar başladım, Ctrl+ tuşlarına bastım Cve hala hiçbir şey olmadı ...


Yanıtlar:


511

GÜNCELLEME:

Aramak için process.on('exit')başka bir durumda ( SIGINTveya işlenmeyen istisna) bir işleyici kaydedebilirsinizprocess.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
Aynı yerde hem Ctrl + C hem de normal bir çıkış işlemek için bir yol var mı, yoksa iki ayrı işleyici yazmak zorunda mıyım? İşlenmeyen istisna gibi diğer çıkış türleri ne olacak - bu durumda belirli bir işleyici var, ancak bunu aynı işleyicinin üçüncü bir kopyasıyla ele almalıyım?
Erel Segal-Halevi

1
@RobFox resume () okuma işlemini başlatır. Stdin varsayılan olarak duraklatılmıştır. Daha fazla bilgi için: github.com/joyent/node/blob/…
Emil Condrea

65
İşleyicide işlem must onlygerçekleştirdiğinizi unutmayınsynchronousexit
Lewis

2
@KesemDavid Bunun beforeExityerine etkinliği kullanmanız gerektiğini düşünüyorum .
Lewis

22
Bu çözümün birçok sorunu var. (1) Ana süreçlere sinyalleri bildirmez. (2) Çıkış kodunu üst sürece aktarmaz. (3) Ctrl-C SIGINT'i görmezden gelen Emacs benzeri çocuklara izin vermez. (4) Zaman uyumsuz temizlemeye izin vermez. (5) stderrBirden çok temizleme işleyicisinde tek bir iletiyi koordine etmez . Tüm bunları yapan bir modül yazdım, github.com/jtlapp/node-cleanup , başlangıçta aşağıdaki cleanup.js çözümüne dayanıyordu, ancak geri bildirimlere dayanarak büyük ölçüde revize edildi. Umarım faydalı olur.
Joe Lapp

180

Aşağıdaki komut dosyası, tüm çıkış koşulları için tek bir işleyiciye izin verir. Özel temizleme kodu gerçekleştirmek için uygulamaya özel geri arama işlevi kullanır.

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

Bu kod yakalanmamış özel durumları, Ctrl+ Cve normal çıkış olaylarını engeller . Daha sonra, çıkmadan önce tek bir isteğe bağlı kullanıcı temizleme geri çağırma işlevi çağırır ve tüm çıkış koşullarını tek bir nesneyle işler.

Modül, başka bir olay yayıcı tanımlamak yerine işlem nesnesini genişletir. Uygulamaya özel bir geri arama olmadan, temizleme varsayılan olarak op olmayan bir işlevdir. Bu, Ctrl+ ile çıkarken alt süreçlerin çalışmaya devam ettiği yerlerde kullanımım için yeterliydi C.

SIGHUP gibi diğer çıkış etkinliklerini istediğiniz gibi kolayca ekleyebilirsiniz. Not: NodeJS kılavuzuna göre, SIGKILL'in bir dinleyicisi olamaz. Aşağıdaki test kodu cleanup.js dosyasını kullanmanın çeşitli yollarını gösterir

// 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 bu özel kod nereye gidiyor?
hownowbrowncow

11
Bu kodu vazgeçilmez buldum ve bunun için bir düğüm paketi oluşturdum, değişiklikler, sizi ve bu SO cevabını kredilendirdim. Umarım bu iyi, @CanyonCasa. Teşekkür ederim! npmjs.com/package/node-cleanup
Joe Lapp

3
Temizliği seviyorum. Ama process.exit (0) 'dan hoşlanmıyorum; cons.org/cracauer/sigint.html Benim hissim , çekirdeğin yıkımı yapmasına izin vermeniz. Bir SIGINT ile aynı şekilde çıkmıyorsunuz. SIGINT 2 ile çıkmıyor. SIGINT hata koduyla yanlış anlaşıyorsunuz. Aynı değiller. Aslında Ctrl + C 130. Değil 2. ile mevcut tldp.org/LDP/abs/html/exitcodes.html
Banjocat

5
Ben tekrar yazmış npmjs.com/package/node-cleanup o SIGINT elleçleme Banjocat en linke @ başına, diğer süreçlerle iyi çalışır böylece. Ayrıca şimdi çağrı yapmak yerine sinyalleri ana prosese doğru bir şekilde iletir process.exit(). Temizleme işleyicileri artık çıkış kodu veya sinyalin bir işlevi gibi davranma esnekliğine sahiptir ve temizleme işleyicileri, zaman uyumsuz temizlemeyi desteklemek veya döngüsel temizlemeyi önlemek için gerektiğinde kaldırılabilir. Şimdi yukarıdaki koda çok az benziyor.
Joe Lapp

3
Ben de (umarım) kapsamlı bir test paketi yaptım bahsetmeyi unuttum.
Joe Lapp

29

Bu, bulabildiğim her çıkış etkinliğini yakalar. Şimdiye kadar oldukça güvenilir ve temiz görünüyor.

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

Bu harika!
Andranik Hovesyan

Harika iş, şu anda üretimde kullanıyorum! Çok teşekkürler!
randy

20

"exit", düğüm bittiğinde tetiklenen bir olaydır ve dahili olarak olay döngüsüdür, işlemi harici olarak sonlandırdığınızda tetiklenmez.

Aradığınız şey SIGINT üzerinde bir şey yürütmektir.

Http://nodejs.org/api/process.html#process_signal_events adresindeki dokümanlar bir örnek verir:

SIGINT dinleme örneği:

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

Not: bu sigint'i kesiyor gibi görünüyor ve kodunuzu bitirdiğinizde process.exit () öğesini çağırmanız gerekir.


1
Aynı yerde hem Ctrl + C'yi hem de normal bir çıkışı ele almanın bir yolu var mı? Yoksa iki özdeş işleyici yazmak zorunda mıyım?
Erel Segal-Halevi

Not olarak, düğümü bir kill komutu ile bitirmeniz gerekiyorsa kodu kill -2geçirirsiniz SIGINT. Bu şekilde yapmalıyız çünkü bir txt dosyasına düğüm kaydı yapıyoruz, böylece Ctrl + C mümkün değil.
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
Bu cevap tüm zaferi hak ediyor, ancak açıklama olmadığı için maalesef yukarı oy yok. Ne anlamlı bu cevabı hakkında, doc diyor , "Dinleyici fonksiyonları senkronize işlemleri yalnızca gerçekleştirmelisiniz. Node.js süreci hala olay döngü içinde sıraya herhangi bir ek iş terk edilmesi neden 'çıkış' olay dinleyicileri çağırarak hemen sonra çıkılacak. " ve bu cevap bu sınırlamanın üstesinden gelir!
xpt



0

İşte pencereler için güzel bir kesmek

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

0

Başka bir cevapla oynadıktan sonra, bu görev için benim çözümüm. Bu şekilde uygulamak temizliği tek bir yerde merkezileştirmeme yardımcı olur ve temizliğin iki kez ele alınmasını önler.

  1. Diğer tüm çıkış kodlarını 'çıkış' koduna yönlendirmek istiyorum.
const others = [`SIGINT`, `SIGUSR1`, `SIGUSR2`, `uncaughtException`, `SIGTERM`]
others.forEach((eventType) => {
    process.on(eventType, exitRouter.bind(null, { exit: true }));
})
  1. ExitRouter'ın yaptığı işlem process.exit ()
function exitRouter(options, exitCode) {
   if (exitCode || exitCode === 0) console.log(`ExitCode ${exitCode}`);
   if (options.exit) process.exit();
}
  1. 'Çıkışta' temizliği yeni bir işlevle halledin
function exitHandler(exitCode) {
  console.log(`ExitCode ${exitCode}`);
  console.log('Exiting finally...')
}

process.on('exit', exitHandler)

Demo amaçlı olarak, bu benim özüm ile bağlantı . Dosyada, çalışan işlemi sahte bir setTimeout ekleyin.

Koşup node node-exit-demo.jshiçbir şey yapmazsanız, 2 saniye sonra günlüğü görürsünüz:

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

Hizmet bitmeden önce sonlandırırsanız ctrl+C, şunu görürsünüz:

^CExitCode SIGINT
ExitCode 0
Exiting finally...

Ne oldu Düğüm işlemi ilk olarak SIGINT koduyla çıktıktan sonra process.exit () öğesine yönlendirilir ve son olarak çıkış kodu 0 ile çıkar.


-1

Sürecin başka bir düğüm işlemi tarafından ortaya çıkması durumunda, örneğin:

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

Ve daha sonra şu şekilde öldürmeye çalışıyorsunuz:

child.kill();

Etkinliği [çocukta] şu şekilde halledersiniz:

process.on('SIGTERM', function() {
    console.log('Goodbye!');
});
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.