From f91bd6851f417b54390e66734032c688380cc5ca Mon Sep 17 00:00:00 2001 From: Alice Gaudon Date: Fri, 26 Mar 2021 10:32:02 +0100 Subject: [PATCH] Application: make start/stop sturdier, catch more stop signals --- src/Application.ts | 81 +++++++++++++++++++++++++++++++++++----------- 1 file changed, 63 insertions(+), 18 deletions(-) diff --git a/src/Application.ts b/src/Application.ts index 3b90dfc..b37b3be 100644 --- a/src/Application.ts +++ b/src/Application.ts @@ -29,6 +29,8 @@ export default abstract class Application implements Extendable { + if (this.started) throw new Error('Application already started'); + if (this.busy) throw new Error('Application busy'); + this.busy = true; + // Load core version const file = this.isInNodeModules() ? path.join(__dirname, '../../package.json') : @@ -73,22 +79,28 @@ export default abstract class Application implements Extendable { + const exitHandler = () => { this.stop().catch(console.error); - }); + }; + process.once('exit', exitHandler); + process.once('SIGINT', exitHandler); + process.once('SIGUSR1', exitHandler); + process.once('SIGUSR2', exitHandler); + process.once('uncaughtException', exitHandler); // Register migrations MysqlConnectionManager.registerMigrations(this.getMigrations()); - // Process command line - if (!this.ignoreCommandLine && await this.processCommandLine()) { - await this.stop(); - return; - } - // Register all components and alike await this.init(); + // Process command line + if (!this.ignoreCommandLine && await this.processCommandLine()) { + this.started = true; + this.busy = false; + return; + } + // Security if (process.env.NODE_ENV === 'production') { await this.checkSecuritySettings(); @@ -203,26 +215,54 @@ export default abstract class Application implements Extendable { const args = process.argv; + // Flags + const flags = { + verbose: false, + fullHttpRequests: false, + }; + let mainCommand: string | null = null; + const mainCommandArgs: string[] = []; for (let i = 2; i < args.length; i++) { switch (args[i]) { case '--verbose': - logger.setSettings({minLevel: "trace"}); + flags.verbose = true; break; case '--full-http-requests': - LogRequestsComponent.logFullHttpRequests(); + flags.fullHttpRequests = true; break; case 'migration': - await MysqlConnectionManager.migrationCommand(args.slice(i + 1)); - return true; + if (mainCommand === null) mainCommand = args[i]; + else throw new Error(`Only one main command can be used at once (${mainCommand},${args[i]})`); + break; default: - logger.warn('Unrecognized argument', args[i]); + if (mainCommand) mainCommandArgs.push(args[i]); + else logger.fatal('Unrecognized argument', args[i]); return true; } } + + if (flags.verbose) logger.setSettings({minLevel: "trace"}); + if (flags.fullHttpRequests) LogRequestsComponent.logFullHttpRequests(); + + if (mainCommand) { + switch (mainCommand) { + case 'migration': + await MysqlConnectionManager.migrationCommand(mainCommandArgs); + await this.stop(); + break; + default: + logger.fatal('Unimplemented main command', mainCommand); + break; + } + return true; + } + return false; } @@ -247,13 +287,18 @@ export default abstract class Application implements Extendable { - logger.info('Stopping application...'); + if (this.started && !this.busy) { + this.busy = true; + logger.info('Stopping application...'); - for (const component of this.components) { - await component.stop?.(); + for (const component of this.components) { + await component.stop?.(); + } + + logger.info(`${this.constructor.name} stopped properly.`); + this.started = false; + this.busy = false; } - - logger.info(`${this.constructor.name} v${this.version} - bye`); } private routes(initRouter: Router, handleRouter: Router) {