Use ConfigureAwait(false) on await foreach in ExtractorBase and TransformerBase - #363
Merged
Chris-Wolfgang merged 1 commit intoAug 13, 2026
Merged
Conversation
…formerBase Four await foreach sites resumed on the captured synchronization context: ExtractorBase.ExtractAsync (ExtractorBase.cs:326) ExtractorBase.ExtractWithProgressAsync (ExtractorBase.cs:344) TransformerBase.TransformAsync (TransformerBase.cs:334) TransformerBase.TransformWithProgressAsync (TransformerBase.cs:352) These packages ship net462 and netstandard2.0, where resuming on the caller's context is a real deadlock risk for consumers calling sync-over-async. CA2007 is set to warning for src, but it does not analyse await foreach, so the analyzer gate could never catch this. The rest of the package already used ConfigureAwait(false) at every other await foreach (EtlPipelineImpl.cs:142, EtlPipelineSink.cs:59, MiddlewareExtensions.cs:56 and :121) - these four were inconsistent with the package's own convention rather than a deliberate choice. The 0.22.0 CHANGELOG entry claimed the release was "not a behavioural change"; amended to note this fix rather than leave the claim inaccurate. Full solution build 0 warnings / 0 errors; 1085 tests green on net10.0. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
3 tasks
Chris-Wolfgang
added a commit
that referenced
this pull request
Aug 13, 2026
) Coyote 1.7.11 throws a NullReferenceException inside its own CoyoteRuntime.IsTaskUncontrolled when it meets the ConfiguredCancelableAsyncEnumerable awaiter introduced by the ConfigureAwait(false) fix (#363) in ExtractorBase.ExtractWithResetAsync. This is an instrumentation crash, not a discovered race: the run reports "Found 1 bug" on iteration #1 having explored exactly 1 execution path in 0.096 sec. Coyote is dormant -- 1.7.11 is the newest release on nuget.org and the last upstream commit was 2024-12-11 -- so there is no version to upgrade to. Reverting #363 was rejected: that would reintroduce a real net462 / netstandard2.0 sync-over-async deadlock risk in the two base classes every extract and transform stage inherits, purely to satisfy a test harness. The test races DisposeAsync against an in-flight enumeration, so it necessarily routes through ExtractAsync -> ExtractWithResetAsync; there is no partial carve-out that keeps the test without the configured-enumerable path. The method is left intact in the source file so restoring it is a one-line change once #364 lands. Concurrent_item_count_increments_never_lose_an_update is unaffected and keeps running -- it never enumerates. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This was referenced Aug 13, 2026
Merged
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.
Use
ConfigureAwait(false)onawait foreachinExtractorBase/TransformerBaseStacked on #357. Found during the 0.22.0 code-review pass.
The defect
Four
await foreachsites resume on the caller's captured synchronization context:ExtractorBase.ExtractAsyncExtractorBase.cs:326ExtractorBase.ExtractWithProgressAsyncExtractorBase.cs:344TransformerBase.TransformAsyncTransformerBase.cs:334TransformerBase.TransformWithProgressAsyncTransformerBase.cs:352These packages ship
net462andnetstandard2.0, where that is a real deadlock risk for any consumer calling sync-over-async. And because these are the two base classes every ETL stage inherits, the exposure is the whole family, not one code path.Why CI never caught it
CA2007iswarningforsrc(.editorconfig:192) — but it does not analyseawait foreachat all. The analyzer gate is structurally blind here, so this can only be found by reading.Why this is a fix, not a style change
The rest of the package already does it —
EtlPipelineImpl.cs:142,EtlPipelineSink.cs:59,MiddlewareExtensions.cs:56and:121. These four were inconsistent with the package's own established convention, not a deliberate exception.CHANGELOG
The 0.22.0 entry asserted the release was "not a behavioural change". That is no longer strictly true, so the entry is amended with a
### Fixedsection rather than left to mislead.Verification
Full solution build 0 warnings / 0 errors; 1085 tests green on net10.0.