From 74910ffcdf0d2cebb3af930bf0072d0a99f0b627 Mon Sep 17 00:00:00 2001 From: Asher Date: Mon, 17 Aug 2020 14:17:55 -0500 Subject: [PATCH] Hotswap on SIGUSR1 (#1970) --- src/node/wrapper.ts | 49 ++++++++++++++++++++++++++++++--------------- 1 file changed, 33 insertions(+), 16 deletions(-) diff --git a/src/node/wrapper.ts b/src/node/wrapper.ts index 6e2e7e74b..ba459efd1 100644 --- a/src/node/wrapper.ts +++ b/src/node/wrapper.ts @@ -32,7 +32,7 @@ export class IpcMain { public readonly onMessage = this._onMessage.event private readonly _onDispose = new Emitter() public readonly onDispose = this._onDispose.event - public readonly exit: (code?: number) => never + public readonly processExit: (code?: number) => never public constructor(public readonly parentPid?: number) { process.on("SIGINT", () => this._onDispose.emit("SIGINT")) @@ -40,7 +40,7 @@ export class IpcMain { process.on("exit", () => this._onDispose.emit(undefined)) // Ensure we control when the process exits. - this.exit = process.exit + this.processExit = process.exit process.exit = function (code?: number) { logger.warn(`process.exit() was prevented: ${code || "unknown code"}.`) } as (code?: number) => never @@ -71,6 +71,14 @@ export class IpcMain { } } + public exit(error?: number | ProcessError): never { + if (error && typeof error !== "number") { + this.processExit(typeof error.code === "number" ? error.code : 1) + } else { + this.processExit(error) + } + } + public handshake(child?: cp.ChildProcess): Promise { return new Promise((resolve, reject) => { const target = child || process @@ -161,28 +169,37 @@ export class WrapperProcess { } }) - ipcMain().onMessage(async (message) => { + ipcMain().onMessage((message) => { switch (message.type) { case "relaunch": logger.info(`Relaunching: ${this.currentVersion} -> ${message.version}`) this.currentVersion = message.version - this.started = undefined - if (this.process) { - this.process.removeAllListeners() - this.process.kill() - } - try { - await this.start() - } catch (error) { - logger.error(error.message) - ipcMain().exit(typeof error.code === "number" ? error.code : 1) - } + this.relaunch() break default: logger.error(`Unrecognized message ${message}`) break } }) + + process.on("SIGUSR1", async () => { + logger.info("Received SIGUSR1; hotswapping") + this.relaunch() + }) + } + + private async relaunch(): Promise { + this.started = undefined + if (this.process) { + this.process.removeAllListeners() + this.process.kill() + } + try { + await this.start() + } catch (error) { + logger.error(error.message) + ipcMain().exit(typeof error.code === "number" ? error.code : 1) + } } public start(): Promise { @@ -244,13 +261,13 @@ export const wrap = (fn: () => Promise): void => { .then(() => fn()) .catch((error: ProcessError): void => { logger.error(error.message) - ipcMain().exit(typeof error.code === "number" ? error.code : 1) + ipcMain().exit(error) }) } else { const wrapper = new WrapperProcess(require("../../package.json").version) wrapper.start().catch((error) => { logger.error(error.message) - ipcMain().exit(typeof error.code === "number" ? error.code : 1) + ipcMain().exit(error) }) } }