DC-067: graceful shutdown coordinator (EventEmitter, 10s drain, idempotent)
This commit is contained in:
+43
-34
@@ -252,43 +252,52 @@ process.on('uncaughtException', (error) => {
|
||||
log.info('server', 'All feature modules initialized');
|
||||
});
|
||||
|
||||
// Graceful shutdown
|
||||
const shutdown = (signal) => {
|
||||
log.info('shutdown', `${signal} received, draining connections...`);
|
||||
|
||||
const resourceMonitor = require('./src/managers/resource-monitor');
|
||||
const backupManager = require('./src/utilities/backup-manager');
|
||||
const healthChecker = require('./src/monitoring/health-checker');
|
||||
const updateManager = require('./src/managers/update-manager');
|
||||
const selfUpdater = require('./src/docker/self-updater');
|
||||
|
||||
resourceMonitor.stop();
|
||||
backupManager.stop();
|
||||
healthChecker.stop();
|
||||
updateManager.stop();
|
||||
selfUpdater.stop();
|
||||
|
||||
try {
|
||||
const dockerMaintenance = require('./src/docker/docker-maintenance');
|
||||
dockerMaintenance.stop();
|
||||
} catch { /* optional */ }
|
||||
|
||||
try {
|
||||
const logDigest = require('./src/security/log-digest');
|
||||
logDigest.stop();
|
||||
} catch { /* optional */ }
|
||||
// Graceful shutdown (DC-067) — drains in-flight HTTP connections, stops
|
||||
// each manager in deterministic order, emits a 'shutdown' event for any
|
||||
// additional listeners, and force-exits after a 10s drain timeout.
|
||||
// Idempotent: a second SIGTERM during shutdown is a no-op.
|
||||
const {
|
||||
createShutdownCoordinator,
|
||||
installSignalHandlers,
|
||||
DEFAULT_DRAIN_TIMEOUT_MS,
|
||||
} = require('./src/utilities/shutdown');
|
||||
|
||||
server.close(() => {
|
||||
log.info('shutdown', 'HTTP server closed');
|
||||
process.exit(0);
|
||||
const optionalManagers = [];
|
||||
try {
|
||||
optionalManagers.push({
|
||||
name: 'docker-maintenance',
|
||||
stop: () => require('./src/docker/docker-maintenance').stop(),
|
||||
});
|
||||
|
||||
// Force exit after 5s if connections don't drain
|
||||
setTimeout(() => process.exit(0), 5000).unref();
|
||||
};
|
||||
} catch { /* optional module */ }
|
||||
try {
|
||||
optionalManagers.push({
|
||||
name: 'log-digest',
|
||||
stop: () => require('./src/security/log-digest').stop(),
|
||||
});
|
||||
} catch { /* optional module */ }
|
||||
|
||||
process.on('SIGTERM', () => shutdown('SIGTERM'));
|
||||
process.on('SIGINT', () => shutdown('SIGINT'));
|
||||
const coordinator = createShutdownCoordinator({
|
||||
server,
|
||||
log,
|
||||
drainTimeoutMs: DEFAULT_DRAIN_TIMEOUT_MS,
|
||||
managers: [
|
||||
{ name: 'resource-monitor', stop: () => require('./src/managers/resource-monitor').stop() },
|
||||
{ name: 'backup-manager', stop: () => require('./src/utilities/backup-manager').stop() },
|
||||
{ name: 'health-checker', stop: () => require('./src/monitoring/health-checker').stop() },
|
||||
{ name: 'update-manager', stop: () => require('./src/managers/update-manager').stop() },
|
||||
{ name: 'self-updater', stop: () => require('./src/docker/self-updater').stop() },
|
||||
...optionalManagers,
|
||||
],
|
||||
});
|
||||
|
||||
// Expose the shutdown signal as an event so additional listeners can
|
||||
// subscribe without touching this file. The coordinator is an
|
||||
// EventEmitter and emits 'shutdown' on SIGTERM/SIGINT.
|
||||
coordinator.on('shutdown', (signal) => {
|
||||
log.info('shutdown', 'shutdown event observed', { signal });
|
||||
});
|
||||
|
||||
installSignalHandlers(coordinator);
|
||||
|
||||
} catch (error) {
|
||||
console.error('[FATAL] Server startup failed:', error);
|
||||
|
||||
Reference in New Issue
Block a user