Skip to content

Commit 3686f6f

Browse files
Copilotstephentoub
andcommitted
Fix SendRequestAsync hang when background SSE stream delivers response before foreground transport send completes
Replace `await SendToRelatedTransportAsync(request, cancellationToken)` with `Task.WhenAny(sendTask, tcs.Task)` so that if the response TCS completes first (via a concurrent background channel such as the background GET SSE stream in Streamable HTTP), execution proceeds immediately without waiting for the foreground send. If the send completes first (normal case), it is awaited directly, preserving the original behavior. The still-running send task's exceptions are observed via a fire-and-forget ContinueWith(OnlyOnFaulted) to prevent unobserved task exceptions. Co-authored-by: stephentoub <2642209+stephentoub@users.noreply.github.com>
1 parent 1d25d9b commit 3686f6f

File tree

1 file changed

+20
-1
lines changed

1 file changed

+20
-1
lines changed

src/ModelContextProtocol.Core/McpSessionHandler.cs

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -536,7 +536,26 @@ public async Task<JsonRpcResponse> SendRequestAsync(JsonRpcRequest request, Canc
536536
LogSendingRequest(EndpointName, request.Method);
537537
}
538538

539-
await SendToRelatedTransportAsync(request, cancellationToken).ConfigureAwait(false);
539+
// Wait for either the transport send to complete or for the response to arrive via a
540+
// concurrent channel (e.g. the background GET SSE stream in Streamable HTTP). Without
541+
// this, the foreground transport send could block indefinitely waiting for a response
542+
// that was already delivered via a different stream.
543+
Task sendTask = SendToRelatedTransportAsync(request, cancellationToken);
544+
if (sendTask != await Task.WhenAny(sendTask, tcs.Task).ConfigureAwait(false))
545+
{
546+
// The response arrived via a concurrent channel before the transport send completed.
547+
// Observe any exception from the still-running send to prevent unobserved task exceptions.
548+
_ = sendTask.ContinueWith(
549+
static (t, _) => _ = t.Exception,
550+
null,
551+
CancellationToken.None,
552+
TaskContinuationOptions.OnlyOnFaulted,
553+
TaskScheduler.Default);
554+
}
555+
else
556+
{
557+
await sendTask.ConfigureAwait(false);
558+
}
540559

541560
// Now that the request has been sent, register for cancellation. If we registered before,
542561
// a cancellation request could arrive before the server knew about that request ID, in which

0 commit comments

Comments
 (0)