Application: make start/stop sturdier, catch more stop signals

This commit is contained in:
Alice Gaudon 2021-03-26 10:32:02 +01:00
parent 93e79984e3
commit f91bd6851f

View File

@ -29,6 +29,8 @@ export default abstract class Application implements Extendable<ApplicationCompo
private cacheProvider?: CacheProvider; private cacheProvider?: CacheProvider;
private ready: boolean = false; private ready: boolean = false;
private started: boolean = false;
private busy: boolean = false;
protected constructor(version: string, ignoreCommandLine: boolean = false) { protected constructor(version: string, ignoreCommandLine: boolean = false) {
this.version = version; this.version = version;
@ -59,6 +61,10 @@ export default abstract class Application implements Extendable<ApplicationCompo
} }
public async start(): Promise<void> { public async start(): Promise<void> {
if (this.started) throw new Error('Application already started');
if (this.busy) throw new Error('Application busy');
this.busy = true;
// Load core version // Load core version
const file = this.isInNodeModules() ? const file = this.isInNodeModules() ?
path.join(__dirname, '../../package.json') : path.join(__dirname, '../../package.json') :
@ -73,22 +79,28 @@ export default abstract class Application implements Extendable<ApplicationCompo
logger.info(`${config.get('app.name')} v${this.version} | swaf v${this.coreVersion}`); logger.info(`${config.get('app.name')} v${this.version} | swaf v${this.coreVersion}`);
// Catch interrupt signals // Catch interrupt signals
process.once('SIGINT', () => { const exitHandler = () => {
this.stop().catch(console.error); 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 // Register migrations
MysqlConnectionManager.registerMigrations(this.getMigrations()); MysqlConnectionManager.registerMigrations(this.getMigrations());
// Process command line
if (!this.ignoreCommandLine && await this.processCommandLine()) {
await this.stop();
return;
}
// Register all components and alike // Register all components and alike
await this.init(); await this.init();
// Process command line
if (!this.ignoreCommandLine && await this.processCommandLine()) {
this.started = true;
this.busy = false;
return;
}
// Security // Security
if (process.env.NODE_ENV === 'production') { if (process.env.NODE_ENV === 'production') {
await this.checkSecuritySettings(); await this.checkSecuritySettings();
@ -203,26 +215,54 @@ export default abstract class Application implements Extendable<ApplicationCompo
this.routes(initRouter, handleRouter); this.routes(initRouter, handleRouter);
this.ready = true; this.ready = true;
this.started = true;
this.busy = false;
} }
protected async processCommandLine(): Promise<boolean> { protected async processCommandLine(): Promise<boolean> {
const args = process.argv; 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++) { for (let i = 2; i < args.length; i++) {
switch (args[i]) { switch (args[i]) {
case '--verbose': case '--verbose':
logger.setSettings({minLevel: "trace"}); flags.verbose = true;
break; break;
case '--full-http-requests': case '--full-http-requests':
LogRequestsComponent.logFullHttpRequests(); flags.fullHttpRequests = true;
break; break;
case 'migration': case 'migration':
await MysqlConnectionManager.migrationCommand(args.slice(i + 1)); if (mainCommand === null) mainCommand = args[i];
return true; else throw new Error(`Only one main command can be used at once (${mainCommand},${args[i]})`);
break;
default: default:
logger.warn('Unrecognized argument', args[i]); if (mainCommand) mainCommandArgs.push(args[i]);
else logger.fatal('Unrecognized argument', args[i]);
return true; 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; return false;
} }
@ -247,13 +287,18 @@ export default abstract class Application implements Extendable<ApplicationCompo
} }
public async stop(): Promise<void> { public async stop(): Promise<void> {
logger.info('Stopping application...'); if (this.started && !this.busy) {
this.busy = true;
logger.info('Stopping application...');
for (const component of this.components) { for (const component of this.components) {
await component.stop?.(); 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) { private routes(initRouter: Router, handleRouter: Router) {