aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--docs/PAGING_FINDINGS.md22
-rw-r--r--idatui/app.py11
-rwxr-xr-xspawn.sh5
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