UNPKG

@meteorjs/ddp-graceful-shutdown

Version:

Close DDP connections gradually on server shutdown

129 lines (107 loc) 5.51 kB
"use strict"; var _slicedToArray = function () { function sliceIterator(arr, i) { var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i["return"]) _i["return"](); } finally { if (_d) throw _e; } } return _arr; } return function (arr, i) { if (Array.isArray(arr)) { return arr; } else if (Symbol.iterator in Object(arr)) { return sliceIterator(arr, i); } else { throw new TypeError("Invalid attempt to destructure non-iterable instance"); } }; }(); var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } // DDPGracefulShutdown is a class which tracks open connections in a DDP server // and shuts them down gradually when SIGTERM is received. Docker-base hosting // environments typically send SIGTERM to processes, wait a grace period, and // then send the un-ignorable SIGKILL. DDPGracefulShutdown allows you to detect // SIGTERM and close connections over the whole grace period to ease load on new // processes. var DDPGracefulShutdown = function () { // gracePeriodMillis and server are required. Typically, server is set to // Meteor.server. On Galaxy, a good value for gracePeriodMillis is 1000 * // process.env.METEOR_SIGTERM_GRACE_PERIOD_SECONDS. function DDPGracefulShutdown(_ref) { var _this = this; var gracePeriodMillis = _ref.gracePeriodMillis, server = _ref.server; _classCallCheck(this, DDPGracefulShutdown); this.gracePeriodMillis = gracePeriodMillis; this.connections = new Map(); server.onConnection(function (conn) { _this.connections.set(conn.id, conn); conn.onClose(function () { _this.connections.delete(conn.id); }); }); } // Sets up a SIGTERM handler to call closeConnections with logging. You should // either call this function or arrange for closeConnections to be called in // some other way. _createClass(DDPGracefulShutdown, [{ key: "installSIGTERMHandler", value: function installSIGTERMHandler() { var _this2 = this; process.on("SIGTERM", function () { _this2.closeConnections({ log: true }); }); } // closeConnections calculates an interval for closing connections and starts // doing so. It is intended to be called when SIGTERM is received; // installSIGTERMHandler arranges for this to happen. If log is specified (the // default if this is called from the default SIGTERM handler) it will log one // line to stdout as well. }, { key: "closeConnections", value: function closeConnections() { var _ref2 = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}, log = _ref2.log; if (log) { console.log("Got SIGTERM; will close " + this.connections.size + " connection(s) in " + this.gracePeriodMillis + "ms"); } var delay = void 0, batchSize = void 0; if (this.gracePeriodMillis >= this.connections.size) { delay = this.gracePeriodMillis / this.connections.size; batchSize = 1; } else { delay = 1; batchSize = this.connections.size / this.gracePeriodMillis; } this.closeOneBatchAndScheduleNext(delay, batchSize); } // Internal function which closes one batch of arbitrarily chosen connections // and waits to close the next one. // // conn.close needs to be called from within a Fiber, so this arranges to get // the code in a Fiber by calling it from meteor-promise's queue. }, { key: "closeOneBatchAndScheduleNext", value: function closeOneBatchAndScheduleNext(delay, batchSize) { var _this3 = this; Promise.resolve().then(function () { var closed = 0; while (_this3.connections.size > 0 && closed < batchSize) { _this3.closeOneConnection(); closed++; } if (_this3.connections.size > 0) { setTimeout(function () { return _this3.closeOneBatchAndScheduleNext(delay, batchSize); }, delay); } }); } }, { key: "closeOneConnection", value: function closeOneConnection() { var _this4 = this; Promise.resolve().then(function () { var _connections$entries$ = _this4.connections.entries().next(), done = _connections$entries$.done, value = _connections$entries$.value; if (done) { return; } var _value = _slicedToArray(value, 2), id = _value[0], conn = _value[1]; _this4.connections.delete(id); conn.close(); }); } }]); return DDPGracefulShutdown; }(); exports.DDPGracefulShutdown = DDPGracefulShutdown;