graceful.js 3.6 KB

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