From e61aad2c19437ef283098b8d6ce2228d5f7f1d82 Mon Sep 17 00:00:00 2001 From: Arjun Komath Date: Sat, 28 Mar 2026 15:55:05 +1100 Subject: [PATCH] Prevent duplicate gateway restarts after self-reload --- docker-compose.yml | 15 +++++++++ src/server.js | 84 +++++++++++++++++++++++++++++++--------------- 2 files changed, 72 insertions(+), 27 deletions(-) create mode 100644 docker-compose.yml diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..405257b --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,15 @@ +services: + openclaw: + build: . + ports: + - "8080:8080" + environment: + - PORT=8080 + - SETUP_PASSWORD=test + - OPENCLAW_STATE_DIR=/data/.openclaw + - OPENCLAW_WORKSPACE_DIR=/data/workspace + - ENABLE_WEB_TUI=true + volumes: + - ./.tmpdata:/data + + diff --git a/src/server.js b/src/server.js index 641e8ee..5c6c165 100644 --- a/src/server.js +++ b/src/server.js @@ -190,33 +190,39 @@ function sleep(ms) { return new Promise((r) => setTimeout(r, ms)); } +async function probeGatewayOnce() { + const endpoints = ["/openclaw", "/", "/health"]; + + for (const endpoint of endpoints) { + try { + const res = await fetch(`${GATEWAY_TARGET}${endpoint}`, { + method: "GET", + }); + if (res) { + return { ok: true, endpoint }; + } + } catch (err) { + if (err.code !== "ECONNREFUSED" && err.cause?.code !== "ECONNREFUSED") { + const msg = err.code || err.message; + if (msg !== "fetch failed" && msg !== "UND_ERR_CONNECT_TIMEOUT") { + log.warn("gateway", `health check error: ${msg}`); + } + } + } + } + + return { ok: false, endpoint: null }; +} + async function waitForGatewayReady(opts = {}) { const timeoutMs = opts.timeoutMs ?? 60_000; const start = Date.now(); - const endpoints = ["/openclaw", "/", "/health"]; while (Date.now() - start < timeoutMs) { - if (!gatewayProc) { - log.error("gateway", "process exited during readiness check"); - return false; - } - for (const endpoint of endpoints) { - try { - const res = await fetch(`${GATEWAY_TARGET}${endpoint}`, { - method: "GET", - }); - if (res) { - log.info("gateway", `ready at ${endpoint}`); - return true; - } - } catch (err) { - if (err.code !== "ECONNREFUSED" && err.cause?.code !== "ECONNREFUSED") { - const msg = err.code || err.message; - if (msg !== "fetch failed" && msg !== "UND_ERR_CONNECT_TIMEOUT") { - log.warn("gateway", `health check error: ${msg}`); - } - } - } + const probe = await probeGatewayOnce(); + if (probe.ok) { + log.info("gateway", `ready at ${probe.endpoint}`); + return true; } await sleep(250); } @@ -283,12 +289,24 @@ async function startGateway() { } const delay = Math.min(2000 * Math.pow(2, gatewayRestartCount), 60_000); log.info("gateway", `scheduling auto-restart in ${delay / 1000}s (attempt ${gatewayRestartCount}, uptime ${Math.round(uptime / 1000)}s)...`); - setTimeout(() => { - if (!shuttingDown && !gatewayProc && isConfigured()) { - ensureGatewayRunning().catch((err) => { - log.error("gateway", `auto-restart failed: ${err.message}`); - }); + setTimeout(async () => { + if (shuttingDown || gatewayProc || !isConfigured()) { + return; } + + const probe = await probeGatewayOnce(); + if (probe.ok) { + log.info( + "gateway", + `gateway still reachable at ${probe.endpoint}; assuming OpenClaw restarted itself`, + ); + gatewayRestartCount = 0; + return; + } + + ensureGatewayRunning().catch((err) => { + log.error("gateway", `auto-restart failed: ${err.message}`); + }); }, delay); } }); @@ -297,6 +315,10 @@ async function startGateway() { async function ensureGatewayRunning() { if (!isConfigured()) return { ok: false, reason: "not configured" }; if (gatewayProc) return { ok: true }; + const probe = await probeGatewayOnce(); + if (probe.ok) { + return { ok: true, reason: "reachable" }; + } if (!gatewayStarting) { gatewayStarting = (async () => { await syncAllowedOrigins(); @@ -333,6 +355,7 @@ async function restartGateway() { gatewayProc = null; intentionalRestart = false; } + await runCmd(OPENCLAW_NODE, clawArgs(["gateway", "stop"])); gatewayRestartCount = 0; return ensureGatewayRunning(); } @@ -1527,6 +1550,13 @@ async function gracefulShutdown(signal) { } } + try { + const stopResult = await runCmd(OPENCLAW_NODE, clawArgs(["gateway", "stop"])); + log.info("wrapper", `gateway stop during shutdown exit=${stopResult.code}`); + } catch (err) { + log.warn("wrapper", `gateway stop during shutdown failed: ${err.message}`); + } + process.exit(0); }