index.js 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135
  1. 'use strict';
  2. var http = require('http');
  3. var cluster = require('cluster');
  4. var ms = require('humanize-ms');
  5. var pstree = require('ps-tree');
  6. /**
  7. * graceful, please use with `cluster` in production env.
  8. *
  9. * @param {Object} options
  10. * - {Array<HttpServer>} servers, we need to close it and stop taking new requests.
  11. * - {Function(err, throwErrorCount)} [error], when uncaughtException emit, error(err, count).
  12. * You can log error here.
  13. * - {Number} [killTimeout], worker suicide timeout, default is 30 seconds.
  14. * - {Object} [worker], worker contains `disconnect()`.
  15. */
  16. module.exports = function graceful(options) {
  17. options = options || {};
  18. var killTimeout = ms(options.killTimeout || '30s');
  19. var onError = options.error || function () {};
  20. var servers = options.servers || options.server || [];
  21. if (!Array.isArray(servers)) {
  22. servers = [servers];
  23. }
  24. if (servers.length === 0) {
  25. throw new TypeError('options.servers required!');
  26. }
  27. var throwErrorCount = 0;
  28. process.on('uncaughtException', function (err) {
  29. throwErrorCount += 1;
  30. onError(err, throwErrorCount);
  31. console.error('[%s] [graceful:worker:%s:uncaughtException] throw error %d times',
  32. Date(), process.pid, throwErrorCount);
  33. console.error(err);
  34. console.error(err.stack);
  35. if (throwErrorCount > 1) {
  36. return;
  37. }
  38. servers.forEach(function (server) {
  39. if (server instanceof http.Server) {
  40. server.on('request', function (req, res) {
  41. // Let http server set `Connection: close` header, and close the current request socket.
  42. req.shouldKeepAlive = false;
  43. res.shouldKeepAlive = false;
  44. if (!res._header) {
  45. res.setHeader('Connection', 'close');
  46. }
  47. });
  48. }
  49. });
  50. // make sure we close down within `killTimeout` seconds
  51. var killtimer = setTimeout(function () {
  52. console.error('[%s] [graceful:worker:%s] kill timeout, exit now.', Date(), process.pid);
  53. if (process.env.NODE_ENV !== 'test') {
  54. // kill children by SIGKILL before exit
  55. killChildren(function() {
  56. process.exit(1);
  57. });
  58. }
  59. }, killTimeout);
  60. console.error('[%s] [graceful:worker:%s] will exit after %dms', Date(), process.pid, killTimeout);
  61. // But don't keep the process open just for that!
  62. // If there is no more io waitting, just let process exit normally.
  63. if (typeof killtimer.unref === 'function') {
  64. // only worked on node 0.10+
  65. killtimer.unref();
  66. }
  67. var worker = options.worker || cluster.worker;
  68. // cluster mode
  69. if (worker) {
  70. try {
  71. // stop taking new requests.
  72. // because server could already closed, need try catch the error: `Error: Not running`
  73. for (var i = 0; i < servers.length; i++) {
  74. var server = servers[i];
  75. server.close();
  76. console.error('[%s] [graceful:worker:%s] close server#%s, _connections: %s',
  77. Date(), process.pid, i, server._connections);
  78. }
  79. console.error('[%s] [graceful:worker:%s] close %d servers!',
  80. Date(), process.pid, servers.length);
  81. } catch (er1) {
  82. // Usually, this error throw cause by the active connections after the first domain error,
  83. // oh well, not much we can do at this point.
  84. console.error('[%s] [graceful:worker:%s] Error on server close!\n%s',
  85. Date(), process.pid, er1.stack);
  86. }
  87. try {
  88. // Let the master know we're dead. This will trigger a
  89. // 'disconnect' in the cluster master, and then it will fork
  90. // a new worker.
  91. worker.send('graceful:disconnect');
  92. worker.disconnect();
  93. console.error('[%s] [graceful:worker:%s] worker disconnect!',
  94. Date(), process.pid);
  95. } catch (er2) {
  96. // Usually, this error throw cause by the active connections after the first domain error,
  97. // oh well, not much we can do at this point.
  98. console.error('[%s] [graceful:worker:%s] Error on worker disconnect!\n%s',
  99. Date(), process.pid, er2.stack);
  100. }
  101. }
  102. });
  103. };
  104. function killChildren(callback) {
  105. pstree(process.pid, function(err, children) {
  106. if (err) {
  107. // if get children error, just ignore it
  108. console.error('[%s] [graceful:worker:%s] pstree find children error: %s', Date(), process.pid, err);
  109. callback();
  110. return;
  111. }
  112. children.forEach(function(child) {
  113. kill(parseInt(child.PID));
  114. });
  115. callback();
  116. });
  117. }
  118. function kill(pid) {
  119. try {
  120. process.kill(pid, 'SIGKILL');
  121. } catch (_) {
  122. // ignore
  123. }
  124. }