← Commit history

v0.9.6

John Lauer ·9cfe50e4c9 ·3mo ago ·parent c180103
3 files changed +31−4
BRIDGE_VERSION+1−1
@@ -1 +1 @@-0.9.5+0.9.6
bridge.json+1−1
@@ -2,7 +2,7 @@   "manifest_version": 1,   "name": "kicad",   "displayName": "KiCad EDA",-  "version": "0.9.5",+  "version": "0.9.6",   "description": "Reverse bridge for KiCad \u2014 board/schematic introspection, lint via kicad-cli, plugin install, multi-instance probe, in-process DRC.",   "homepage": "https://github.com/adom-inc/adom-desktop",   "author": "Adom Inc.",
handlers/bridge_client.py+29−2
@@ -77,7 +77,7 @@ def _read_discovery(path: Path) -> Optional[dict]:         return None  -def _probe(port: int, timeout: float = 2.0) -> Optional[dict]:+def _probe(port: int, timeout: float = 1.0) -> Optional[dict]:     """GET http://127.0.0.1:<port>/status. Returns None if unreachable."""     try:         with urllib.request.urlopen(f"http://127.0.0.1:{port}/status", timeout=timeout) as resp:@@ -86,6 +86,32 @@ def _probe(port: int, timeout: float = 2.0) -> Optional[dict]:         return None  +# Per-call probe cache + deadline so handle_bridge_status can never hang no+# matter how many discovery files have accumulated. Many dead python processes+# all register the SAME reverse-plugin port, so probing each FILE is wasteful+# (and a single slow/half-open port × N files = a multi-minute hang — observed+# 2026-06-25 with 288 stale files). We probe each UNIQUE port at most once, and+# stop probing entirely once a wall-clock budget is exhausted.+_PROBE_BUDGET_S = 3.0+++def _make_port_prober():+    cache: dict[int, Optional[dict]] = {}+    deadline = time.monotonic() + _PROBE_BUDGET_S++    def probe_port(port: int) -> Optional[dict]:+        if port in cache:+            return cache[port]+        if time.monotonic() > deadline:+            cache[port] = None  # over budget — treat as not-alive without probing+            return None+        result = _probe(port)+        cache[port] = result+        return result++    return probe_port++ def call(port: int, method: str, params: Optional[dict] = None, timeout: float = 10.0) -> dict:     """Make a POST /rpc call. Returns the parsed JSON-RPC response."""     body = json.dumps({@@ -132,6 +158,7 @@ def handle_bridge_status(kicad_info: dict, args: dict) -> dict:     filter_pid = args.get("pid")     prune_stale = bool(args.get("pruneStale")) +    probe_port = _make_port_prober()  # unique-port cache + wall-clock budget     plugins = []     for path in _discovery_files():         meta = _read_discovery(path)@@ -158,7 +185,7 @@ def handle_bridge_status(kicad_info: dict, args: dict) -> dict:             "discoveryFile": str(path),             "alive": False,         }-        status = _probe(meta.get("port", 0)) if meta.get("port") else None+        status = probe_port(meta.get("port", 0)) if meta.get("port") else None         if status:             entry["alive"] = True             entry["uptimeMs"] = status.get("uptimeMs")