stdio client never closes the server's stdin, so every client dispose burns the full ShutdownTimeout (5s by default)
Dieses Issue hat noch niemand übernommen.
Bewertung
- Schwierigkeit
- 2/5
- Geschätzter Aufwand
- 1-3 Stunden
- Anfängerfreundlichkeit
- 84/100
Rechercherichtung
Beginne in StdioClientSessionTransport.CleanupAsync und vergleiche den Prozessbehandlungscode in StdioClientTransport.cs und StdioClientSessionTransport.cs mit der EOF-Behandlung von StreamServerTransport. Reproduziere das Problem, indem du einen stdio-Client freigibst und dies mit dem standardmäßigen ShutdownTimeout zeitlich abstimmst. Erledigt bedeutet, dass stdin vor dem Warten geschlossen wird, der Server umgehend beendet wird und KillTree nur als Fallback verbleibt.
Vom Indexierungsmodell aus dem Issue-Text verfasst.
Beschreibung
Summary
StdioClientSessionTransport.CleanupAsync waits up to ShutdownTimeout for the server process to exit on its own, and only then kills the process tree. But nothing ever asks the server to exit: the client never closes the child's standard input, which the specification defines as the graceful-shutdown signal. So for any server that is alive and well behaved, that wait always runs to the full timeout — 5 seconds by default — and the process is force-killed afterwards regardless.
Hosts that create and dispose a client per tool call pay this on every single call.
Version checked: v1.4.0 (same code on main at the time of writing).
What the spec requires
From basic/transports/stdio — Shutdown:
- Closing the input stream to the child process (the server).
- Waiting for the server to exit.
- If the server does not exit within a reasonable time, forcibly terminating the process…
and:
Servers should exit promptly when their standard input is closed or reads return end-of-file […] the primary graceful-shutdown signal and the only portable one.
Step 1 is missing in the client.
What the SDK does
CleanupAsync order of operations:
WaitForProcessExitAsync()— bounded by_options.ShutdownTimeout- detach the stderr handler
StdioClientTransport.DisposeProcess(..., _options.ShutdownTimeout)
WaitForProcessExitAsync on .NET:
using var timeoutCts = new CancellationTokenSource(_options.ShutdownTimeout);
await _process.WaitForExitAsync(timeoutCts.Token).ConfigureAwait(false);
DisposeProcess:
processRunning = processRunning && !HasExited(process);
if (processRunning)
{
process.KillTree(shutdownTimeout);
}
There is no StandardInput.Close() / .Dispose() anywhere in StdioClientTransport.cs or StdioClientSessionTransport.cs. The default:
/// The amount of time to wait for the server to shut down gracefully. The default is 5 seconds.
public TimeSpan ShutdownTimeout { get; set; } = TimeSpan.FromSeconds(5);
The doc comment says "wait for the server to shut down gracefully" — but the server was never told to, so the wait is guaranteed to expire.
The server side already handles it
StreamServerTransport's read loop, same version:
if (line is null)
{
LogTransportEndOfStream(Name);
break;
}
EOF breaks the loop, SetDisconnected completes the transport, the process exits. So closing stdin would make servers built on this SDK exit in milliseconds — the fix needs no change on the server side.
Impact
Measured on a host that creates and disposes a stdio client per tool call: 6.7–7.0s wall clock per tool call, of which ~5s is this wait. One of the measured calls was a query that returned an empty array — no data, no work — and still took 6.95s. Eighteen tool cycles across four configurations, all in the same band.
Two consequences, not one:
- Latency: a fixed ~5s added to every client dispose.
- The server is always force-killed. Because the wait can only expire,
KillTreealways runs, so the server never executes its own shutdown path — no flush, no cleanup, no chance to release resources. LoweringShutdownTimeoutas a workaround makes the kill arrive sooner, so it trades latency for a harder kill rather than fixing it.
Suggested fix
Close the child's standard input at the start of CleanupAsync, before WaitForProcessExitAsync. The subsequent wait then ends when the server actually exits, usually in milliseconds, and KillTree degrades to the safety net the spec intends. Optionally escalate SIGTERM before SIGKILL on POSIX, as the spec describes.
Repro
Start any stdio server built on this SDK, issue one tool call, dispose the client, and time the dispose. It returns after ShutdownTimeout, independently of what the server does. Setting ShutdownTimeout to a small value makes the dispose proportionally faster, which confirms the wait is the whole cost.
- Vorherrschende Sprache
- C#
- Sterne
- 4.5k
- Forks
- 814
- Ø Merge
- 9 T. 19 Std.
- Gemergte PRs (30 T.)
- 4
Beitragsleitfaden
Erste Schritte
- Lesen Sie das ganze Issue und danach den Beitragsleitfaden des Projekts.
- Schreiben Sie ins Issue, dass Sie es übernehmen — das erspart doppelte Arbeit.
- Forken Sie das Repository und arbeiten Sie in einem Branch.
- Öffnen Sie einen Pull Request, der die Issue-Nummer nennt.
Mehr aus modelcontextprotocol/csharp-sdk
-
Schwierigkeit 2/5 1-3 Stunden Anfängerfreundlichkeit 78/100
modelcontextprotocol/csharp-sdk#1867 ·
-
Schwierigkeit 1/5 Unter einer Stunde Anfängerfreundlichkeit 88/100
modelcontextprotocol/csharp-sdk#1840 · 1 Kommentar ·
-
enhancement needs confirmation
Schwierigkeit 2/5 1-3 Stunden Anfängerfreundlichkeit 64/100
modelcontextprotocol/csharp-sdk#678 · 1 Kommentar ·
-
enhancement needs confirmation P3 ready for work
Schwierigkeit 2/5 1-3 Stunden Anfängerfreundlichkeit 68/100
modelcontextprotocol/csharp-sdk#515 · 6 Kommentare · 3 Reaktionen ·
-
Schwierigkeit 5/5 Über eine Woche Anfängerfreundlichkeit 28/100
modelcontextprotocol/csharp-sdk#1881 ·
Alle Issues in modelcontextprotocol/csharp-sdk
Ähnliche Issues
-
Schwierigkeit 2/5 1-3 Stunden Anfängerfreundlichkeit 86/100
-
:watch: Not Triaged 11.0 fundamentals/subsvc
Schwierigkeit 2/5 1-3 Stunden Anfängerfreundlichkeit 92/100
dotnet/AspNetCore.Docs#37699 ·
-
Schwierigkeit 2/5 1-3 Stunden Anfängerfreundlichkeit 72/100
SubtitleEdit/subtitleedit#15108 · 1 Kommentar ·
-
area/docs-content Bug pulumi/docs
Schwierigkeit 1/5 1-3 Stunden Anfängerfreundlichkeit 94/100
-
agentic-workflows untriaged
Schwierigkeit 2/5 1-3 Stunden Anfängerfreundlichkeit 76/100