app
KiCad - the KiCad Bridge
Public Made by Adomby adom
Reference implementation of the KiCad bridge: multi-instance Python server, forward path via kicad-cli, reverse path via in-process plugin. Most complex of the three bundled bridges.
← Commit history
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")