express: Forcibly terminate HTTP connections when restarting
This should make restarts via `/admin` actions (e.g., plugin installation) more reliable.pull/4770/head
parent
4c4c7b526d
commit
d56a02c85a
|
@ -2,6 +2,7 @@
|
||||||
|
|
||||||
const _ = require('underscore');
|
const _ = require('underscore');
|
||||||
const cookieParser = require('cookie-parser');
|
const cookieParser = require('cookie-parser');
|
||||||
|
const events = require('events');
|
||||||
const express = require('express');
|
const express = require('express');
|
||||||
const expressSession = require('express-session');
|
const expressSession = require('express-session');
|
||||||
const fs = require('fs');
|
const fs = require('fs');
|
||||||
|
@ -14,16 +15,35 @@ const util = require('util');
|
||||||
|
|
||||||
const logger = log4js.getLogger('http');
|
const logger = log4js.getLogger('http');
|
||||||
let serverName;
|
let serverName;
|
||||||
|
const sockets = new Set();
|
||||||
|
const socketsEvents = new events.EventEmitter();
|
||||||
|
|
||||||
exports.server = null;
|
exports.server = null;
|
||||||
|
|
||||||
const closeServer = async () => {
|
const closeServer = async () => {
|
||||||
if (exports.server == null) return;
|
if (exports.server == null) return;
|
||||||
logger.info('Closing HTTP server...');
|
logger.info('Closing HTTP server...');
|
||||||
await Promise.all([
|
// Call exports.server.close() to reject new connections but don't await just yet because the
|
||||||
util.promisify(exports.server.close.bind(exports.server))(),
|
// Promise won't resolve until all preexisting connections are closed.
|
||||||
hooks.aCallAll('expressCloseServer'),
|
const p = util.promisify(exports.server.close.bind(exports.server))();
|
||||||
]);
|
await hooks.aCallAll('expressCloseServer');
|
||||||
|
// Give existing connections some time to close on their own before forcibly terminating. The time
|
||||||
|
// should be long enough to avoid interrupting most preexisting transmissions but short enough to
|
||||||
|
// avoid a noticeable outage.
|
||||||
|
const timeout = setTimeout(async () => {
|
||||||
|
logger.info(`Forcibly terminating remaining ${sockets.size} HTTP connections...`);
|
||||||
|
for (const socket of sockets) socket.destroy(new Error('HTTP server is closing'));
|
||||||
|
}, 5000);
|
||||||
|
let lastLogged = 0;
|
||||||
|
while (sockets.size > 0) {
|
||||||
|
if (Date.now() - lastLogged > 1000) { // Rate limit to avoid filling logs.
|
||||||
|
logger.info(`Waiting for ${sockets.size} HTTP clients to disconnect...`);
|
||||||
|
lastLogged = Date.now();
|
||||||
|
}
|
||||||
|
await events.once(socketsEvents, 'updated');
|
||||||
|
}
|
||||||
|
await p;
|
||||||
|
clearTimeout(timeout);
|
||||||
exports.server = null;
|
exports.server = null;
|
||||||
logger.info('HTTP server closed');
|
logger.info('HTTP server closed');
|
||||||
};
|
};
|
||||||
|
@ -186,6 +206,14 @@ exports.restartServer = async () => {
|
||||||
hooks.aCallAll('expressConfigure', {app}),
|
hooks.aCallAll('expressConfigure', {app}),
|
||||||
hooks.aCallAll('expressCreateServer', {app, server: exports.server}),
|
hooks.aCallAll('expressCreateServer', {app, server: exports.server}),
|
||||||
]);
|
]);
|
||||||
|
exports.server.on('connection', (socket) => {
|
||||||
|
sockets.add(socket);
|
||||||
|
socketsEvents.emit('updated');
|
||||||
|
socket.on('close', () => {
|
||||||
|
sockets.delete(socket);
|
||||||
|
socketsEvents.emit('updated');
|
||||||
|
});
|
||||||
|
});
|
||||||
await util.promisify(exports.server.listen).bind(exports.server)(settings.port, settings.ip);
|
await util.promisify(exports.server.listen).bind(exports.server)(settings.port, settings.ip);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue