fix: observe endpointstream shutdown in SSE closed() - #68
Merged
Conversation
stop() shuts down live client sockets but does not set iostream failbit until the next write. MCP handlers that only poll session.closed() (including the inbound-queue tests) never exited, so wait_for_handlers() deadlocked after drain-on-listen-exit. Latch shut_down on shutdown() and check it from sse::session::closed(). Co-authored-by: Kaius Ruokonen <ruoka@users.noreply.github.com>
ruoka
force-pushed
the
cursor/critical-bug-management-c7b0
branch
from
August 2, 2026 01:36
f16cdd5 to
ff2d785
Compare
ruoka
marked this pull request as ready for review
August 2, 2026 01:36
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.
Bug and impact
After drain-on-listen-exit,
http::server::stop()callsendpointstream::shutdown()on live clients and thenwait_for_handlers().shutdown()wakes blocking I/O but does not set iostreamfailbituntil the next write.http::sse::session::closed()only checkedm_closed || !stream.good(). Custom SSE/MCP handlers that only pollclosed()(including the MCP inbound-queue tests that hold a session open withwhile (!sess.closed())) never observestop(), solisten()/ join hang forever.Production
mcp::server::run_sessionalready probes with heartbeats and is less affected; any attach handler that only pollsclosed()deadlocks shutdown.Root cause
stop()→endpointstream::shutdown()does not surface as stream failure until a write. Session lifecycle state (m_state->closed) is only set inerase_sessionafteron_sessionreturns, so a poll-only handler has no signal.Fix
shut_downonendpointbuf_base::shutdown()endpointstream::shut_down()sse::session::closed()observe the latch (viadynamic_casttoendpointstream)Validation
closed after endpointstream shutdown latchshutdown latch|MCP SSE|SSE session(includes inbound-queue 503 scenarios that previously hung onstop()): all passedNote
Medium Risk
Touches connection lifecycle and SSE shutdown visibility; limited scope but affects server stop and MCP/SSE handler exit paths.
Overview
Fixes a shutdown deadlock where
http::server::stop()shuts down clientendpointstreams but handlers that only pollhttp::sse::session::closed()never saw closure until a write failed.endpointbuf_base::shutdown()now sets an atomicshut_downlatch (in addition to waking the socket).endpointstream::shut_down()exposes that state (including afterclose()).sse::session::closed()treats an underlyingendpointstreamas closed when the latch is set, viadynamic_cast.Adds a regression test: after
stream.shutdown(),session.closed()is true while iostreamgood()may still be true.Reviewed by Cursor Bugbot for commit ff2d785. Bugbot is set up for automated code reviews on this repo. Configure here.