diff --git a/src/BaseService.ts b/src/BaseService.ts index b22e96ca9..8329c9cb4 100644 --- a/src/BaseService.ts +++ b/src/BaseService.ts @@ -7,7 +7,7 @@ import * as uuid from 'uuid/v4'; import { SchemaError } from 'src/error/SchemaError'; import { ServiceModule } from 'src/module/ServiceModule'; -import { Service, ServiceDefinition } from 'src/Service'; +import { Service, ServiceDefinition, ServiceLifecycle } from 'src/Service'; import { Clock } from 'src/utils/Clock'; import { JsonPath } from 'src/utils/JsonPath'; import { dictToMap } from 'src/utils/Map'; @@ -64,6 +64,10 @@ export abstract class BaseService implements Service { } } + public async notify(event: ServiceLifecycle): Promise { + this.logger.debug({ event }, 'service notified of event'); + } + public abstract start(): Promise; public abstract stop(): Promise; diff --git a/src/Bot.ts b/src/Bot.ts index 90d9b009e..e5e731198 100644 --- a/src/Bot.ts +++ b/src/Bot.ts @@ -13,7 +13,7 @@ import { checkFilter, Filter, FilterData, FilterValue } from 'src/filter/Filter' import { ContextFetchOptions, Listener, ListenerData } from 'src/listener/Listener'; import { ServiceModule } from 'src/module/ServiceModule'; import { Parser, ParserData } from 'src/parser/Parser'; -import { Service, ServiceDefinition } from 'src/Service'; +import { Service, ServiceDefinition, ServiceLifecycle } from 'src/Service'; import { filterNil, mustFind } from 'src/utils'; import { incrementServiceCounter } from 'src/utils/metrics/Service'; import { StorageLogger, StorageLoggerOptions } from 'src/utils/StorageLogger'; @@ -88,6 +88,18 @@ export class Bot extends BaseService implements Service { return this.storage; } + public async notify(event: ServiceLifecycle) { + await super.notify(event); + await this.services.notify(event); + + switch (event) { + case ServiceLifecycle.Reset: + this.metrics.clear(); + this.logger.info('metrics reset'); + break; + } + } + /** * Set up the async resources that cannot be created in the constructor: filters, controllers, parsers, etc */ diff --git a/src/Service.ts b/src/Service.ts index dd26ed9db..6fb1b3712 100644 --- a/src/Service.ts +++ b/src/Service.ts @@ -8,6 +8,13 @@ export interface ServiceDefinition { data: TData; } +export enum ServiceLifecycle { + Reload = 'reload', + Reset = 'reset', + Start = 'start', + Stop = 'stop', +} + export interface ServiceMetadata { /** * The service's unique id. @@ -33,6 +40,7 @@ export interface ServiceMetadata { export interface Service extends ServiceMetadata { readonly id: string; + notify(event: ServiceLifecycle): Promise; start(): Promise; stop(): Promise; } diff --git a/src/index.ts b/src/index.ts index b50fe3f19..2431970b2 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,4 +1,4 @@ -import { Container, Module } from 'noicejs'; +import { Container, Logger, Module } from 'noicejs'; import * as sourceMapSupport from 'source-map-support'; import * as yargs from 'yargs-parser'; @@ -15,7 +15,8 @@ import { ServiceModule } from 'src/module/ServiceModule'; import { TransformModule } from 'src/module/TransformModule'; import { BunyanLogger } from 'src/utils/BunyanLogger'; import { Schema } from 'src/utils/Schema'; -import { signal, SIGNAL_STOP } from 'src/utils/Signal'; +import { signal, SIGNAL_RELOAD, SIGNAL_RESET, SIGNAL_STOP } from 'src/utils/Signal'; +import { ServiceLifecycle } from './Service'; // main arguments const MAIN_ARGS: yargs.Options = { @@ -81,9 +82,26 @@ function createModules(botModule: BotModule, migrate: boolean) { return modules; } -async function handleSignals(bot: Bot) { +async function handleSignals(bot: Bot, logger: Logger) { await bot.start(); - await signal(SIGNAL_STOP); + await bot.notify(ServiceLifecycle.Start); + + const signals = [SIGNAL_RELOAD, SIGNAL_RESET, SIGNAL_STOP]; + let s = await signal(...signals); + while (s !== SIGNAL_STOP) { + switch (s) { + case SIGNAL_RELOAD: + bot.notify(ServiceLifecycle.Reload); + break; + case SIGNAL_RESET: + bot.notify(ServiceLifecycle.Reset); + break; + } + s = await signal(...signals); + } + + logger.info('stop signal'); + await bot.notify(ServiceLifecycle.Stop); await bot.stop(); } @@ -116,7 +134,7 @@ async function main(argv: Array): Promise { botModule.setBot(bot); logger.info('starting bot'); - await handleSignals(bot); + await handleSignals(bot, logger); return STATUS_SUCCESS; } diff --git a/src/module/ServiceModule.ts b/src/module/ServiceModule.ts index adff26818..d6bf0818b 100644 --- a/src/module/ServiceModule.ts +++ b/src/module/ServiceModule.ts @@ -4,7 +4,7 @@ import { Container } from 'noicejs/Container'; import { BotServiceOptions } from 'src/BotService'; import { NotFoundError } from 'src/error/NotFoundError'; -import { Service, ServiceDefinition, ServiceMetadata } from 'src/Service'; +import { Service, ServiceDefinition, ServiceMetadata, ServiceLifecycle } from 'src/Service'; import { mustGet } from 'src/utils/Map'; /** @@ -29,6 +29,12 @@ export class ServiceModule extends Module implements Service { return this.services.size; } + public async notify(event: ServiceLifecycle) { + for (const svc of this.services.values()) { + await svc.notify(event); + } + } + public async start() { for (const svc of this.services.values()) { await svc.start(); diff --git a/src/utils/Signal.ts b/src/utils/Signal.ts index 49f97d8de..be4ca8d2b 100644 --- a/src/utils/Signal.ts +++ b/src/utils/Signal.ts @@ -1,13 +1,14 @@ -export const SIGNAL_RELOAD: Array = ['SIGHUP']; -export const SIGNAL_STOP: Array = ['SIGINT', 'SIGTERM']; +export const SIGNAL_RELOAD: NodeJS.Signals = 'SIGHUP'; +export const SIGNAL_RESET: NodeJS.Signals = 'SIGINT'; +export const SIGNAL_STOP: NodeJS.Signals = 'SIGTERM'; -export function signal(signals: Array): Promise { +export function signal(...signals: Array): Promise { return new Promise((res, _) => { - function handler() { - for (const sig of signals) { - process.removeListener(sig, handler); + function handler(fired: NodeJS.Signals) { + for (const signal of signals) { + process.removeListener(signal, handler); } - res(); + res(fired); } for (const sig of signals) {