ทำการดำเนินการล้างข้อมูลก่อน Node.js ออก


326

ฉันต้องการบอก Node.js ให้ทำอะไรบางอย่างก่อนที่มันจะออกมาเสมอไม่ว่าจะด้วยเหตุผลใดก็ตาม - Ctrl+ C, ข้อยกเว้นหรือเหตุผลอื่น ๆ

ฉันลองสิ่งนี้:

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

ฉันเริ่มต้นกระบวนการฆ่ามันและไม่มีอะไรเกิดขึ้น ฉันเริ่มต้นใหม่อีกครั้งกดCtrl+ Cและยังไม่มีอะไรเกิดขึ้น ...


คำตอบ:


511

UPDATE:

คุณสามารถลงทะเบียนตัวจัดการ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 และทางออกปกติในที่เดียวกันหรือฉันต้องเขียนตัวจัดการสองตัวแยกกันหรือไม่? แล้วทางออกประเภทอื่นเช่นข้อยกเว้นที่ไม่ได้จัดการ - มีตัวจัดการเฉพาะสำหรับกรณีนั้น แต่ฉันควรจัดการสิ่งนี้ด้วยสำเนาที่สามของตัวจัดการเดียวกันหรือไม่
Erel Segal-Halevi

1
@RobFox ดำเนินการต่อ () เริ่มต้นกระบวนการอ่าน Stdin ถูกหยุดชั่วคราวตามค่าเริ่มต้น คุณสามารถอ่านเพิ่มเติมได้ที่: github.com/joyent/node/blob/…
Emil Condrea

65
โปรดทราบว่าคุณmust onlyดำเนินsynchronousการในexitตัวจัดการ
Lewis

2
@KesemDavid ฉันคิดว่าคุณควรใช้beforeExitกิจกรรมแทน
ลูอิส

22
วิธีนี้มีปัญหามากมาย (1) ไม่รายงานสัญญาณไปยังกระบวนการหลัก (2) ไม่ส่งรหัสทางออกไปยังกระบวนการหลัก (3) ไม่อนุญาตสำหรับเด็กที่มีลักษณะเหมือน Emacs ที่เพิกเฉยกับ Ctrl-C SIGINT (4) ไม่อนุญาตการล้างข้อมูลแบบอะซิงโครนัส (5) มันไม่ได้ประสานงานstderrข้อความเดียวกับตัวจัดการการล้างข้อมูลหลายรายการ ฉันได้เขียนโมดูลที่ทำสิ่งทั้งหมดนี้github.com/jtlapp/node-cleanupซึ่งเดิมใช้โซลูชัน cleanup.js ด้านล่าง แต่ได้ปรับปรุงอย่างมากตามความคิดเห็น ฉันหวังว่ามันจะเป็นประโยชน์
Joe Lapp

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และเหตุการณ์การออกปกติ จากนั้นจะเรียกใช้ฟังก์ชันการเรียกกลับการล้างข้อมูลผู้ใช้ทางเลือกเดียวก่อนออกจากการจัดการเงื่อนไขการออกทั้งหมดด้วยวัตถุเดียว

โมดูลเพียงแค่ขยายวัตถุกระบวนการแทนการกำหนดตัวปล่อยเหตุการณ์อื่น หากไม่มีการเรียกกลับเฉพาะแอพการล้างค่าเริ่มต้นจะไม่มีฟังก์ชั่น op นี้ก็เพียงพอสำหรับการใช้งานของฉันที่กระบวนการที่เด็กถูกทิ้งให้ทำงานเมื่อออกจาก+CtrlC

คุณสามารถเพิ่มกิจกรรมการออกอื่น ๆ เช่น 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
Joe Lapp

3
ฉันรักการทำความสะอาด แต่ฉันไม่ชอบกระบวนการออก (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()นอกจากนี้ยังมีสัญญาณในขณะนี้รีเลย์อย่างถูกต้องเพื่อการปกครองแทนการโทร ตัวจัดการการล้างข้อมูลในขณะนี้มีความยืดหยุ่นในการทำงานเป็นฟังก์ชันของรหัสทางออกหรือสัญญาณและตัวจัดการการล้างข้อมูลสามารถถอนการติดตั้งได้ตามความจำเป็นเพื่อรองรับการล้างข้อมูลแบบอะซิงโครนัสหรือป้องกันการล้างข้อมูลแบบวงกลม ตอนนี้มันมีความคล้ายคลึงกับรหัสข้างบนเล็กน้อย
Joe Lapp

3
ลืมที่จะพูดถึงว่าฉันได้ทำชุดทดสอบที่ครอบคลุม (หวังว่า)
Joe Lapp

29

สิ่งนี้จับทุกเหตุการณ์ทางออกที่ฉันพบว่าสามารถจัดการได้ ดูเหมือนน่าเชื่อถือและสะอาดมาก

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

นี่มันเจ๋งมาก!
Andranik Hovesyan

เยี่ยมมากฉันใช้มันในการผลิตทันที! ขอบคุณมัด!
ตัณหา

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 และทางออกปกติในที่เดียวกันหรือไม่? หรือฉันจะต้องเขียนตัวจัดการที่เหมือนกันสองตัว?
Erel Segal-Halevi

เช่นเดียวกับการบันทึกถ้าคุณต้องจบโหนดด้วยคำสั่ง kill การทำkill -2จะส่งSIGINTรหัส เราต้องทำเช่นนี้เพราะเรามีการบันทึกโหนดไปยังไฟล์ txt ดังนั้น Ctrl + C จึงเป็นไปไม่ได้
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
คำตอบนี้สมควรได้รับเกียรติทั้งหมด แต่เนื่องจากไม่มีคำอธิบายจึงไม่มีการลงคะแนนใด ๆ อย่างน่าเสียดาย สิ่งที่สำคัญเกี่ยวกับคำตอบนี้เป็นdoc กล่าวว่า , "ฟังก์ชั่นฟังเท่านั้นต้องดำเนินการซิงโคร. กระบวนการ 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 () ภายในการติดต่อกลับ สิ่งนี้ทำให้ฉันสะดุด
Nick Manning



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. ที่ 'exit' ให้จัดการทำความสะอาดด้วยฟังก์ชั่นใหม่
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...

สิ่งที่เกิดขึ้นคือกระบวนการโหนดที่เริ่มต้นด้วยรหัส SIGINT จากนั้นจะประมวลผลเส้นทางไปยัง process.exit () และในที่สุดก็ออกจากด้วยรหัสทางออก 0


-1

ในกรณีที่กระบวนการเกิดโดยกระบวนการโหนดอื่นเช่น:

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

และคุณพยายามที่จะฆ่ามันในภายหลังผ่าน:

child.kill();

นี่คือวิธีที่คุณจัดการกับเหตุการณ์ [บนลูก]:

process.on('SIGTERM', function() {
    console.log('Goodbye!');
});
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.