DC-067: graceful shutdown coordinator (EventEmitter, 10s drain, idempotent)

This commit is contained in:
hermes
2026-08-12 03:36:49 -07:00
parent ff81d99021
commit 88ff260e5e
3 changed files with 494 additions and 34 deletions
+43 -34
View File
@@ -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);