Skip to content

Commit bc10e41

Browse files
ericstjCopilot
andcommitted
Add timeout to WaitForExitAsync in GetUnexpectedExitExceptionAsync
When CleanupAsync is entered from ReadMessagesAsync (stdout EOF), the cancellation token is _shutdownCts.Token which hasn't been canceled yet (base.CleanupAsync runs later). If stderr EOF hasn't been delivered by the threadpool yet, WaitForExitAsync hangs indefinitely. Use a linked CTS with ShutdownTimeout to bound the wait. The process is already dead; we're just draining stderr pipe buffers. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 8ad61e5 commit bc10e41

1 file changed

Lines changed: 7 additions & 1 deletion

File tree

src/ModelContextProtocol.Core/Client/StdioClientSessionTransport.cs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -99,8 +99,14 @@ protected override async ValueTask CleanupAsync(Exception? error = null, Cancell
9999
try
100100
{
101101
// The process has exited, but we still need to ensure stderr has been flushed.
102+
// Use a bounded wait: the process is already dead, we're just draining pipe
103+
// buffers. If the caller's token is never canceled (e.g. _shutdownCts hasn't
104+
// been canceled yet), an unbounded wait here can hang indefinitely when the
105+
// threadpool is slow to deliver the stderr EOF callback.
102106
#if NET
103-
await _process.WaitForExitAsync(cancellationToken).ConfigureAwait(false);
107+
using var timeoutCts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
108+
timeoutCts.CancelAfter(_options.ShutdownTimeout);
109+
await _process.WaitForExitAsync(timeoutCts.Token).ConfigureAwait(false);
104110
#else
105111
_process.WaitForExit((int)_options.ShutdownTimeout.TotalMilliseconds);
106112
#endif

0 commit comments

Comments
 (0)