Report cancellation as cancellation on .NET Framework - #795
Merged
Conversation
Both clients cancel by closing the TcpClient, because net462 through net48 have no cancellable connect or write. .NET Framework's TcpClient.Dispose nulls its Client field, so a token that fires around the connect does not leave a torn down socket behind - it leaves a null one, and the next line dereferences it. The result was cancellation wearing the wrong exception type. In ViewerClient a NullReferenceException from Configure or HalfClose escaped the catch entirely and surfaced inside the caller's failing test; an ObjectDisposedException got swallowed as "no owner", after which a viewer was launched under a token that had already been cancelled. In PiperClient the same shape was reported as a send failure, so the cancel was lost. Ask the token straight after the connect, which is where the window is, and treat a NullReferenceException as what it is: ViewerClient adds it to the ignorable set beside ObjectDisposedException, and PiperClient maps it to OperationCanceledException when the token has fired. Verified only by the suite passing on net48 as well as net10.0. Landing the race itself needs the token to fire inside a window a few instructions wide.
This was referenced Aug 26, 2026
This was referenced Aug 27, 2026
Closed
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.
Both clients cancel by closing the TcpClient, because net462 through net48 have
no cancellable connect or write. .NET Framework's TcpClient.Dispose nulls its
Client field, so a token that fires around the connect does not leave a torn
down socket behind - it leaves a null one, and the next line dereferences it.
The result was cancellation wearing the wrong exception type. In ViewerClient a
NullReferenceException from Configure or HalfClose escaped the catch entirely
and surfaced inside the caller's failing test; an ObjectDisposedException got
swallowed as "no owner", after which a viewer was launched under a token that
had already been cancelled. In PiperClient the same shape was reported as a send
failure, so the cancel was lost.
Ask the token straight after the connect, which is where the window is, and
treat a NullReferenceException as what it is: ViewerClient adds it to the
ignorable set beside ObjectDisposedException, and PiperClient maps it to
OperationCanceledException when the token has fired.
Verified only by the suite passing on net48 as well as net10.0. Landing the race
itself needs the token to fire inside a window a few instructions wide.