From dd98421b087df72ac46da894f4eb7592980bc0b5 Mon Sep 17 00:00:00 2001 From: Nasyarobby Putra Date: Sun, 23 Aug 2026 08:45:26 +0700 Subject: [PATCH] fix(server): refine error handling and PM2 connection logic - Reduced the maximum restarts for PM2 processes from 20 to 8 and added a restart delay of 2000ms for better stability. - Improved error handling during server startup and PM2 connection, ensuring clearer logging and graceful exits on failure. - Introduced a utility function to filter out PM2 metadata from environment variables, preventing conflicts during process management. --- ecosystem.config.cjs | 3 ++- packages/server/control.js | 39 +++++++++++++++-------------- packages/server/pm2-bridge.js | 46 ++++++++++++++++++++++++++++++++++- 3 files changed, 68 insertions(+), 20 deletions(-) diff --git a/ecosystem.config.cjs b/ecosystem.config.cjs index d42a8b3..228a9ec 100644 --- a/ecosystem.config.cjs +++ b/ecosystem.config.cjs @@ -48,7 +48,8 @@ module.exports = { instances: 1, exec_mode: "fork", autorestart: true, - max_restarts: 20, + max_restarts: 8, + restart_delay: 2000, env: { ...env, JFLOW_CONTROL_PORT: env.JFLOW_CONTROL_PORT ?? "8600", diff --git a/packages/server/control.js b/packages/server/control.js index f79c885..5a7ea65 100644 --- a/packages/server/control.js +++ b/packages/server/control.js @@ -64,13 +64,6 @@ try { process.exit(1); } -try { - await connectPm2(); -} catch (err) { - log.error({ err }, "failed to connect to PM2 — is pm2 installed?"); - process.exit(1); -} - async function applyDesiredState() { const state = readControlState(); await ensureHttp({ @@ -98,8 +91,6 @@ async function applyDesiredState() { ); } -await applyDesiredState(); - const server = fastify({ loggerInstance: log }); await server.register(cookie); await server.register(jwt, { @@ -484,12 +475,24 @@ process.on("SIGINT", shutdown); process.on("SIGTERM", shutdown); const port = Number(process.env.JFLOW_CONTROL_PORT ?? process.env.PORT ?? 8600); -server - .listen({ host: "0.0.0.0", port }) - .then(() => { - log.info(`Control is running on port ${port}`); - }) - .catch((err) => { - log.error({ err }, "failed to start control"); - process.exit(1); - }); +try { + await server.listen({ host: "0.0.0.0", port }); + log.info(`Control is running on port ${port}`); +} catch (err) { + log.error({ err }, "failed to start control"); + process.exit(1); +} + +try { + await connectPm2(); +} catch (err) { + log.error({ err }, "failed to connect to PM2 — is pm2 installed?"); + process.exit(1); +} + +try { + await applyDesiredState(); +} catch (err) { + log.error({ err }, "failed to apply desired state"); + process.exit(1); +} diff --git a/packages/server/pm2-bridge.js b/packages/server/pm2-bridge.js index 9bbec47..d7b2850 100644 --- a/packages/server/pm2-bridge.js +++ b/packages/server/pm2-bridge.js @@ -164,13 +164,57 @@ export async function restartPm2Process(pmId) { return { name: proc.name, pmId: id }; } +/** + * PM2 injects these into process.env of a managed app. Spreading them into + * `pm2.start({ env })` overwrites `name` / `pm_exec_path` so God restarts + * jflow-control instead of launching http/worker (EADDRINUSE :8600 loop). + */ +const PM2_META_KEYS = new Set([ + "name", + "namespace", + "exec_mode", + "exec_interpreter", + "instances", + "instance_var", + "node_app_instance", + "unique_id", + "status", + "username", + "windowsHide", + "merge_logs", + "vizion", + "vizion_running", + "autostart", + "autorestart", + "automation", + "km_link", +]); + +/** + * @param {NodeJS.ProcessEnv} env + * @returns {NodeJS.ProcessEnv} + */ +export function withoutPm2Meta(env) { + /** @type {NodeJS.ProcessEnv} */ + const out = {}; + for (const [key, val] of Object.entries(env)) { + if (val == null) continue; + if (PM2_META_KEYS.has(key)) continue; + if (key.startsWith("pm_") || key.startsWith("axm_") || key.startsWith("PM2_")) { + continue; + } + out[key] = val; + } + return out; +} + /** * Shared env for child processes. * @param {{ generation: number }} opts */ export function childEnv(opts) { return { - ...process.env, + ...withoutPm2Meta(process.env), JFLOW_CONFIG_GENERATION: String(opts.generation), JFLOW_CORS_ORIGIN: process.env.JFLOW_CORS_ORIGIN ?? "http://localhost:8500", PORT: process.env.JFLOW_HTTP_PORT ?? "8700",