123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135 |
- 'use strict';
- var http = require('http');
- var cluster = require('cluster');
- var ms = require('humanize-ms');
- var pstree = require('ps-tree');
- /**
- * graceful, please use with `cluster` in production env.
- *
- * @param {Object} options
- * - {Array<HttpServer>} servers, we need to close it and stop taking new requests.
- * - {Function(err, throwErrorCount)} [error], when uncaughtException emit, error(err, count).
- * You can log error here.
- * - {Number} [killTimeout], worker suicide timeout, default is 30 seconds.
- * - {Object} [worker], worker contains `disconnect()`.
- */
- module.exports = function graceful(options) {
- options = options || {};
- var killTimeout = ms(options.killTimeout || '30s');
- var onError = options.error || function () {};
- var servers = options.servers || options.server || [];
- if (!Array.isArray(servers)) {
- servers = [servers];
- }
- if (servers.length === 0) {
- throw new TypeError('options.servers required!');
- }
- var throwErrorCount = 0;
- process.on('uncaughtException', function (err) {
- throwErrorCount += 1;
- onError(err, throwErrorCount);
- console.error('[%s] [graceful:worker:%s:uncaughtException] throw error %d times',
- Date(), process.pid, throwErrorCount);
- console.error(err);
- console.error(err.stack);
- if (throwErrorCount > 1) {
- return;
- }
- servers.forEach(function (server) {
- if (server instanceof http.Server) {
- server.on('request', function (req, res) {
- // Let http server set `Connection: close` header, and close the current request socket.
- req.shouldKeepAlive = false;
- res.shouldKeepAlive = false;
- if (!res._header) {
- res.setHeader('Connection', 'close');
- }
- });
- }
- });
- // make sure we close down within `killTimeout` seconds
- var killtimer = setTimeout(function () {
- console.error('[%s] [graceful:worker:%s] kill timeout, exit now.', Date(), process.pid);
- if (process.env.NODE_ENV !== 'test') {
- // kill children by SIGKILL before exit
- killChildren(function() {
- process.exit(1);
- });
- }
- }, killTimeout);
- console.error('[%s] [graceful:worker:%s] will exit after %dms', Date(), process.pid, killTimeout);
- // But don't keep the process open just for that!
- // If there is no more io waitting, just let process exit normally.
- if (typeof killtimer.unref === 'function') {
- // only worked on node 0.10+
- killtimer.unref();
- }
- var worker = options.worker || cluster.worker;
- // cluster mode
- if (worker) {
- try {
- // stop taking new requests.
- // because server could already closed, need try catch the error: `Error: Not running`
- for (var i = 0; i < servers.length; i++) {
- var server = servers[i];
- server.close();
- console.error('[%s] [graceful:worker:%s] close server#%s, _connections: %s',
- Date(), process.pid, i, server._connections);
- }
- console.error('[%s] [graceful:worker:%s] close %d servers!',
- Date(), process.pid, servers.length);
- } catch (er1) {
- // Usually, this error throw cause by the active connections after the first domain error,
- // oh well, not much we can do at this point.
- console.error('[%s] [graceful:worker:%s] Error on server close!\n%s',
- Date(), process.pid, er1.stack);
- }
- try {
- // Let the master know we're dead. This will trigger a
- // 'disconnect' in the cluster master, and then it will fork
- // a new worker.
- worker.send('graceful:disconnect');
- worker.disconnect();
- console.error('[%s] [graceful:worker:%s] worker disconnect!',
- Date(), process.pid);
- } catch (er2) {
- // Usually, this error throw cause by the active connections after the first domain error,
- // oh well, not much we can do at this point.
- console.error('[%s] [graceful:worker:%s] Error on worker disconnect!\n%s',
- Date(), process.pid, er2.stack);
- }
- }
- });
- };
- function killChildren(callback) {
- pstree(process.pid, function(err, children) {
- if (err) {
- // if get children error, just ignore it
- console.error('[%s] [graceful:worker:%s] pstree find children error: %s', Date(), process.pid, err);
- callback();
- return;
- }
- children.forEach(function(child) {
- kill(parseInt(child.PID));
- });
- callback();
- });
- }
- function kill(pid) {
- try {
- process.kill(pid, 'SIGKILL');
- } catch (_) {
- // ignore
- }
- }
|