From 3ce1bb3f7064659fda6b03f6088bebb58596d708 Mon Sep 17 00:00:00 2001 From: Hugo Morosini Date: Fri, 14 Feb 2020 12:33:34 +0100 Subject: [PATCH 1/3] better process management prettier should be better now removed terminator fixes fixed fixes oops --- src/index.js | 4 +- src/main/InternalProcess.js | 146 +++++++++++++++++++++++ src/main/index.js | 4 - src/main/internal-lifecycle.js | 204 +++++++++++---------------------- src/main/terminator.js | 33 ------ src/main/window-lifecycle.js | 3 - 6 files changed, 216 insertions(+), 178 deletions(-) create mode 100644 src/main/InternalProcess.js delete mode 100644 src/main/terminator.js diff --git a/src/index.js b/src/index.js index cad54c67..c79239de 100644 --- a/src/index.js +++ b/src/index.js @@ -1,6 +1,4 @@ -import cluster from "cluster"; - -if (cluster.isMaster) { +if (!process.env.IS_INTERNAL_PROCESS) { // Main electron thread require("./main"); } else { diff --git a/src/main/InternalProcess.js b/src/main/InternalProcess.js new file mode 100644 index 00000000..c8d1aa81 --- /dev/null +++ b/src/main/InternalProcess.js @@ -0,0 +1,146 @@ +import { fork } from "child_process"; +import logger from "~/logger"; + +class InternalProcess { + constructor({ timeout }) { + this.process = null; + + this.timeout = timeout; + this.active = false; + + this.onStartCallback = null; + this.onMessageCallback = null; + this.onExitCallback = null; + + this.messageQueue = []; + } + + onMessage(callback) { + this.onMessageCallback = callback; + } + + onExit(callback) { + this.onExitCallback = callback; + } + + run() { + while (this.messageQueue.length && this.active && this.process) { + const message = this.messageQueue.shift(); + this.process.send(message); + } + } + + send(message) { + this.messageQueue.push(message); + if (this.active) { + this.run(); + } + } + + onStart(callback) { + this.onStartCallback = callback; + } + + configure(path, args, options) { + this.path = path; + this.args = args; + this.options = options; + } + + start() { + if (this.process) { + throw new Error("Internal process is already running !"); + } + + this.process = fork(this.path, this.args, this.options); + + this.active = true; + const pid = this.process.pid; + + logger.warn(`spawned internal process ${pid}`); + + this.process.on("exit", (code, signal) => { + this.process = null; + + if (code !== null) { + console.log(`internal process ${pid} gracefully exited with code ${code}`); + logger.warn(`Internal process ended with code ${code}`); + } else { + console.log(`internal process ${pid} got killed by signal ${signal}`); + logger.warn(`Internal process killed with signal ${signal}`); + } + + if (this.onExitCallback) { + this.onExitCallback(code, signal, this.active); + } + }); + + this.process.on("message", message => { + if (this.onMessageCallback) { + this.onMessageCallback(message); + } + }); + + if (this.messageQueue.length) { + this.run(); + } + + this.process.stdout.on("data", data => + String(data) + .split("\n") + .forEach(msg => { + if (!msg) return; + if (process.env.INTERNAL_LOGS) console.log(msg); + try { + const obj = JSON.parse(msg); + if (obj && obj.type === "log") { + logger.onLog(obj.log); + return; + } + } catch (e) {} + logger.debug("I: " + msg); + }), + ); + this.process.stderr.on("data", data => { + const msg = String(data).trim(); + if (__DEV__) console.error("I.e: " + msg); + logger.error("I.e: " + String(data).trim()); + }); + + if (this.onStartCallback) { + this.onStartCallback(); + } + } + + stop() { + return new Promise((resolve, reject) => { + if (!this.process) { + reject(new Error("Process not running")); + return; + } + + this.messageQueue = []; + const pid = this.process.pid; + + logger.warn(`ending process ${pid} ...`); + this.active = false; + this.process.once("exit", () => { + resolve(); + }); + this.process.disconnect(); + setTimeout(() => { + if (this.process && this.process.pid === pid) { + this.process.kill("SIGINT"); + } + }, this.timeout); + }); + } + + restart() { + return this.stop().then(() => { + this.start(); + }); + } +} + +export default InternalProcess; diff --git a/src/main/index.js b/src/main/index.js index 93b244cf..f862aec4 100644 --- a/src/main/index.js +++ b/src/main/index.js @@ -25,10 +25,6 @@ if (!gotLock) { }); } -app.on("window-all-closed", () => { - app.quit(); -}); - app.on("activate", () => { const w = getMainWindow(); if (w) { diff --git a/src/main/internal-lifecycle.js b/src/main/internal-lifecycle.js index c88eca32..12b1c8f3 100644 --- a/src/main/internal-lifecycle.js +++ b/src/main/internal-lifecycle.js @@ -1,52 +1,29 @@ // @flow -import invariant from "invariant"; import { app, ipcMain } from "electron"; -import debounce from "lodash/debounce"; import path from "path"; import rimraf from "rimraf"; -import cluster from "cluster"; import { setEnvUnsafe, getAllEnvs } from "@ledgerhq/live-common/lib/env"; import { isRestartNeeded } from "~/helpers/env"; import logger from "~/logger"; -import { setInternalProcessPID } from "./terminator"; import { getMainWindow } from "./window-lifecycle"; -import { isTerminated } from "~/main/terminator"; +import InternalProcess from "./InternalProcess"; // ~~~ Local state that main thread keep const hydratedPerCurrency = {}; -let internalProcess; // ~~~ const LEDGER_CONFIG_DIRECTORY = app.getPath("userData"); const LEDGER_LIVE_SQLITE_PATH = path.resolve(app.getPath("userData"), "sqlite"); +const internal = new InternalProcess({ timeout: 2000 }); + const cleanUpBeforeClosingSync = () => { rimraf.sync(path.resolve(LEDGER_CONFIG_DIRECTORY, "sqlite/*.log")); }; -const handleExit = (worker: ?cluster$Worker, code, signal) => { - const pid = String(worker); - console.log(`worker ${pid} died with error code ${code} and signal ${signal}`); - logger.warn(`Internal process ended with code ${code}`); - internalProcess = null; -}; - -const killInternalProcess = () => { - if (internalProcess) { - logger.log("killing internal process..."); - internalProcess.removeListener("exit", handleExit); - internalProcess.kill("SIGINT"); - internalProcess = null; - } -}; - -const killInternalProcessDebounce = debounce(() => { - killInternalProcess(); -}, 500); - const sentryEnabled = false; const userId = "TODO"; @@ -62,90 +39,82 @@ const spawnCoreProcess = () => { SENTRY_USER_ID: userId, }; - cluster.setupMaster({ - exec: `${__dirname}/main.bundle.js`, + internal.configure(`${__dirname}/main.bundle.js`, [], { + env, execArgv: (process.env.LEDGER_INTERNAL_ARGS || "").split(/[ ]+/).filter(Boolean), silent: true, }); + internal.start(); +}; - const worker = cluster.fork(env); - setInternalProcessPID(worker.process.pid); - - worker.process.stdout.on("data", data => - String(data) - .split("\n") - .forEach(msg => { - if (!msg) return; - if (process.env.INTERNAL_LOGS) console.log(msg); - try { - const obj = JSON.parse(msg); - if (obj && obj.type === "log") { - logger.onLog(obj.log); - return; - } - } catch (e) {} - logger.debug("I: " + msg); - }), - ); - worker.process.stderr.on("data", data => { - const msg = String(data).trim(); - if (__DEV__) console.error("I.e: " + msg); - logger.error("I.e: " + String(data).trim()); - }); +internal.onStart(() => { + internal.process.on("message", handleGlobalInternalMessage); - worker.on("message", handleGlobalInternalMessage); - worker.on("exit", handleExit); - worker.send({ + internal.send({ type: "init", hydratedPerCurrency, }); +}); - internalProcess = worker; -}; - -process.on("exit", () => { - killInternalProcess(); +app.on("window-all-closed", async () => { + logger.warn("cleaning internal because main is done"); + if (internal.active) { + await internal.stop(); + app.quit(); + } cleanUpBeforeClosingSync(); + app.quit(); }); -ipcMain.on("clean-processes", () => { - killInternalProcess(); +ipcMain.on("clean-processes", async () => { + logger.warn("cleaning processes on demand"); + if (internal.active) { + await internal.stop(); + } + spawnCoreProcess(); }); -ipcMainListenReceiveCommands({ - onUnsubscribe: requestId => { - if (internalProcess) { - internalProcess.send({ type: "command-unsubscribe", requestId }); +let ongoing = {}; + +internal.onMessage(message => { + const event = ongoing[message.requestId]; + if (event) { + event.reply("command-event", message); + + if (message.type === "cmd.ERROR" || message.type === "cmd.COMPLETE") { + delete ongoing[message.requestId]; } - }, - onCommand: (command, notifyCommandEvent) => { - if (!internalProcess) spawnCoreProcess(); - const p = internalProcess; - invariant(p, "internalProcess not started !?"); - - const handleExit = code => { - p.removeListener("message", handleMessage); - p.removeListener("exit", handleExit); - notifyCommandEvent({ + } +}); + +internal.onExit((code, signal, unexpected) => { + if (unexpected) { + Object.keys(ongoing).forEach(requestId => { + const event = ongoing[requestId]; + event.reply("command-event", { type: "cmd.ERROR", - requestId: command.requestId, - data: { message: `Internal process error (${code})`, name: "InternalError" }, + requestId, + data: { + message: + code !== null + ? `Internal process error (${code})` + : `Internal process killed by signal (${signal})`, + name: "InternalError", + }, }); - }; - - const handleMessage = payload => { - if (payload.requestId !== command.requestId) return; - notifyCommandEvent(payload); - if (payload.type === "cmd.ERROR" || payload.type === "cmd.COMPLETE") { - p.removeListener("message", handleMessage); - p.removeListener("exit", handleExit); - } - }; + }); + } + ongoing = {}; +}); + +ipcMain.on("command", (event, command) => { + ongoing[command.requestId] = event; + internal.send({ type: "command", command }); +}); - p.on("exit", handleExit); - p.on("message", handleMessage); - p.send({ type: "command", command }); - }, +ipcMain.removeListener("command-unsubscribe", (event, { requestId }) => { + delete ongoing[requestId]; + internal.send({ type: "command-unsubscribe", requestId }); }); function handleGlobalInternalMessage(payload) { @@ -180,16 +149,17 @@ ipcMain.on('sentryLogsChanged', (event, payload) => { }) */ -ipcMain.on("setEnv", (event, env) => { +ipcMain.on("setEnv", async (event, env) => { const { name, value } = env; if (setEnvUnsafe(name, value)) { if (isRestartNeeded(name)) { - killInternalProcessDebounce(); + if (internal.active) { + await internal.stop(); + } + spawnCoreProcess(); } else { - const p = internalProcess; - if (!p) return; - p.send({ type: "setEnv", env }); + internal.send({ type: "setEnv", env }); } } }); @@ -197,42 +167,6 @@ ipcMain.on("setEnv", (event, env) => { ipcMain.on("hydrateCurrencyData", (event, { currencyId, serialized }) => { if (hydratedPerCurrency[currencyId] === serialized) return; hydratedPerCurrency[currencyId] = serialized; - const p = internalProcess; - if (p) { - p.send({ type: "hydrateCurrencyData", serialized, currencyId }); - } -}); -// Implements command message of (Main proc -> Renderer proc) -// (dual of ipcRendererSendCommand) -type Msg = { - type: "cmd.NEXT" | "cmd.COMPLETE" | "cmd.ERROR", - requestId: string, - data?: A, -}; -function ipcMainListenReceiveCommands(o: { - onUnsubscribe: (requestId: string) => void, - onCommand: ( - command: { id: string, data: *, requestId: string }, - notifyCommandEvent: (Msg<*>) => void, - ) => void, -}) { - const onCommandUnsubscribe = (event, { requestId }) => { - o.onUnsubscribe(requestId); - }; - - const onCommand = (event, command) => { - o.onCommand(command, payload => { - if (isTerminated()) return; - event.sender.send("command-event", payload); - }); - }; - - ipcMain.on("command-unsubscribe", onCommandUnsubscribe); - ipcMain.on("command", onCommand); - - return () => { - ipcMain.removeListener("command-unsubscribe", onCommandUnsubscribe); - ipcMain.removeListener("command", onCommand); - }; -} + internal.send({ type: "hydrateCurrencyData", serialized, currencyId }); +}); diff --git a/src/main/terminator.js b/src/main/terminator.js deleted file mode 100644 index 26a3f055..00000000 --- a/src/main/terminator.js +++ /dev/null @@ -1,33 +0,0 @@ -// @flow - -// <((((((\\\ -// / . }\ -// ;--..--._|} -// (\ '--/\--' ) -// \\ | '-' :'| -// \\ . -==- .-| -// \\ \.__.' \--._ -// [\\ __.--| // _/'--. -// \ \\ .'-._ ('-----'/ __/ \ -// \ \\ / __>| | '--. | -// \ \\ | \ | / / / -// \ '\ / \ | | _/ / -// \ \ \ | | / / -// \ \ \ / - -let INTERNAL_PROCESS_PID: ?number = null; -let terminated = false; - -function kill(processType, pid) { - console.log(`-> Killing ${processType} process ${pid}`); // eslint-disable-line no-console - process.kill(pid, "SIGTERM"); -} - -exports.setInternalProcessPID = (pid: number) => (INTERNAL_PROCESS_PID = pid); - -exports.terminate = () => { - terminated = true; - if (INTERNAL_PROCESS_PID) kill("internal", INTERNAL_PROCESS_PID); -}; - -exports.isTerminated = () => terminated; diff --git a/src/main/window-lifecycle.js b/src/main/window-lifecycle.js index 2275c295..6071b746 100644 --- a/src/main/window-lifecycle.js +++ b/src/main/window-lifecycle.js @@ -1,7 +1,6 @@ // @flow import "./setup"; import { BrowserWindow, screen } from "electron"; -import { terminate } from "./terminator"; import path from "path"; const intFromEnv = (key: string, def: number): number => { @@ -84,8 +83,6 @@ export async function createMainWindow({ dimensions, positions }: any) { mainWindow.webContents.openDevTools(); } - mainWindow.on("close", terminate); - mainWindow.on("closed", () => { mainWindow = null; }); From c502ecd7987af48cfefe11881935e29c47ce0ff0 Mon Sep 17 00:00:00 2001 From: Hugo Morosini Date: Tue, 18 Feb 2020 14:44:36 +0100 Subject: [PATCH 2/3] test --- src/internal/index.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/internal/index.js b/src/internal/index.js index 72872c89..729136f7 100644 --- a/src/internal/index.js +++ b/src/internal/index.js @@ -10,6 +10,8 @@ import LoggerTransport from "~/logger/logger-transport-internal"; import { executeCommand, unsubscribeCommand, unsubscribeAllCommands } from "./commandHandler"; +coucou_arnaud = 1; + process.on("exit", () => { logger.debug("exiting process, unsubscribing all..."); unsubscribeSetup(); From 8a67181b21fcae9401266c46870e9978da0a2d4e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ga=C3=ABtan=20Renaudeau?= Date: Tue, 18 Feb 2020 15:10:03 +0100 Subject: [PATCH 3/3] Update index.js --- src/internal/index.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/internal/index.js b/src/internal/index.js index 729136f7..76c26ccb 100644 --- a/src/internal/index.js +++ b/src/internal/index.js @@ -10,6 +10,8 @@ import LoggerTransport from "~/logger/logger-transport-internal"; import { executeCommand, unsubscribeCommand, unsubscribeAllCommands } from "./commandHandler"; +/* eslint-disable */ +// $FlowFixMe coucou_arnaud = 1; process.on("exit", () => {