From 4a83deb7bc2dcfed4696cd1bf2542a947ad7c4fb Mon Sep 17 00:00:00 2001 From: blasty Date: Thu, 9 Jul 2026 15:55:38 +0200 Subject: stop piling up idalib workers; raise max-workers headroom Root cause of 'Maximum idalib worker count reached': the app bumped idle_ttl to ~never on every open, so sessions never freed and hit the default cap of 4. - app: drop the immortal bump_idle_ttl(1e9); rely on the KeepAlive heartbeat (120s) to keep the running session warm, and open with a moderate idle_ttl (1800s) so the worker self-exits and frees its slot after the TUI closes. - spawn.sh: set IDA_MCP_MAX_WORKERS (default 8) for headroom. - doc: supervisor prunes unreachable workers before counting, so killing an idle worker frees a slot on the next open. (Verified separately: big/truncated decompile results propagate renames fine via the earlier force_recompile fix.) --- docs/PAGING_FINDINGS.md | 22 +++++++++++++++++++--- idatui/app.py | 11 ++++++----- spawn.sh | 5 ++++- 3 files changed, 29 insertions(+), 9 deletions(-) diff --git a/docs/PAGING_FINDINGS.md b/docs/PAGING_FINDINGS.md index 3d3ec1a..d2187ae 100644 --- a/docs/PAGING_FINDINGS.md +++ b/docs/PAGING_FINDINGS.md @@ -120,9 +120,25 @@ found"`). Belt-and-suspenders, and it also covers *adopted* sessions whose path we don't control / don't want to re-open. -Recommended TUI policy: `bump_idle_ttl()` on open **and** run a `KeepAlive` as a -safety net. "Worker not reachable" then only occurs on a real crash, which the -app handles by re-`idb_open`. +Recommended TUI policy: run a `KeepAlive` heartbeat (interval < TTL) to keep the +*running* session warm, and use a **moderate** idle TTL (not ‘never’) so the +worker is reclaimed after the TUI exits. "Worker not reachable" then only occurs +on a real crash, which the app handles by re-`idb_open`. + +## Supervisor worker cap (`max_workers`, default 4) + +The supervisor allows at most `max_workers` concurrently-open binaries; a 5th +`idb_open` fails with "Maximum idalib worker count reached". Before counting it +**prunes unreachable workers**, so a dead/killed worker frees its slot on the +next open. Two consequences drove design choices: + +* Do **not** make sessions immortal (`bump_idle_ttl(1e9)`) — they pile up to the + cap and never free. Instead: `KeepAlive` while running + a moderate TTL + (idatui uses 1800s on `--open`) so idle sessions self-exit after the TUI + closes and free their slot. +* `spawn.sh` sets `IDA_MCP_MAX_WORKERS` (default raised to 8) for headroom; to + free a stuck slot immediately, kill the idle worker process (it gets pruned on + the next open). ## Writable path requirement (operational) diff --git a/idatui/app.py b/idatui/app.py index 3dfc553..7dc59b4 100644 --- a/idatui/app.py +++ b/idatui/app.py @@ -1040,8 +1040,11 @@ class IdaTui(App): self.app.call_from_thread(self._status, f"opening {self._open_path}…") import os path = os.path.abspath(os.path.expanduser(self._open_path)) + # Moderate idle TTL: the keepalive heartbeat (below) keeps the + # session alive while the TUI runs; once it exits the worker + # idles out and frees its slot (avoids piling up to max-workers). res = client.call("idb_open", input_path=path, - idle_ttl_sec=1_000_000_000, timeout=1800.0) + idle_ttl_sec=1800, timeout=1800.0) if not (isinstance(res, dict) and res.get("success")): err = res.get("error") if isinstance(res, dict) else res self.app.call_from_thread(self._status, f"open failed: {err}") @@ -1052,10 +1055,8 @@ class IdaTui(App): health = client.health() module = health.get("module", "?") if self._do_keepalive: - try: - client.bump_idle_ttl() - except Exception: - pass + # Keep the session warm while we run; don't make it immortal, so + # it's reclaimed after the TUI closes. self._ka = client.keepalive(interval=120.0).start() program = Program(client) except Exception as e: # noqa: BLE001 diff --git a/spawn.sh b/spawn.sh index 8bc0b6f..4b0c63c 100755 --- a/spawn.sh +++ b/spawn.sh @@ -1,2 +1,5 @@ #!/bin/sh -uv run idalib-mcp --host 127.0.0.1 --port 8745 $(pwd)/bin/ls +# Start the ida-pro-mcp supervisor. IDA_MCP_MAX_WORKERS raises the ceiling on +# concurrently-open binaries (default 4); idle workers self-exit and free slots. +IDA_MCP_MAX_WORKERS="${IDA_MCP_MAX_WORKERS:-8}" \ + uv run idalib-mcp --host 127.0.0.1 --port 8745 $(pwd)/bin/ls -- cgit v1.3.1-sl0p