Uh oh!
There was an error while loading. Please reload this page.
Fix synchronous cancellation race in Channels - #132230
Conversation
Use stable cancelability state initialized before cancellation registration, and add deterministic regression coverage for bounded and rendezvous channels.\n\nCo-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
|
Azure Pipelines: Successfully started running 3 pipeline(s). 13 pipeline(s) were filtered out due to trigger conditions. There may be pipelines that require an authorized user to comment /azp run to run. |
There was a problem hiding this comment.
Pull request overview
This PR fixes a race in System.Threading.Channels async operations where synchronous cancellation during token registration could cause completion to proceed without an atomic reservation, enabling double-completion and corrupting waiter/reader linked lists (with downstream assertion failures and potential item loss).
Changes:
- Make cancelability an immutable construction-time property (
_isCancelable) and use it for completion reservation decisions, avoiding reliance on_cancellationRegistration.Tokenwhen registration may be a default value. - Add test infrastructure to construct synchronously-canceled internal channel async operations and inspect/modify their linked-list state.
- Add deterministic regression tests for rendezvous waiter removal and bounded-channel direct handoff skipping canceled readers.
Show a summary per file
| File | Description |
|---|---|
| src/libraries/System.Threading.Channels/src/System/Threading/Channels/AsyncOperation.cs | Introduces _isCancelable and switches completion reservation/assert logic to use it, addressing the synchronous-cancellation registration race. |
| src/libraries/System.Threading.Channels/tests/TestBase.cs | Adds reflection-based helpers to create/cancel internal async operations and manipulate their list links/heads for deterministic regression coverage. |
| src/libraries/System.Threading.Channels/tests/RendezvousChannelTests.cs | Adds a regression test validating delayed removal of a synchronously-canceled waiter doesn’t corrupt the waiting-reader list. |
| src/libraries/System.Threading.Channels/tests/BoundedChannelTests.cs | Adds regression tests for synchronous cancellation during registration and for skipping canceled readers during direct handoff. |
Review details
- Files reviewed: 4/4 changed files
- Comments generated: 1
- Review effort level: Lite
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
jkotas
commented
Aug 17, 2026
/azp run runtime-nativeaot-outerloop |
|
Azure Pipelines: Successfully started running 1 pipeline(s). |
tannergooding
commented
Aug 18, 2026
The change LGTM, but Jan's test feedback is applicable still |
PranavSenthilnathan
commented
Aug 18, 2026
The Cancellation can queue removal while an operation is still linked. Since if(headisnull){return;}Debug.Assert(op.Nextisnull==op.Previousisnull);I suggest including this fix in this PR because it closes #129796; otherwise the same Known Build Error remains possible. It does not change release behavior. Note GitHub Copilot helped draft this comment. |
Replace reflection-based white-box coverage with a single NativeAOT-compatible UnsafeAccessor test.\n\nCo-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>\nCopilot-Session: 7b4977f8-095c-4594-83da-8acc9a24fe5b
Uh oh!
There was an error while loading. Please reload this page.
Check for a detached operation list before asserting link consistency, as completion may clear detached links concurrently.\n\nCo-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>\nCopilot-Session: 7b4977f8-095c-4594-83da-8acc9a24fe5b
There was a problem hiding this comment.
Review details
Suppressed comments (1)
src/libraries/System.Threading.Channels/tests/BoundedChannelTests.cs:541
- The PR description claims deterministic coverage was added for additional scenarios (skipping a canceled bounded-channel reader during direct handoff, and delayed rendezvous waiter removal after the list has been drained), but the diff here only adds the synchronous-cancellation-during-registration test. Either add the missing tests (likely in BoundedChannelTests/RendezvousChannelTests) or update the PR description so it accurately reflects the changes in this PR.
#if NET
[Fact]
public async Task AsyncOperation_SynchronousCancellationDuringRegistration_ReservesCompletion()
{
using var cts = new CancellationTokenSource();
cts.Cancel();
object operation = AsyncOperationAccessors<int>.CreateBlockedReadAsyncOperation(
runContinuationsAsynchronously: true,
cts.Token,
pooled: false,
static (state, token) => Assert.True(AsyncOperationAccessors<int>.TrySetCanceled(state, token)));
var valueTask = new ValueTask<int>((IValueTaskSource<int>)operation, token: 0);
await AssertExtensions.CanceledAsync(cts.Token, async () => await valueTask);
Assert.False(AsyncOperationAccessors<int>.TryReserveCompletionIfCancelable(operation));
}
- Files reviewed: 3/3 changed files
- Comments generated: 0 new
- Review effort level: Lite
steveisok
commented
Aug 21, 2026
@PranavSenthilnathan can I get another review on this? |
PranavSenthilnathan
commented
Aug 21, 2026
The mono/wasm tests failures look valid: |
The generic UnsafeAccessorType constructor binding is unsupported on Mono and fails across desktop and browser runtimes. Keep the regression covered by CoreCLR and NativeAOT.\n\nCo-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>\nCopilot-Session: 7b4977f8-095c-4594-83da-8acc9a24fe5b
Uh oh!
There was an error while loading. Please reload this page.
steveisok
commented
Aug 21, 2026
/backport to release/11.0-rc1 |
Started backporting to |
…2638) Backport of #132230 to release/11.0-rc1 /cc @steveisok ## Customer Impact - [ ] Customer reported - [ ] Found internally [Select one or both of the boxes. Describe how this issue impacts customers, citing the expected and actual behaviors and scope of the issue. If customer-reported, provide the issue number.] ## Regression - [ ] Yes - [ ] No [If yes, specify when the regression was introduced. Provide the PR or commit if known.] ## Testing [How was the fix verified? How was the issue missed previously? What tests were added?] ## Risk [High/Medium/Low. Justify the indication by mentioning how risks were measured and addressed.] **IMPORTANT**: If this backport is for a servicing release, please verify that: - For .NET 8 and .NET 9: The PR target branch is `release/X.0-staging`, not `release/X.0`. - For .NET 10+: The PR target branch is `release/X.0` (no `-staging` suffix). ## Package authoring no longer needed in .NET 9 **IMPORTANT**: Starting with .NET 9, you no longer need to edit a NuGet package's csproj to enable building and bump the version. Keep in mind that we still need package authoring in .NET 8 and older versions. Co-authored-by: Steve Pfister <steveisok@users.noreply.github.com>
Fixes#129796
Fixes#132094
AsyncOperationused_cancellationRegistration.Tokento determine whether completion needed atomic reservation. If cancellation ran synchronously insideUnsafeRegister, the registration had not yet been assigned, so the callback observed a default non-cancelable token and completed without reserving the operation.A reader or waiter could then be reserved and completed a second time. This caused linked-list assertion failures in
RendezvousChanneland could silently lose items during aBoundedChanneldirect handoff.Use an immutable
_isCancelablevalue initialized before registration can invoke the callback. This preserves the non-cancelable fast path, pooling behavior, and registration cleanup without retaining the fullCancellationTokenon modern .NET.Adds deterministic coverage for:
The added field fits existing object padding;
BlockedReadAsyncOperation<int>remains 96 bytes.Validation
Note
This pull request description was generated with the assistance of GitHub Copilot.