@meteorjs/ddp-graceful-shutdown
Version:
Close DDP connections gradually on server shutdown
129 lines (107 loc) • 5.51 kB
JavaScript
;
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;