aboutsummaryrefslogtreecommitdiffstats
path: root/idatui/rpcclient.py
diff options
context:
space:
mode:
Diffstat (limited to 'idatui/rpcclient.py')
-rw-r--r--idatui/rpcclient.py21
1 files changed, 19 insertions, 2 deletions
diff --git a/idatui/rpcclient.py b/idatui/rpcclient.py
index ecbdfed..e0a5602 100644
--- a/idatui/rpcclient.py
+++ b/idatui/rpcclient.py
@@ -25,11 +25,20 @@ from typing import Any
class RpcClient:
- def __init__(self, sock_path: str):
+ #: Default read timeout (s). Bounds any single call so a slow/hung server
+ #: (e.g. Hex-Rays grinding on an undecompilable function) can't block the
+ #: CLI forever. Override via ctor or the IDATUI_RPC_TIMEOUT env var.
+ DEFAULT_TIMEOUT = 90.0
+
+ def __init__(self, sock_path: str, timeout: float | None = None):
self.path = sock_path
self._sock: socket.socket | None = None
self._buf = b""
self._id = 0
+ if timeout is None:
+ env = os.environ.get("IDATUI_RPC_TIMEOUT")
+ timeout = float(env) if env else self.DEFAULT_TIMEOUT
+ self.timeout = timeout
def __enter__(self) -> "RpcClient":
self.connect()
@@ -41,6 +50,8 @@ class RpcClient:
def connect(self) -> None:
s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
s.connect(self.path)
+ if self.timeout and self.timeout > 0:
+ s.settimeout(self.timeout)
self._sock = s
def close(self) -> None:
@@ -54,7 +65,13 @@ class RpcClient:
req = {"id": self._id, "method": method, "params": params}
self._sock.sendall(json.dumps(req).encode() + b"\n")
while b"\n" not in self._buf:
- chunk = self._sock.recv(65536)
+ try:
+ chunk = self._sock.recv(65536)
+ except socket.timeout as e:
+ raise RpcError(
+ f"no response for {method!r} within {self.timeout}s "
+ f"(server busy or the op is hung, e.g. a failed decompile)"
+ ) from e
if not chunk:
raise ConnectionError("server closed the connection")
self._buf += chunk