Thực hiện hành động dọn dẹp ngay trước khi thoát Node.js


325

Tôi muốn nói với Node.js luôn luôn làm một cái gì đó ngay trước khi nó thoát ra, vì bất kỳ lý do gì - Ctrl+ C, một ngoại lệ hoặc bất kỳ lý do nào khác.

Tôi đã thử điều này:

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

Tôi bắt đầu quá trình, giết nó, và không có gì xảy ra. Tôi bắt đầu lại, nhấn Ctrl+ C, và vẫn không có gì xảy ra ...


Câu trả lời:


511

CẬP NHẬT:

Bạn có thể đăng ký một trình xử lý cho process.on('exit')và trong mọi trường hợp khác ( SIGINThoặc ngoại lệ chưa xử lý) để gọiprocess.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
Có cách nào để xử lý cả Ctrl + C và lối thoát thông thường ở cùng một nơi không, hoặc tôi có phải viết hai trình xử lý riêng biệt không? Còn các loại lối thoát khác, chẳng hạn như ngoại lệ chưa được xử lý - có một trình xử lý cụ thể cho trường hợp đó, nhưng tôi có nên xử lý việc này với bản sao thứ ba của cùng một trình xử lý không?
Erel Segal-Halevi

1
@RobFox CV () khởi tạo quá trình đọc. Stdin bị tạm dừng theo mặc định. Bạn có thể đọc thêm trên: github.com/joyent/node/blob/ mẹo
Emil Condrea

65
Lưu ý rằng bạn must onlythực hiện các synchronousthao tác trong exittrình xử lý
Lewis

2
@KesemDavid Tôi nghĩ bạn nên sử dụng beforeExitsự kiện thay thế.
Lewis

22
Giải pháp này có nhiều vấn đề. (1) Nó không báo cáo tín hiệu cho các quá trình cha. (2) Nó không truyền mã thoát cho quy trình cha. (3) Nó không cho phép những đứa trẻ giống Emacs bỏ qua Ctrl-C SIGINT. (4) Nó không cho phép dọn dẹp không đồng bộ. (5) Nó không phối hợp một stderrtin nhắn trên nhiều trình xử lý dọn dẹp. Tôi đã viết một mô-đun thực hiện tất cả điều này, github.com/jtlapp/node-cleanup , ban đầu dựa trên giải pháp Cleanup.js bên dưới, nhưng được sửa đổi rất nhiều dựa trên phản hồi. Tôi hy vọng nó chứng minh hữu ích.
Joe Lapp

179

Kịch bản dưới đây cho phép có một trình xử lý duy nhất cho tất cả các điều kiện thoát. Nó sử dụng chức năng gọi lại dành riêng cho ứng dụng để thực hiện mã dọn dẹp tùy chỉnh.

dọn dẹp.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);
  });
};

Mã này chặn các ngoại lệ chưa được phát hiện, Ctrl+ Cvà các sự kiện thoát bình thường. Sau đó, nó gọi một chức năng gọi lại dọn dẹp tùy chọn của người dùng trước khi thoát, xử lý tất cả các điều kiện thoát với một đối tượng.

Mô-đun chỉ đơn giản là mở rộng đối tượng quá trình thay vì xác định một trình phát sự kiện khác. Nếu không có một cuộc gọi lại cụ thể cho ứng dụng, việc dọn dẹp sẽ mặc định là không có chức năng op. Điều này là đủ cho việc sử dụng của tôi khi các tiến trình con bị bỏ chạy khi thoát bằng Ctrl+ C.

Bạn có thể dễ dàng thêm các sự kiện thoát khác như SIGHUP như mong muốn. Lưu ý: theo hướng dẫn sử dụng NodeJS, SIGKILL không thể có người nghe. Mã kiểm tra bên dưới thể hiện nhiều cách sử dụng 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 mã cụ thể này đi đâu?
hownowbrowncow

10
Tôi đã tìm thấy mã này không thể thiếu và tạo ra một gói nút cho nó, với các sửa đổi, ghi có cho bạn và câu trả lời SO này. Hy vọng là ổn, @CanyonCasa. Cảm ơn bạn! npmjs.com/package/node-cleanup
Joe Lapp

3
Tôi yêu việc dọn dẹp. Nhưng tôi không thích process.exit (0); cons.org/cracauer/sigint.html Cảm giác của tôi là bạn nên để hạt nhân xử lý sự hủy diệt. Bạn không thoát ra giống như một SIGINT. SIGINT không thoát với 2. Bạn đang nhầm SIGINT với mã lỗi. Họ không giống nhau. Trên thực tế Ctrl + C tồn tại với 130. Không phải 2. tldp.org/LDP/abs/html/exitcodes.html
Banjocat

5
Tôi đã viết lại npmjs.com/package/node-cleanup để việc xử lý SIGINT hoạt động tốt với các quy trình khác, theo liên kết của mỗi @ Banjocat. Bây giờ nó cũng chuyển tiếp tín hiệu chính xác đến tiến trình cha thay vì gọi process.exit(). Trình xử lý dọn dẹp hiện có tính linh hoạt để hoạt động như một chức năng của mã thoát hoặc tín hiệu, và trình xử lý dọn dẹp có thể được gỡ cài đặt, khi cần thiết để hỗ trợ dọn dẹp không đồng bộ hoặc ngăn chặn dọn dẹp theo chu kỳ. Bây giờ nó có chút giống với mã trên.
Joe Lapp

3
Quên đề cập đến việc tôi cũng đã thực hiện một bộ thử nghiệm toàn diện (hy vọng).
Joe Lapp

28

Điều này nắm bắt mọi sự kiện thoát tôi có thể tìm thấy có thể được xử lý. Có vẻ khá đáng tin cậy và sạch sẽ cho đến nay.

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

Điều này thật tuyệt!
Andranik Hovesyan

Tuyệt vời, tôi đang sử dụng nó trong sản xuất ngay bây giờ! Cảm ơn nhiều!
randy

20

"exit" là một sự kiện được kích hoạt khi nút hoàn thành vòng lặp sự kiện bên trong, nó không được kích hoạt khi bạn chấm dứt quá trình bên ngoài.

Những gì bạn đang tìm kiếm là thực hiện một cái gì đó trên SIGINT.

Các tài liệu tại http://nodejs.org/api/ process.html # process_signal_events đưa ra một ví dụ:

Ví dụ về nghe cho 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.');
});

Lưu ý: điều này dường như làm gián đoạn sigint và bạn sẽ cần gọi process.exit () khi bạn hoàn thành với mã của mình.


1
Có cách nào để xử lý cả Ctrl + C và lối thoát thông thường ở cùng một nơi không? Hay tôi phải viết hai trình xử lý giống hệt nhau?
Erel Segal-Halevi

Cũng như một lưu ý, nếu bạn phải kết thúc nút bằng lệnh kill kill -2thì sẽ truyền SIGINTmã. Chúng tôi phải làm theo cách này vì chúng tôi có nút đăng nhập vào tệp txt nên Ctrl + C là không thể.
Áo

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
Câu trả lời này xứng đáng với tất cả vinh quang, nhưng vì không có lời giải thích, nên không có sự bỏ phiếu nào cả. Điều quan trọng về câu trả lời này là, tài liệu nói , "Các hàm Listener chỉ phải thực hiện các hoạt động đồng bộ. Quá trình Node.js sẽ thoát ngay lập tức sau khi gọi trình lắng nghe sự kiện 'exit' khiến bất kỳ công việc bổ sung nào vẫn bị xếp hàng trong vòng lặp sự kiện bị bỏ qua. " , Và câu trả lời này vượt qua giới hạn đó!
xpt

7

Chỉ muốn đề cập đến deathgói ở đây: https://github.com/jprichardson/node-death

Thí dụ:

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

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

Có vẻ như bạn phải thoát khỏi chương trình một cách rõ ràng với process.exit () trong hàm gọi lại. Điều này làm tôi vấp ngã.
Nick Manning


0

Đây là một bản hack hay cho windows

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

0

Sau khi chơi xung quanh với câu trả lời khác, đây là giải pháp của tôi cho nhiệm vụ này. Thực hiện theo cách này giúp tôi tập trung dọn dẹp ở một nơi, ngăn chặn việc xử lý gấp đôi việc dọn dẹp.

  1. Tôi muốn định tuyến tất cả các mã thoát khác đến mã 'thoát'.
const others = [`SIGINT`, `SIGUSR1`, `SIGUSR2`, `uncaughtException`, `SIGTERM`]
others.forEach((eventType) => {
    process.on(eventType, exitRouter.bind(null, { exit: true }));
})
  1. Những gì exitRouter làm là gọi process.exit ()
function exitRouter(options, exitCode) {
   if (exitCode || exitCode === 0) console.log(`ExitCode ${exitCode}`);
   if (options.exit) process.exit();
}
  1. Trên 'exit', xử lý dọn dẹp với một chức năng mới
function exitHandler(exitCode) {
  console.log(`ExitCode ${exitCode}`);
  console.log('Exiting finally...')
}

process.on('exit', exitHandler)

Đối với mục đích demo, đây là liên kết đến ý chính của tôi. Trong tệp, tôi thêm một setTimeout để giả mạo quá trình đang chạy.

Nếu bạn chạy node node-exit-demo.jsvà không làm gì thì sau 2 giây, bạn sẽ thấy nhật ký:

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

Khác nếu trước khi dịch vụ kết thúc, bạn chấm dứt bởi ctrl+C, bạn sẽ thấy:

^CExitCode SIGINT
ExitCode 0
Exiting finally...

Điều đã xảy ra là quá trình Node ban đầu được thoát với mã SIGINT, sau đó nó định tuyến đến process.exit () và cuối cùng đã thoát với mã thoát 0.


-1

Trong trường hợp quy trình được sinh ra bởi một quy trình nút khác, như:

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

Và bạn cố gắng giết nó sau, thông qua:

child.kill();

Đây là cách bạn xử lý sự kiện [trên đứa trẻ]:

process.on('SIGTERM', function() {
    console.log('Goodbye!');
});
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.