From b96489a3cfb191490ded97bac6f5d4733a055330 Mon Sep 17 00:00:00 2001 From: 4 Bytes Robby Date: Sun, 14 Jun 2026 18:18:39 +0200 Subject: [PATCH] fix: spawn lock + dead port reconnect --- src/bus-client.ts | 18 ++++++++++++++++++ src/bus-tui.ts | 9 +++++++++ 2 files changed, 27 insertions(+) diff --git a/src/bus-client.ts b/src/bus-client.ts index 9bc122e..4c4cd77 100644 --- a/src/bus-client.ts +++ b/src/bus-client.ts @@ -7,6 +7,10 @@ import type { BusEnvelope, BusHealth } from "./types.js"; const BASE_URL = "http://127.0.0.1"; +// Spawn lock — prevents concurrent connect() calls from spawning duplicate +// bus processes while a start is already in flight. Released on settle. +let spawnLock: Promise | null = null; + /** * Server-side client for the plugin bus. * Plugins import BusClient, publish messages via HTTP POST. @@ -67,8 +71,22 @@ export class BusClient { /** * Spawn the bus binary and read the port from stdout. + * Guarded by a module-level spawn lock so concurrent connect() calls share + * the same in-flight spawn instead of forking multiple bus processes. */ private static async startBus(timeoutMs: number): Promise { + if (spawnLock) return spawnLock; + spawnLock = BusClient.spawnBus(timeoutMs).finally(() => { + spawnLock = null; + }); + return spawnLock; + } + + /** + * Inner spawn implementation — wraps the child process in a Promise. + * Kept separate from startBus() so the spawn lock can be released cleanly. + */ + private static spawnBus(timeoutMs: number): Promise { const binary = BusClient.findBusBinary(); return new Promise((resolve, reject) => { const child = spawn(binary, [], { diff --git a/src/bus-tui.ts b/src/bus-tui.ts index f612f55..b590aa8 100644 --- a/src/bus-tui.ts +++ b/src/bus-tui.ts @@ -164,6 +164,15 @@ export class BusTui { private scheduleReconnect(): void { if (this.closed) return; this.reconnectTimer = setTimeout(async () => { + // Re-read the port file — the bus may have restarted on a new port + // (e.g. after a crash) and the cached port would now be dead. + this.port = 0; + try { + const port = await discoverPort(2000); + if (port > 0) this.port = port; + } catch { + // discoverPort failed — keep port=0, open() will report and retry + } try { await this.open(); } catch {