handle sigterm for graceful shutdown

This commit is contained in:
Erik 2023-02-10 23:56:26 +02:00
parent 32a58b0519
commit 0a2d0818e7
Signed by: Navy.gif
GPG Key ID: 2532FBBB61C65A68
3 changed files with 41 additions and 10 deletions

View File

@ -38,6 +38,8 @@ class Controller extends EventEmitter {
this.logger.error(ex.stack);
});
process.on('SIGINT', this.#shutdown.bind(this));
this.on('built', () => {
this.logger.info(`API Controller built`);
this._built = true;
@ -90,6 +92,16 @@ class Controller extends EventEmitter {
shard.on('message', (msg) => this._handleMessage(shard, msg));
}
async #shutdown () {
this.logger.info('Received SIGINT, shutting down');
const promises = this.shards.map(shard => shard.awaitShutdown());
await Promise.all(promises);
this.logger.info(`Shutdown completed, goodbye`);
// eslint-disable-next-line no-process-exit
process.exit();
}
}
module.exports = Controller;

View File

@ -2,7 +2,8 @@ const ChildProcess = require('node:child_process');
const { EventEmitter } = require('node:events');
const { Util } = require('../util');
// Give subprocess 90s to shut down before being forcibly killed
const KillTO = 90 * 1000;
class Shard extends EventEmitter {
constructor (controller, id, options = {}) {
@ -31,6 +32,8 @@ class Shard extends EventEmitter {
// Set in the spawn method
this.spawnedAt = null;
this._awaitingShutdown = null;
}
async spawn (waitForReady = false) {
@ -85,7 +88,7 @@ class Shard extends EventEmitter {
const to = setTimeout(() => {
this.process.kill();
resolve();
}, 5000);
}, KillTO);
// Gracefully handle exit
this.process.once('exit', (code, signal) => {
clearTimeout(to);
@ -118,6 +121,13 @@ class Shard extends EventEmitter {
});
}
awaitShutdown () {
this._respawn = false;
return new Promise((resolve) => {
this._awaitingShutdown = resolve;
});
}
_handleMessage (message) {
if (message) {
if (message._ready) {
@ -125,6 +135,10 @@ class Shard extends EventEmitter {
this.emit('ready');
return;
} else if (message._shutdown) {
setTimeout(() => {
if (this.process)
this.process.kill('SIGKILL');
}, KillTO);
this.ready = false;
this.emit('shutdown');
return;
@ -150,6 +164,8 @@ class Shard extends EventEmitter {
this.process.removeAllListeners();
this.emit('death');
if (this._awaitingShutdown)
this._awaitingShutdown();
if (code !== 0)
this.crashes.push(Date.now() - this.spawnedAt);

View File

@ -155,6 +155,7 @@ class Server extends EventEmitter {
this.app.use(this.#ready.bind(this)); // denies requests before the server is ready
process.on('message', this._handleMessage.bind(this));
process.on('SIGINT', this.shutdown.bind(this));
}
@ -209,17 +210,19 @@ class Server extends EventEmitter {
async shutdown () {
this.logger.info(`Received shutdown command, initiating graceful shutdown`);
// Tells the manager the shard is shutting down, will set a timeout to kill the shard if it hangs
process.send({ _shutdown: true });
this._ready = false; // stops any new connections
this._ready = false;
this.server.close(async () => {
await this.mongodb.close();
await this.mariadb.close();
this.logger.status('DB shutdowns complete.');
await this.mongodb.close();
await this.mariadb.close();
this.logger.status('DB shutdowns complete.');
this.logger.status('Shutdown complete. Goodbye');
// eslint-disable-next-line no-process-exit
process.exit();
this.logger.status('Shutdown complete. Goodbye');
// eslint-disable-next-line no-process-exit
process.exit();
});
}