tbench2 env server: enable idle-session reaper to reclaim leaked slots - #1000
Conversation
The /mcp WebSocket handler releases its session slot and Docker container only in a finally block, which runs solely on a clean socket close. Half-open or uncleanly-dropped connections never raise WebSocketDisconnect, so receive_text() blocks forever, finally never runs, and the slot + container leak. Over ~20-24h these accumulate until all MAX_CONCURRENT_ENVS slots are held and new episodes deadlock on CAPACITY_REACHED, requiring a manual env-server reset. create_app was called with a bare max_concurrent_envs, which the core server maps to ConcurrencyConfig(session_timeout=None) -> the idle-session reaper is disabled. Pass an explicit ConcurrencyConfig with session_timeout instead (SESSION_IDLE_TIMEOUT_S env var, default 3600s) so _reap_idle_sessions runs and evicts stale sessions, reclaiming their containers automatically.
|
The docs for this PR live here. All of your documentation changes will be reflected on that endpoint. The docs are available until 30 days after the last update. |
caiotheodoro
left a comment
There was a problem hiding this comment.
src/openenv/core/env_server/http_server.py:1168 — risk: this is the first env in the repo to turn the idle reaper on (grep shows no other env passes session_timeout), and it surfaces a gap in the shared reaper path rather than one in this diff. mcp_websocket_endpoint closes over session_env as a local and never rereads self._sessions. _destroy_session (line 441) pops the session and calls env.close() — for tbench2 that stops+removes the Docker container — without touching the websocket itself. A client that's slow rather than dead, past the 1hr SESSION_IDLE_TIMEOUT_S, gets its container killed mid-connection; its next message still runs mcp_handler against the now-closed session_env and comes back as an opaque INTERNAL_ERROR instead of a clean timeout signal. Worth closing the websocket (or flagging the session so the handler bails cleanly) from _destroy_session before this ships, since it's the first PR to make that path reachable.
There was a problem hiding this comment.
Stale comment
Still not merge-ready despite the earlier human approval on
adc5d447.
- Branch is 117 commits behind
main(mergeStateStatus: BEHIND). Refresh and re-run exact-head CI before any merge attempt.- The follow-up comment from
caiotheodorois a real product concern: enabling the idle reaper for the first time can destroy a live Docker-backed session while the WebSocket is still open, turning a slow client into an opaque INTERNAL_ERROR. Prefer closing/signaling the websocket from session destroy, or otherwise fail cleanly, before this becomes the first production caller of that path.- Automation cannot update the PR branch (403). Author/maintainer refresh needed.
Sent by Cursor Automation: Release
There was a problem hiding this comment.
Stale comment
Refresh review vs main
b12450f2(Ben ask):Verdict: LGTM after rebase. Exact head
adc5d447is ~129 behind main; historical CI was green andnblintaoAPPROVED. Diff correctly enables core idle reaper viaConcurrencyConfig(session_timeout=SESSION_IDLE_TIMEOUT_S)(default 3600) for tbench2 capacity leaks from half-open sockets. Env-local, low risk. Please rebase onto current main and re-run CI, then this is squash-merge ready.Sent by Cursor Automation: Release
|
Thanks |
There was a problem hiding this comment.
Refresh review (Wed 2026-09-16)
Still BEHIND main (adc5d447). Diff is still only enabling session_timeout in envs/tbench2_env/server/app.py.
Prior blocker stands: core _destroy_session does not close/signal the MCP WebSocket. Enabling the reaper makes tbench2 the first production caller of that broken path → opaque INTERNAL_ERROR / hung receive for slow clients. #1169’s /ws?session_id= attach exemptions do not protect ordinary /mcp WS sessions.
Also: please remove the copy of this change that was folded into #1172 until the core signaling fix exists.
Needs author/core fix — not mergeable after refresh alone.
Sent by Cursor Automation: Release


What
The
tbench2_envserver now enables the core idle-session reaper by passing an explicitConcurrencyConfig(max_concurrent_envs=..., session_timeout=...)tocreate_app, instead of the baremax_concurrent_envs=argument. A newSESSION_IDLE_TIMEOUT_Senv var (default3600) controls the reap interval.Why
The
/mcpWebSocket handler releases its session slot and Docker container only in afinallyblock, which runs solely on a clean socket close. Half-open or uncleanly-dropped connections never raiseWebSocketDisconnect, soreceive_text()blocks forever, thefinallynever runs, and the session slot + its Docker container leak.Under sustained load these leaks accumulate over roughly a day until all
MAX_CONCURRENT_ENVSslots are permanently held. New episodes then deadlock onCAPACITY_REACHEDand the only recovery is a manual env-server restart.Passing a bare
max_concurrent_envsmaps toConcurrencyConfig(session_timeout=None)in the core server, which disables_reap_idle_sessions. Supplying an explicitsession_timeoutre-enables the reaper so stale sessions are evicted and their containers reclaimed automatically, without operator intervention.Notes
SESSION_IDLE_TIMEOUT_S=3600preserves existing behavior for short-lived sessions while bounding leaked slots.envs/tbench2_env/server/app.py; no change to the core server or to the on-the-wire protocol.Note
Low Risk
Single-file server bootstrap change with a conservative default timeout; no protocol or core server edits.
Overview
The tbench2_env server now passes an explicit
ConcurrencyConfigtocreate_app(withmax_concurrent_envsandsession_timeout) instead of onlymax_concurrent_envs, which turns on the core idle-session reaper that was previously off whensession_timeoutwas unset.A new
SESSION_IDLE_TIMEOUT_Senv var (default 3600) sets how long a session can sit idle before it is reaped so its WebSocket slot and Docker container are released. That targets leaks from half-open/mcpWebSocket connections that never hit a clean disconnect, which could otherwise hold allMAX_CONCURRENT_ENVSslots until a manual restart.Reviewed by Cursor Bugbot for commit adc5d44. Bugbot is set up for automated code reviews on this repo. Configure here.