Skip to content

Report cancellation as cancellation on .NET Framework - #795

Merged
SimonCropp merged 1 commit into
mainfrom
fix-framework-cancel-exception-shape
Aug 22, 2026
Merged

Report cancellation as cancellation on .NET Framework#795
SimonCropp merged 1 commit into
mainfrom
fix-framework-cancel-exception-shape

Conversation

@SimonCropp

Copy link
Copy Markdown
Member

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.

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.
@SimonCropp SimonCropp added this to the 20.0.0 milestone Aug 22, 2026
@SimonCropp
SimonCropp merged commit 631ea9b into main Aug 22, 2026
10 checks passed
@SimonCropp
SimonCropp deleted the fix-framework-cancel-exception-shape branch August 22, 2026 00:43
This was referenced Aug 26, 2026
This was referenced Aug 27, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

1 participant