'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} 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 } }