Skip to content

Prevent the circuit breaker from capturing the ambient ExecutionContext - #3284

Merged
martincostello merged 1 commit into
App-vNext:mainfrom
Nacl-HCl:fix-circuit-breaker-executioncontext-flow-3275
Oct 24, 2026
Merged

martincostello merged 1 commit into
App-vNext:mainfrom
Nacl-HCl:fix-circuit-breaker-executioncontext-flow-3275

Conversation

@Nacl-HCl

Copy link
Copy Markdown
Contributor

Fixes #3275

The issue or feature being addressed

#3275

Details on the issue fix or feature implementation

ScheduledTaskExecutor starts its background processing loop with
Task.Run(StartProcessingAsync) in its constructor, which flows the ambient
ExecutionContext into the loop. The loop waits on its semaphore for as long
as the pipeline lives, so every AsyncLocal value of the code that happened
to build the pipeline stays reachable for the pipeline's lifetime (e.g. the
first user's circuit in a Blazor Server app is never collected after it is
disposed).

The loop is infrastructure that belongs to no caller, and the scheduled tasks
are the breaker's own state-change callbacks, which a caller's ambient
AsyncLocal values should not reach either. The constructor now starts the
loop with the flow suppressed:

using (ExecutionContext.SuppressFlow())
{
    ProcessingTask = Task.Run(StartProcessingAsync);
}

Confirm the following

  • I started this PR by branching from the head of the default branch
  • I have targeted the PR to merge into the default branch
  • I have included unit tests for the issue/feature
  • I have successfully run a local build
  • I have the experience and knowledge necessary to understand, review, and validate all content in this PR.
  • This PR has content that I did not fully write myself.
    • I used AI to assist in writing or reviewing the content of this PR.

Verification

  • New regression test Constructor_DoesNotCaptureAmbientExecutionContext
    (in ScheduledTaskExecutorTests): builds an executor while an
    AsyncLocal<object?> holds a value, then asserts the value is collectable
    while the executor (and its background task) is kept alive.
    • before the fix: fails (IsAlive is True — the leak reproduces)
    • after the fix: passes (5/5 runs, no flakiness)
  • Polly.Core.Tests full suite (net10.0): 782/782 passed, 0 failed
  • Polly.Core builds clean for all target frameworks
    (net8.0;net6.0;netstandard2.0;net472;net462), 0 warnings, 0 errors

Note

This PR description was prepared with AI assistance (Muse). The fix and the
regression test were verified by running the reproduction and the test suite
locally as described above.

…xt (App-vNext#3275)

ScheduledTaskExecutor starts its background processing loop with Task.Run in its constructor, which flows the ambient ExecutionContext. The loop lives as long as the pipeline, so every AsyncLocal value of the code that built the pipeline stays reachable for the pipeline's lifetime. Start the loop with the flow suppressed; the loop is infrastructure that belongs to no caller.
@codecov

codecov Bot commented Oct 24, 2026 •

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 98.02%. Comparing base (d065fd7) to head (b07d0ff).
⚠️ Report is 1 commits behind head on main.

Additional details and impacted files
@@           Coverage Diff           @@
##             main    #3284   +/-   ##
=======================================
  Coverage   98.02%   98.02%           
=======================================
  Files         321      321           
  Lines        7099     7103    +4     
  Branches     1089     1089           
=======================================
+ Hits         6959     6963    +4     
  Misses         98       98           
  Partials       42       42           
Flag Coverage Δ
linux 97.97% <100.00%> (+<0.01%) ⬆️
macos 98.01% <100.00%> (+<0.01%) ⬆️
windows 98.02% <100.00%> (+<0.01%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

@martincostello martincostello added bug fix ai-generated A comment that appears to be AI generated. labels Oct 24, 2026
@martincostello martincostello added this to the v8.8.1 milestone Oct 24, 2026
@martincostello
martincostello merged commit 813d10a into App-vNext:main Oct 24, 2026
27 checks passed
@github-actions

Copy link
Copy Markdown
Contributor

Thanks for your contribution @Nacl-HCl - the changes from this pull request have been published as part of version 8.8.1 📦, which is now available from NuGet.org 🚀

SonnyRR pushed a commit to SonnyRR/stacked-deck-templates that referenced this pull request Oct 26, 2026
Updated [Polly](https://github.com/App-vNext/Polly) from 8.8.0 to 8.8.1.

<details>
<summary>Release notes</summary>

_Sourced from [Polly's
releases](https://github.com/App-vNext/Polly/releases)._

## 8.8.1

## Highlights

* Prevent the circuit breaker from capturing the ambient
ExecutionContext by @​Nacl-HCl in
App-vNext/Polly#3284
* Improve pipeline execution performance by @​martincostello in
App-vNext/Polly#3287
* Fix cleanup of discarded registry pipelines by @​harshvarudkar in
App-vNext/Polly#3288

## What's Changed

* Add Copilot code review skill by @​martincostello in
App-vNext/Polly#3247
* Fix benchmarks by @​martincostello in
App-vNext/Polly#3248
* Update PR template by @​martincostello in
App-vNext/Polly#3249
* Update README with OSMF details for Polly by @​joelhulen in
App-vNext/Polly#3263
* Fix lint warnings by @​martincostello in
App-vNext/Polly#3264
* Update FUNDING by @​martincostello in
App-vNext/Polly#3258
* Remove obsolete packages references by @​martincostello in
App-vNext/Polly#3267
* Validate packages in parallel by @​martincostello in
App-vNext/Polly#3283
* .NET 11 preparation by @​martincostello in
App-vNext/Polly#3285

## New Contributors

* @​Nacl-HCl made their first contribution in
App-vNext/Polly#3284
* @​harshvarudkar made their first contribution in
App-vNext/Polly#3288

**Full Changelog**:
App-vNext/Polly@8.8.0...8.8.1

Commits viewable in [compare
view](App-vNext/Polly@8.8.0...8.8.1).
</details>

[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=Polly&package-manager=nuget&previous-version=8.8.0&new-version=8.8.1)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ai-generated A comment that appears to be AI generated. bug fix

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Circuit breaker keeps the ExecutionContext of whoever builds the pipeline alive for the pipeline's lifetime

2 participants