Repository navigation
fix(tui_gateway): route frontend-polled inline RPCs to pool to prevent WS disconnect under GIL pressure (#50005) - #54287
Closed
Zyxxx-xxxyZ wants to merge 1 commit into
Conversation
…t WS disconnect under GIL pressure (NousResearch#50005) The Desktop GUI WebSocket disconnects under GIL pressure from concurrent agent turns, causing false 'needs setup' and session content loading failures (NousResearch#50005). The root cause is that the WS read loop in handle_ws() processes requests sequentially — inline handlers (not in _LONG_HANDLERS) block the loop until handle_request() returns. Under GIL contention from multiple agent turns, even lightweight RPCs like session.list and pet.info can take seconds, causing frontend requests to time out (120s) and the WebSocket to disconnect. Fix: - Route session.list, session.info, pet.info, and process.list through _LONG_HANDLERS so dispatch() returns immediately (_pool.submit + return None) and the WS read loop is never blocked by frontend-polled RPCs - Increase the RPC thread pool from 4 to 8 workers to handle the additional pool-routed handlers under multi-agent load - Relax ws_ping_interval/timeout for loopback binds (30s/60s vs 20s/20s) so a recoverable GIL stall doesn't kill an otherwise-healthy local connection. Non-loopback binds keep 20/20 to stay under Cloudflare Tunnel's idle timeout. Complementary to NousResearch#51841 (WS transport layer: token coalescing + heartbeat watchdog). This PR fixes the dispatch layer (NousResearch#50005's inline-RPC blocking path), while NousResearch#51841 fixes the transport layer (WS ping + frame batching). Verified: 8 concurrent agent turns for 15+ minutes with zero WS disconnects and zero 'needs setup' occurrences. Stress tested with 12 GIL-heavy threads (99% intensity) + 3 concurrent WS clients: max RPC latency 472ms, 0 timeouts, 0 WS errors.
Related: this targets the same Desktop-GUI WS-disconnect class as #53497 (which moves |
teknium1
added a commit
that referenced
this pull request
Jun 30, 2026
1 task done
teknium1
added a commit
that referenced
this pull request
Jun 30, 2026
teknium1
added a commit
that referenced
this pull request
Jun 30, 2026
This was referenced Jun 30, 2026
dtera
pushed a commit
to dtera/hermes-agent
that referenced
this pull request
Jul 1, 2026
waefrebeorn
pushed a commit
to waefrebeorn/slermes
that referenced
this pull request
Jul 2, 2026
Jasper6439
pushed a commit
to Jasper6439/hermes-agent
that referenced
this pull request
Jul 5, 2026
habarmc1223-sudo
pushed a commit
to habarmc1223-sudo/hermes-agent-fluxmem
that referenced
this pull request
Jul 8, 2026
santhreal
pushed a commit
to santhreal/hermes-agent
that referenced
this pull request
Jul 13, 2026
Gravezzz
pushed a commit
to Gravezzz/hermes-agent
that referenced
this pull request
Jul 21, 2026
leewenjie
pushed a commit
to leewenjie/hermes-agent
that referenced
this pull request
Aug 7, 2026
melon-xf
added a commit
to melon-xf/hermes-agent
that referenced
this pull request
Sep 3, 2026
fsca8
pushed a commit
to fsca8/hermes-agent
that referenced
this pull request
Oct 6, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
The Desktop GUI WebSocket disconnects under GIL pressure from concurrent agent turns, causing false "needs setup" and session content loading failures (#50005). The root cause is that the WS read loop in
handle_ws()processes requests sequentially — inline handlers (NOT in_LONG_HANDLERS) block the loop untilhandle_request()returns. Under GIL contention from multiple agent turns, even lightweight RPCs likesession.listandpet.infocan take seconds, causing frontend requests to time out (120s) and the WebSocket to disconnect.Root Cause
For inline handlers,
dispatch()runshandle_request()synchronously — the loop blocks until it returns. For_LONG_HANDLERS,dispatch()does_pool.submit()+return None— microsecond return, loop stays free.Under GIL pressure from N concurrent agent turns (daemon threads doing JSON serialization of 96K+ token conversation histories), the event loop thread is starved. A
session.listquery that normally takes 2ms can take 5-10s. During that time, all subsequent WS requests queue behind it.When the frontend's 120s RPC timeout fires, the client closes the WebSocket, the server sees
WebSocketDisconnect, all sessions are detached, and the UI shows "needs setup" with session content unable to load. Restart fixes it (no agent turns running) until GIL pressure builds up again.Why existing fixes don't fully solve this
complete.path/complete.slashto poolsession.list,session.info,pet.info,process.liststill inlineFix
1. Route frontend-polled RPCs to
_LONG_HANDLERS(dispatch layer — the core fix)File:
tui_gateway/server.pyAdded
session.list,session.info,pet.info, andprocess.listto_LONG_HANDLERS. These are polled by the Desktop frontend and were running inline, blocking the WS read loop under GIL pressure. Nowdispatch()returns immediately for these RPCs, so the WS read loop is never blocked.2. Increase RPC thread pool 4 to 8 workers
File:
tui_gateway/server.pyWith 4 additional RPCs moved to the pool, the default 4 workers can be exhausted under multi-agent load. Increased to 8.
3. Relax
ws_ping_interval/ws_ping_timeoutfor loopback bindsFile:
hermes_cli/web_server.pyLoopback binds (Desktop case) now use 30s/60s instead of 20s/20s. A GIL-heavy agent turn can stall the event loop past 20s, and uvicorn's keepalive ping runs on that same starved loop — so a 20s timeout kills an otherwise-healthy local connection over a recoverable stall. Non-loopback binds keep 20/20 to stay under Cloudflare Tunnel's idle timeout.
This is identical to the ping fix in #51841 — the two PRs are complementary.
Complementary to #51841
This PR fixes the dispatch layer (#50005's inline-RPC blocking path), while #51841 fixes the transport layer (WS ping + token frame batching + heartbeat watchdog). Both are needed for full resilience:
Tests
File:
tests/tui_gateway/test_inline_rpc_gil_starvation.py(7 tests, all passing)session.list,session.info,pet.info,process.listis in_LONG_HANDLERSsession.list(simulating GIL contention) must not block a concurrent fast handlerpet.infomust not blockprompt.submit_rpc_pool_workers >= 8Full test suite: 82/82 pass (7 new + 75 existing
test_protocol.py).Validation
Real-world stress test: 8 concurrent agent turns for 15+ minutes with zero WS disconnects and zero "needs setup" occurrences.
Adversarial stress test with direct GIL pressure simulation (12 threads at 99% intensity + 3 concurrent WS clients):
Total: 657 RPCs, 0 failures, 0 timeouts, 0 WS disconnects.
For comparison, the same test against unpatched
mainproducesWebSocketDisconnectwithin 60s under 6 GIL threads.What this does NOT touch
Refs #50005