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 ready: boolean = false;
private started: boolean = false;
private busy: boolean = false;
protected constructor(version: string, ignoreCommandLine: boolean = false) {
this.version = version;
@ -59,6 +61,10 @@ export default abstract class Application implements Extendable<ApplicationCompo
}
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
const file = this.isInNodeModules() ?
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}`);
// Catch interrupt signals
process.once('SIGINT', () => {
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<ApplicationCompo
this.routes(initRouter, handleRouter);
this.ready = true;
this.started = true;
this.busy = false;
}
protected async processCommandLine(): Promise<boolean> {
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<ApplicationCompo
}
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) {
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) {