Add server-initiated session cancellation to the dotnet test IPC protocol (#8691)#9549
Draft
Evangelink wants to merge 2 commits into
Draft
Add server-initiated session cancellation to the dotnet test IPC protocol (#8691)#9549Evangelink wants to merge 2 commits into
Evangelink wants to merge 2 commits into
Conversation
…ocol (#8691) Implements Option B from the RFC: a reverse "server control" pipe. The SDK advertises a control pipe name in its handshake reply; the test host connects back and parks a long-poll WaitForServerControlRequest that the SDK completes with a ServerControlMessage (CancelSession). On cancel the host stops gracefully via IGracefulStopTestExecutionCapability so trx/artifacts survive, falling back to hard cancellation when no graceful capability exists. A dropped control pipe is treated as "host gone => cancel". - Protocol 1.4.0; handshake property ServerControlPipeName=12; serializer ids WaitForServerControlRequest=13, ServerControlMessage=14. - NamedPipeClient gains exitProcessOnConnectionLoss so the control channel does not kill the host on disconnect. - Contract + round-trip unit tests and end-to-end acceptance tests (graceful cancel still reports; no-advertise is a no-op). Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
… the parked read on exit - StartServerControlChannelAsync no longer blocks test start on the control-pipe connect; connect + long-poll now run entirely on the background listener task (fixes up-to-30s startup stall and de-risks multi-process contention). - OnExitAsync now cancels then disposes the control pipe client before a bounded wait, so the parked long-poll read is force-aborted even on .NET Framework where cancelling an in-flight named-pipe read is unreliable (fixes potential exit hang). - Documented the SDK-side contract for ServerControlPipeName (one control connection per connecting process; SDK must keep the pipe open for the whole data session, since an early drop is treated as cancel). Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Contributor
There was a problem hiding this comment.
Pull request overview
Adds server-initiated session cancellation support to the dotnet test IPC protocol (MTP “server mode”) by introducing a reverse “server control” named pipe that the SDK can use to push a cooperative CancelSession signal to the test host, allowing graceful shutdown with reporting preserved.
Changes:
- Extend the dotnet-test pipe protocol with v1.4.0, a new handshake property (
ServerControlPipeName), and two new message types (WaitForServerControlRequest,ServerControlMessage). - Implement host-side listening/handling for server control messages (including a graceful-stop path via
IGracefulStopTestExecutionCapability, with a cancellation fallback). - Add protocol/unit/contract coverage plus an end-to-end acceptance test and enhance the fake SDK harness to drive the control channel.
Show a summary per file
| File | Description |
|---|---|
| test/UnitTests/Microsoft.Testing.Platform.UnitTests/IPC/ProtocolTests.cs | Adds round-trip tests for new serializers, updates stable IDs/handshake property list, and bumps supported protocol versions to include 1.4.0. |
| test/UnitTests/Microsoft.Testing.Platform.DotnetTestProtocolContract.UnitTests/DotnetTestProtocolContractTests.cs | Mirrors the protocol contract stability assertions for new IDs/properties and version list. |
| test/IntegrationTests/Microsoft.Testing.Platform.Acceptance.IntegrationTests/DotnetTestPipe/FakeDotnetTestSdkResult.cs | Tracks control-pipe observations (connected + cancel sent) for acceptance assertions. |
| test/IntegrationTests/Microsoft.Testing.Platform.Acceptance.IntegrationTests/DotnetTestPipe/FakeDotnetTestSdk.cs | Adds optional reverse control pipe server and drives CancelSession message in the fake SDK harness. |
| test/IntegrationTests/Microsoft.Testing.Platform.Acceptance.IntegrationTests/DotnetTestPipe/DotnetTestPipeServerCancellationTests.cs | New end-to-end acceptance tests validating graceful server-initiated cancellation behavior and the no-advertisement case. |
| test/IntegrationTests/Microsoft.Testing.Platform.Acceptance.IntegrationTests/DotnetTestPipe/DotnetTestPipeProtocol.cs | Extends black-box protocol contract helper with new serializer IDs, handshake property ID, and server-control message encoding. |
| test/IntegrationTests/Microsoft.Testing.Platform.Acceptance.IntegrationTests/DotnetTestPipe/DotnetTestPipeBaselineTests.cs | Updates baseline advertised protocol versions to include 1.4.0. |
| src/Platform/Microsoft.Testing.Platform/ServerMode/IPushOnlyProtocol.cs | Adds control-channel capability flag and a start method to begin listening for server control signals. |
| src/Platform/Microsoft.Testing.Platform/ServerMode/DotnetTest/IPC/Serializers/WaitForServerControlRequestSerializer.cs | New serializer for an empty long-poll request used on the control pipe. |
| src/Platform/Microsoft.Testing.Platform/ServerMode/DotnetTest/IPC/Serializers/ServerControlMessageSerializer.cs | New serializer for a server-pushed control message (currently Kind only). |
| src/Platform/Microsoft.Testing.Platform/ServerMode/DotnetTest/IPC/ObjectFieldIds.cs | Defines stable serializer IDs/field IDs for the new control messages. |
| src/Platform/Microsoft.Testing.Platform/ServerMode/DotnetTest/IPC/Models/WaitForServerControlRequest.cs | New model representing the parked long-poll request. |
| src/Platform/Microsoft.Testing.Platform/ServerMode/DotnetTest/IPC/Models/ServerControlMessage.cs | New model representing the server’s control reply (e.g., CancelSession). |
| src/Platform/Microsoft.Testing.Platform/ServerMode/DotnetTest/IPC/Constants.cs | Adds handshake property constant for ServerControlPipeName, defines ServerControlKinds, and bumps supported versions to include 1.4.0. |
| src/Platform/Microsoft.Testing.Platform/ServerMode/DotnetTest/DotnetTestConnection.cs | Implements control-pipe negotiation, background connect/listen loop, and one-time cancel dispatch + teardown behavior. |
| src/Platform/Microsoft.Testing.Platform/IPC/Serializers/RegisterSerializers.cs | Registers the two new serializers on named-pipe endpoints. |
| src/Platform/Microsoft.Testing.Platform/IPC/NamedPipeClient.cs | Adds exitProcessOnConnectionLoss flag to allow auxiliary channels to surface disconnects without terminating the process. |
| src/Platform/Microsoft.Testing.Platform/Hosts/TestHostOchestratorHost.cs | Wires control-channel cancel to application token cancellation for orchestrator host scenario. |
| src/Platform/Microsoft.Testing.Platform/Hosts/CommonTestHost.cs | Starts server-control listener after a successful handshake and maps cancel to graceful-stop capability (or token cancellation fallback). |
Review details
- Files reviewed: 19/19 changed files
- Comments generated: 1
- Review effort level: Low
Comment on lines
+56
to
+62
| string? controlOsPipeName = null; | ||
| NamedPipeServerStream? controlStream = null; | ||
| if (advertiseServerControlPipe) | ||
| { | ||
| controlOsPipeName = DotnetTestPipeProtocol.GetPipeName(Guid.NewGuid().ToString("N")); | ||
| controlStream = new(controlOsPipeName, PipeDirection.InOut, maxNumberOfServerInstances: 1, PipeTransmissionMode.Byte, options); | ||
| } |
| if (advertiseServerControlPipe) | ||
| { | ||
| controlOsPipeName = DotnetTestPipeProtocol.GetPipeName(Guid.NewGuid().ToString("N")); | ||
| controlStream = new(controlOsPipeName, PipeDirection.InOut, maxNumberOfServerInstances: 1, PipeTransmissionMode.Byte, options); |
| bool.TryParse(isIDEValue, out bool isIDE) && | ||
| isIDE; | ||
|
|
||
| if (response.Properties?.TryGetValue(HandshakeMessagePropertyNames.ServerControlPipeName, out string? serverControlPipeName) == true && |
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.
Closes #8691.
Implements the RFC's recommended Option B — a reverse "server control" pipe — so
dotnet test-level features (global--maximum-failed-tests,--timeout, graceful Ctrl+C drain) can ask a Microsoft.Testing.Platform test app to stop cooperatively instead of only letting it run to completion orProcess.Kill-ing it.How it works
sequenceDiagram participant SDK as dotnet test (SDK) participant App as MTP test app App->>SDK: Handshake (advertises 1.4.0) SDK-->>App: Handshake reply (ServerControlPipeName=<pipe>) App->>SDK: WaitForServerControlRequest (parks on control pipe) Note over SDK: global budget reached SDK-->>App: ServerControlMessage(CancelSession) App->>App: IGracefulStopTestExecutionCapability.StopTestExecutionAsync() App->>SDK: final results + TestSessionEnd (data pipe) App-->>SDK: exitNamedPipeClientand parks a long-pollWaitForServerControlRequest; the SDK completes it with aServerControlMessage. This is a true server push, so it works even when the app is silent/hung — the crux for--timeout.CancelSessionthe host stops gracefully viaIGracefulStopTestExecutionCapability(the same path--maximum-failed-testsuses), so trx/logs/artifacts for anything that completed are still emitted. It falls back to hard token cancellation only when the running framework has no graceful-stop capability.NamedPipeClient.exitProcessOnConnectionLossflag keeps that from killing the host (the shared data-pipe behavior is unchanged). On exit the parked read is force-aborted (dispose-before-await), so shutdown can't hang — including on .NET Framework where cancelling an in-flight named-pipe read is unreliable.Protocol changes (mirror in dotnet/sdk in lockstep)
ProtocolConstantsbumped to add 1.4.0.ServerControlPipeName = 12.WaitForServerControlRequest = 13,ServerControlMessage = 14(Kindbyte,CancelSession = 1). Note the RFC's "id 11 is next free" was stale — 11/12 are alreadyAzureDevOpsLogMessage/DisplayMessage.ServerControlPipeNamedoc spells out the SDK-side contract: accept one control connection per connecting process, and keep the pipe open until the data session ends (an early drop is read as cancel).Notes vs. the RFC
CancellationTokenSource.Cancel()as the RFC sketched, precisely so reporting survives.Tests
ProtocolTests,DotnetTestProtocolContractTests).DotnetTestPipeServerCancellationTests): a graceful server-initiated cancel still reports a result +TestSessionEndand exits cleanly; an SDK that doesn't advertise the pipe is a no-op.FakeDotnetTestSdkharness to stand up the control pipe and pushCancelSession.All 13 DotnetTestPipe acceptance tests, the protocol unit tests, and the contract tests pass; the strict
-packbuild is green.An expert review pass was run and its findings addressed (background connect, force-abort-on-exit, protocol-contract docs) in the second commit.