From 10d4a4facff136a9dcf013789f538aea8f91d1e9 Mon Sep 17 00:00:00 2001 From: karim Date: Thu, 4 Jun 2026 23:20:59 +0200 Subject: [PATCH] bridge: fix exponential reconnect (OOM) + add dataref resolve probe MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The X-Plane socket fired both 'error' and 'close' on a failed connect, each scheduling a reconnect — so attempts doubled every cycle, leaking sockets/timers until the process OOMed (~3 min with the sim down). Guard onDown so each socket schedules exactly one reconnect (and tear the dead socket down). Also log a one-shot resolve-probe (GET /datarefs?filter[name]=airspeed…) on connect so a Web-API version/format mismatch is visible in the log. Co-Authored-By: Claude Opus 4.8 --- server/bridge.js | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/server/bridge.js b/server/bridge.js index 79fd970..5c2a076 100644 --- a/server/bridge.js +++ b/server/bridge.js @@ -79,6 +79,15 @@ async function fetchAllByName(resource, names) { // ---- X-Plane connection --------------------------------------------------- async function resolveIds() { + // one-shot diagnostic: probe a universal dataref so the log shows the API's + // real response shape (helps when a version/format mismatch yields 0 matches). + try { + const probe = 'sim/cockpit2/gauges/indicators/airspeed_kts_pilot'; + const u = `${REST}/datarefs?filter[name]=${encodeURIComponent(probe)}`; + const r = await fetch(u, { headers: { Accept: 'application/json' } }); + const txt = await r.text(); + log(`resolve-probe: GET ${u} -> HTTP ${r.status}; body ${txt.slice(0, 160)}`); + } catch (e) { log(`resolve-probe failed: ${e.message}`); } const drefNames = Object.values(DATAREFS); const cmdNames = Object.values(COMMANDS); state.drefNameToId = await fetchAllByName('datarefs', [ @@ -146,11 +155,18 @@ function connectXPlane() { } }); + // 'error' and 'close' both fire on a failed socket — guard so each socket + // schedules exactly ONE reconnect (otherwise attempts double every cycle and + // the process leaks sockets/timers until it OOMs). + let downHandled = false; const onDown = (why) => { + if (downHandled) return; + downHandled = true; if (state.xpConnected) log(`X-Plane disconnected (${why})`); state.xpConnected = false; broadcast({ type: 'status', xpConnected: false }); if (state.xpSocket === sock) state.xpSocket = null; + try { sock.removeAllListeners(); sock.terminate?.(); } catch {} setTimeout(connectXPlane, 3000); }; sock.on('close', () => onDown('close'));