You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: docs/resilience.md
+5-5Lines changed: 5 additions & 5 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -6,7 +6,7 @@
6
6
-**`RetryBudget`** — Finagle-style token bucket; safe to share across sync `Client` and `AsyncClient` in the same process. (Finagle-style bounds the global retry rate to prevent retry storms when downstreams degrade.)
7
7
-**`Bulkhead` / `AsyncBulkhead`** — concurrency limiter with bounded acquire-wait (`threading.Semaphore` and `asyncio.Semaphore` respectively)
8
8
9
-
A key ordering constraint: `AsyncBulkhead` must sit inside`AsyncRetry` so one slot covers all retry attempts of a single call. For the full recommended ordering across all four primitives, see [Composition](#composition). Reach for the [Middleware guide](middleware.md) when you want to write your own resilience policy.
9
+
A key ordering constraint: `AsyncBulkhead` must sit outside`AsyncRetry` (before it in `middleware=`) so one slot covers all retry attempts of a single call. For the full recommended ordering across all four primitives, see [Composition](#composition). Reach for the [Middleware guide](middleware.md) when you want to write your own resilience policy.
10
10
11
11
## `AsyncRetry`
12
12
@@ -263,12 +263,12 @@ async with AsyncClient(
263
263
The recommended ordering (not enforced, but each position has a reason):
-`AsyncTimeout` outermost so the overall deadline covers the entire sequence including retries and backoff.
270
-
-`AsyncCircuitBreaker` outside `AsyncRetry` so an open circuit short-circuits the whole retry loop without attempting any calls. This also means the breaker counts one outcome per fully-exhausted retry sequence rather than one per individual attempt.
271
-
-`AsyncBulkhead`inside`AsyncRetry` so one slot covers all retry attempts of a single call. Flip it (`[AsyncRetry, AsyncBulkhead]`) and each retry grabs a fresh slot — defeating the bulkhead under load.
270
+
-`AsyncCircuitBreaker` outside `AsyncRetry` so an open circuit short-circuits the whole retry loop without attempting any calls. This also means the breaker counts one outcome per fully-exhausted retry sequence rather than one per individual attempt. Placing it outside `AsyncBulkhead` too means a request the open circuit rejects never consumes a concurrency slot.
271
+
-`AsyncBulkhead`outside`AsyncRetry` so one slot covers all retry attempts of a single call. Flip those two (`[AsyncRetry, AsyncBulkhead]`) and each retry grabs a fresh slot — defeating the bulkhead under load.
Explain the consequence of breaker-outside-retry: an open circuit short-circuits the whole retry loop, and the breaker counts one outcome per fully-exhausted retry sequence.
Copy file name to clipboardExpand all lines: planning/specs/2026-06-13-circuit-breaker-and-timeout-design.md
+2-2Lines changed: 2 additions & 2 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -21,7 +21,7 @@ Both are pure stdlib (`asyncio.timeout`, `time.monotonic`, `threading.Lock`, `en
21
21
2.**The breaker v1 trips on consecutive failures** (Polly *classic* breaker): open after `failure_threshold` consecutive counted failures → probe after `reset_timeout` → close after `success_threshold` consecutive half-open successes. Rolling-window / failure-rate (Resilience4j / Polly-v8 default) is **deferred to v2**; the config is shaped so adding a `window` mode later is purely additive.
22
22
3.**Failure classification = 5xx + network + timeout, excluding 429.** A *counted failure* is `NetworkError`, httpware `TimeoutError`, or a `StatusError` whose `status_code` is in the effective failure set (default = all 5xx, 500–599). 4xx including 429 do **not** trip the breaker (429 = healthy-but-throttling; tripping amplifies the incident) and count as breaker *successes*. Any other exception type (e.g. `BulkheadFullError`, `ValueError`) propagates unchanged and does **not** affect circuit state.
23
23
4.**Control surface is events-only (YAGNI).** No public `state` property, no `reset()` / `isolate()`. Monitoring goes through the observability events. State introspection and manual control can be added additively in a later release if a concrete consumer demand surfaces.
24
-
5.**Recommended ordering is breaker-outside-retry.** Documented (not enforced): `AsyncTimeout → AsyncCircuitBreaker → AsyncRetry → AsyncBulkhead → terminal`. With the breaker outside retry, an open circuit short-circuits the *entire* retry loop (don't hammer a service that's already down), and the breaker counts one outcome per fully-exhausted retry sequence rather than per attempt.
24
+
5.**Recommended ordering is breaker-outside-retry.** Documented (not enforced): `AsyncTimeout → AsyncCircuitBreaker → AsyncBulkhead → AsyncRetry → terminal` (corrected during implementation: AsyncBulkhead sits outside AsyncRetry to keep one slot per logical call, consistent with the existing `test_bulkhead_outside_retry_holds_one_slot_across_attempts` guidance). With the breaker outside retry, an open circuit short-circuits the *entire* retry loop (don't hammer a service that's already down), and the breaker counts one outcome per fully-exhausted retry sequence rather than per attempt.
-**`docs/resilience.md`:** a CircuitBreaker section and an AsyncTimeout section; the recommended ordering `AsyncTimeout → AsyncCircuitBreaker → AsyncRetry → AsyncBulkhead → terminal` (documented, not enforced); the rationale notes ("why no sync Timeout", "why not duplicate httpx2 per-call timeouts", "429/4xx count as successes, not failures").
299
+
-**`docs/resilience.md`:** a CircuitBreaker section and an AsyncTimeout section; the recommended ordering `AsyncTimeout → AsyncCircuitBreaker → AsyncBulkhead → AsyncRetry → terminal` (documented, not enforced); the rationale notes ("why no sync Timeout", "why not duplicate httpx2 per-call timeouts", "429/4xx count as successes, not failures").
300
300
-**`README.md`:** extend the resilience paragraph from "Retry + Bulkhead" to include CircuitBreaker + AsyncTimeout.
301
301
-**`planning/releases/0.10.0.md`:** new release notes (additive minor; new public names; new observability events).
0 commit comments