Repository navigation
fix(circuit_breaker): await async coroutines inside circuit breaker failure tracking - #8703
Conversation
…sync paths _route() decides CLOSED/PROBE/OPEN once; _on_closed_*/_on_probe_* hold the bookkeeping; the sync and async call methods only differ in how they invoke the protected function. Also dedupes the decorator wrappers and documents async support.
leandrodamascena
left a comment
There was a problem hiding this comment.
Thanks @ASHMEET555, good work, and fast. I tested it against a real DynamoDB table: on develop an async def never trips the circuit, with your change it opens on the second failure, awaits the async callback and closes again after the probe.
One thing I changed, and I pushed it to your branch so you can see it: the PR copied handle, _call_closed and _call_probe into async versions, which means two copies of the state machine to keep in sync. I split the routing into a single _route() and moved the bookkeeping into small _on_closed_* / _on_probe_* helpers, so the sync and async paths only differ in the await. Your tests pass unchanged. I also added a short docs section with an async example.
Approving, this goes out in 3.39.0, credit is yours in the release notes.
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## develop #8703 +/- ##
===========================================
+ Coverage 96.99% 97.00% +0.01%
===========================================
Files 321 321
Lines 16332 16415 +83
Branches 1473 1482 +9
===========================================
+ Hits 15841 15924 +83
Misses 350 350
Partials 141 141 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
…call the circuit concurrently The probe lease is per thread, so concurrent coroutines in one event loop all passed the owner check and probed together. Track in-flight probes per circuit name and give the other callers the open response, matching the single-prober guarantee threads already had.
|
leandrodamascena
left a comment
There was a problem hiding this comment.
@ASHMEET555 I pushed two more commits, take a look when you can.
While testing I noticed the probe owner id lives in thread-local storage. Coroutines share the thread, so when I ran asyncio.gather with 20 calls on a circuit that was due for a probe, all 20 hit the backend and the circuit closed on the first 3 successes. Threads don't have this problem because each one gets its own id.
I fixed it by tracking which circuits have a probe running in the process. The first coroutine probes, the rest get the open response. Added two tests for it. Sync code paths are untouched, I ran the develop test file against the new code to be sure.
ec1bec2
into
aws-powertools:develop
|
Awesome work, congrats on your first merged pull request and thank you for helping improve everyone's experience! |
|
Thank @leandrodamascena! I really appreciate the detailed review, testing against real DynamoDB, and the clean refactoring. Extracting Also, the catch on thread-local storage with concurrent coroutines in Thanks for refining this and getting it over the finish line for 3.39.0! Looking forward to contributing more. |



Issue number: closes #8697
Summary
Adds async coroutine function support to
@circuit_breaker. When wrapping anasync deffunction, the decorator now provides an async wrapper that awaits the coroutine inside the circuit breaker's state machine, allowing qualifying failures to be caught and counted towardsfailure_threshold.Changes
aws_lambda_powertools/utilities/circuit_breaker/circuit_breaker.py:inspect.iscoroutinefunction.async_wrapperfor async functions that invokesawait handler.handle_async().POWERTOOLS_CIRCUIT_BREAKER_DISABLED), await the async function directly.aws_lambda_powertools/utilities/circuit_breaker/base.py:handle_async,_call_closed_async,_call_probe_async, and_open_response_asynctoCircuitBreakerHandler.on_circuit_opencallbacks.tests/functional/circuit_breaker/test_circuit_breaker.py:on_circuit_opencallbacks, open circuit errors, half-open probe success/failure, and disabled environment bypass.User experience
Decorating
async deffunctions with@circuit_breakernow tracks failures as expected:Verification
uv run pytest tests/functional/circuit_breaker/ tests/unit/circuit_breaker/ --cov=aws_lambda_powertools.utilities.circuit_breaker --cov-report=term-missing— 93 passed, 95.01% coverage.uv run ruff check aws_lambda_powertools/utilities/circuit_breaker/ tests/functional/circuit_breaker/— Passed.uv run ruff format --check aws_lambda_powertools/utilities/circuit_breaker/ tests/functional/circuit_breaker/— Passed (14 files already formatted).uv run mypy aws_lambda_powertools/utilities/circuit_breaker/— Passed (no issues found in 10 source files).By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice.
Disclaimer: We value your time and bandwidth. As such, any pull requests created on non-triaged issues might not be successful.