Files
hermes-agent/tui_gateway/entry.py

39 lines
908 B
Python
Raw Normal View History

2026-04-02 19:06:42 -05:00
import json
2026-04-03 19:52:50 -05:00
import signal
2026-04-02 19:06:42 -05:00
import sys
fix(tui-gateway): dispatch slow RPC handlers on a thread pool (#12546) The stdin-read loop in entry.py calls handle_request() inline, so the five handlers that can block for seconds to minutes (slash.exec, cli.exec, shell.exec, session.resume, session.branch) freeze the dispatcher. While one is running, any inbound RPC — notably approval.respond and session.interrupt — sits unread in the pipe buffer and lands only after the slow handler returns. Route only those five onto a small ThreadPoolExecutor; every other handler stays on the main thread so the fast-path ordering is unchanged and the audit surface stays small. write_json is already _stdout_lock-guarded, so concurrent response writes are safe. Pool size defaults to 4 (overridable via HERMES_TUI_RPC_POOL_WORKERS). - add _LONG_HANDLERS set + ThreadPoolExecutor + atexit shutdown - new dispatch(req) function: pool for long handlers, inline for rest - _run_and_emit wraps pool work in a try/except so a misbehaving handler still surfaces as a JSON-RPC error instead of silently dying in a worker - entry.py swaps handle_request → dispatch - 5 new tests: sync path still inline, long handlers emit via stdout, fast handler not blocked behind slow one, handler exceptions map to error responses, non-long methods always take the sync path Manual repro confirms the fix: shell.exec(sleep 3) + terminal.resize sent back-to-back now returns the resize response at t=0s while the sleep finishes independently at t=3s. Before, both landed together at t=3s. Fixes #12546.
2026-04-19 07:47:15 -05:00
from tui_gateway.server import dispatch, resolve_skin, write_json
2026-04-02 19:06:42 -05:00
2026-04-03 19:52:50 -05:00
signal.signal(signal.SIGPIPE, signal.SIG_DFL)
2026-04-13 21:20:55 -05:00
signal.signal(signal.SIGINT, signal.SIG_IGN)
2026-04-03 19:52:50 -05:00
2026-04-02 19:06:42 -05:00
def main():
2026-04-06 18:38:13 -05:00
if not write_json({
2026-04-02 19:06:42 -05:00
"jsonrpc": "2.0",
"method": "event",
"params": {"type": "gateway.ready", "payload": {"skin": resolve_skin()}},
2026-04-06 18:38:13 -05:00
}):
sys.exit(0)
2026-04-02 19:06:42 -05:00
for raw in sys.stdin:
line = raw.strip()
if not line:
continue
try:
req = json.loads(line)
except json.JSONDecodeError:
2026-04-06 18:38:13 -05:00
if not write_json({"jsonrpc": "2.0", "error": {"code": -32700, "message": "parse error"}, "id": None}):
sys.exit(0)
2026-04-02 19:06:42 -05:00
continue
fix(tui-gateway): dispatch slow RPC handlers on a thread pool (#12546) The stdin-read loop in entry.py calls handle_request() inline, so the five handlers that can block for seconds to minutes (slash.exec, cli.exec, shell.exec, session.resume, session.branch) freeze the dispatcher. While one is running, any inbound RPC — notably approval.respond and session.interrupt — sits unread in the pipe buffer and lands only after the slow handler returns. Route only those five onto a small ThreadPoolExecutor; every other handler stays on the main thread so the fast-path ordering is unchanged and the audit surface stays small. write_json is already _stdout_lock-guarded, so concurrent response writes are safe. Pool size defaults to 4 (overridable via HERMES_TUI_RPC_POOL_WORKERS). - add _LONG_HANDLERS set + ThreadPoolExecutor + atexit shutdown - new dispatch(req) function: pool for long handlers, inline for rest - _run_and_emit wraps pool work in a try/except so a misbehaving handler still surfaces as a JSON-RPC error instead of silently dying in a worker - entry.py swaps handle_request → dispatch - 5 new tests: sync path still inline, long handlers emit via stdout, fast handler not blocked behind slow one, handler exceptions map to error responses, non-long methods always take the sync path Manual repro confirms the fix: shell.exec(sleep 3) + terminal.resize sent back-to-back now returns the resize response at t=0s while the sleep finishes independently at t=3s. Before, both landed together at t=3s. Fixes #12546.
2026-04-19 07:47:15 -05:00
resp = dispatch(req)
2026-04-02 19:06:42 -05:00
if resp is not None:
2026-04-06 18:38:13 -05:00
if not write_json(resp):
sys.exit(0)
2026-04-02 19:06:42 -05:00
if __name__ == "__main__":
main()