Disable server/discover probe timeout in in-memory tests to fix flaky Windows Debug CI hang (#1701)#1702
Open
PranavSenthilnathan wants to merge 1 commit into
Conversation
PranavSenthilnathan
marked this pull request as draft
July 14, 2026 01:21
halter73
reviewed
Jul 14, 2026
On slow Windows Debug CI, the default 5s DiscoverProbeTimeout can be spuriously exceeded when a July2026-capable client probes server/discover over the in-memory pipe. The client then falls back to the initialize handshake and negotiates an older protocol, dropping tasks-extension negotiation. For TaskCancellationIntegrationTests, whose tool runs Task.Delay(Timeout.Infinite), the fallback makes the server run the tool inline instead of as a background task, so the tools/call never returns and the test host hangs until the blame timeout. Force DiscoverProbeTimeout = Timeout.InfiniteTimeSpan in ClientServerTestBase.CreateMcpClientForServer. The in-memory pipe server always answers server/discover immediately, so the probe never legitimately needs to time out. This is applied even when a caller supplies its own options so no test can reintroduce the flake. Tests that intentionally exercise the probe-timeout fallback (July2026ProtocolFallbackTests) build their own transport and bypass this helper, so they are unaffected. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
PranavSenthilnathan
force-pushed
the
pranavsenthilnathan/fix-task-cancellation-test-hang
branch
from
July 16, 2026 00:47
42a99f0 to
0726434
Compare
PranavSenthilnathan
marked this pull request as ready for review
July 16, 2026 01:09
halter73
reviewed
Jul 16, 2026
|
|
||
| // Disable the server/discover probe timeout to avoid CI slowness spuriously tripping it (issue #1701). | ||
| // Tests that need a specific probe timeout should create their own client instead of using this helper. | ||
| clientOptions.DiscoverProbeTimeout = Timeout.InfiniteTimeSpan; |
Contributor
There was a problem hiding this comment.
Would the 60 second TestConstant.DefaultTimeout timeout be better?
Suggested change
| clientOptions.DiscoverProbeTimeout = Timeout.InfiniteTimeSpan; | |
| clientOptions.DiscoverProbeTimeout = TestConstant.DefaultTimeout; |
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.
Summary
Fixes the flaky Windows Debug CI test-host hang tracked in #1701, which surfaced most often as
TaskCancellationIntegrationTests.TaskTool_CancellationToken_GetTaskShowsWorkingBeforeCancel.The fix is a one-line, test-only change:
ClientServerTestBase.CreateMcpClientForServernow forcesDiscoverProbeTimeout = Timeout.InfiniteTimeSpanfor the in-memory client. The in-memory pipe server always answersserver/discoverinstantly, so the probe never legitimately needs a timeout — but on a slow/overloaded CI agent the default 5s probe timeout could be spuriously exceeded, triggering a protocol-fallback cascade that ends in a hang (details below).Root cause
A July2026-capable client (the default) does not use the initialize handshake. Instead it calls
server/discoverto learn the server's capabilities, and only falls back to the legacyinitializehandshake if the probe fails or times out. That probe is bounded byMcpClientOptions.DiscoverProbeTimeout(default 5s).On a slow CI agent, the in-memory
server/discoverround-trip could occasionally take longer than 5s. When that happened:initializehandshake, negotiating an older protocol version.TaskCancellationIntegrationTestsregisters a tool whose body isTask.Delay(Timeout.Infinite, cancellationToken). Without the tasks extension, the server ran that tool inline on thetools/callrequest instead of dispatching it as a cancellable background task.tools/callresponse, and the client'sawait CallToolRawAsync(...)blocks forever.Because the trigger is purely environmental timing, the failure was intermittent and Windows-Debug-CI-specific.
How it was diagnosed (hang dump)
The blame-timeout hang dump was the key. Walking the managed thread stacks:
CallToolRawAsync→SendRequestAsync, awaiting the JSON-RPC response fortools/call— i.e. the client had sent the request and was waiting for a reply that never came.Task.Delay(Timeout.Infinite, …)synchronously on the request-handling path — proving the tool was running inline rather than as a background task.That pairing (client awaiting a
tools/callresponse while the server is blocked inside the tool on the same call) is only possible when the tasks extension was not negotiated. Confirming the negotiated protocol version was older than July2026 pointed directly at aserver/discover→initializefallback, and the only thing that forces that fallback in the in-memory harness is the probe timeout expiring — i.e. CI slowness tripping the 5sDiscoverProbeTimeout.The fix
Within
ClientServerTestBase, a fallback can only be caused by the probe timeout (the shared in-memory server always supportsserver/discover, and back-compat tests that want an older protocol pin the client version, skipping the probe entirely). Disabling the probe timeout therefore removes the spurious-fallback class outright, with no per-caller churn and no production code change.Tests that intentionally exercise the probe-timeout fallback (
July2026ProtocolFallbackTests) build their own transport and callMcpClient.CreateAsyncdirectly, so they bypass this helper and are unaffected.Testing
dotnet build— clean (0 warnings / 0 errors).TestServer.exePATH issue unrelated to this change; they build and run in CI.)