Changelog¶
v0.3.1 (2026-03-19)¶
New Features¶
- Context propagation:
resilience_contextContextVar carries request-scoped metadata (trace ID, user ID, etc.) into everyResilienceEvent.contextfield automatically - Per-attempt timeout:
TimeoutConfig(per_attempt=False)enforces a single deadline across all retry attempts instead of resetting per attempt - Retry budget:
RetryBudgetConfig(max_retries=100, refill_rate=10)provides a shared token pool that prevents cascading retries across decorated functions - Health check:
health_check(registry)returns a dict summarizing circuit breaker states, in-flight calls, and rate limiter availability for all registered functions - OpenTelemetry listener:
OpenTelemetryListeneremits spans and attributes for each resilience event, integrating with the OpenTelemetry SDK - Prometheus listener:
PrometheusListenerexports counters and histograms to Prometheus via the official client library - Graceful shutdown:
shutdown()drains in-flight calls and releases thread pool resources cleanly - Event duration tracking:
ResilienceEvent.durationfield populated on SUCCESS events for observability integrations - Production/Stable classifier: PyPI classifier bumped to
Development Status :: 5 - Production/Stable
Bug Fixes¶
- AsyncResultCache.put() race condition: Fixed unsafe read of
_store.keys()without holding the lock;ResultCache.put()now returns evicted keys directly - Prometheus histogram unused:
PrometheusListenernow observes call duration via_duration_histogramon SUCCESS events - Cache lock memory leak: Per-key locks in cache stampede prevention are now evicted alongside their cache entries, preventing unbounded lock accumulation
asyncio.iscoroutinefunctiondeprecation: Replaced remaining usage withinspect.iscoroutinefunctionfor Python 3.14+ compatibilityinstall_uvloopdeprecated API: Switched fromuvloop.install()touvloop.EventLoopPolicy()to silence deprecation warnings- Registry executor exposure:
ResilienceRegistrynow exposes executors viaget_executor(name)for runtime introspection without private attribute access
Performance¶
- Zero overhead from new features when no listeners attached
- Duration timing gated behind
if has_listeners:— zero cost on hot path - Lazy in-flight tracking: counter and lock only allocated when
enable_in_flight_tracking()is called - Benchmark results (Apple Silicon): 0.64μs decorator overhead, 223,934 ops/sec throughput, 14.4x faster than tenacity async
Tests¶
- 365 tests (up from 289), 96% branch coverage
- New tests for context propagation, per-attempt timeout, retry budget, health check, OpenTelemetry listener, Prometheus listener, graceful shutdown, event duration, and async cache race fix
v0.3.0 (2026-03-19)¶
New Features¶
- Circuit breaker manual control: Added
reset(),force_open(),force_close()methods toCircuitBreakerfor operational needs (maintenance windows, graceful degradation) - Async fallback handlers:
FallbackConfig.handlercan now be an async function when used with async decorated functions. Detected viaasyncio.iscoroutinefunction - Cache stampede prevention: Per-key locking with double-check pattern prevents thundering herd on cache miss. Only one thread/coroutine computes per cache key; others wait for the result
- Sync timeout thread cancellation: Uses
ctypes.pythonapi.PyThreadState_SetAsyncExcfor best-effort thread interruption on CPython. Exception chain preserved (from exc). Will not interrupt blocking C extensions - AsyncBulkhead loop safety: Detects event loop changes and recreates semaphore automatically. Safe across multiple
asyncio.run()calls
Bug Fixes¶
- FallbackConfig default constructor:
FallbackConfig()now works without arguments. Whenhandler=None,fallback_onis auto-cleared to()to prevent silently returningNoneon exceptions - Circuit breaker race condition: Removed lock-free fast path in
allow_request()— always acquires lock. Safe for free-threaded Python 3.13+ (--disable-gil) - Circuit breaker re-checked between retries: CB state is verified before each retry attempt. If the CB opened (from other threads), retries stop immediately with
CircuitOpenError - MetricsCollector async collision: Replaced
threading.get_ident()withcontextvars.ContextVar— concurrent async coroutines no longer corrupt latency tracking - Jitter zero-delay floor: Jitter now has a 10% minimum floor (
max(base * 0.1, random() * delay)), preventing zero-delay retry storms - Timeout exception chaining:
ResilienceTimeoutErrornow chains viafrom excinstead offrom None, preserving original traceback for debugging strict_policydocstring: Corrected from "1 retry (2 total attempts)" to "No retries (1 total attempt)"- Django
max_retries→max_attempts: Config key renamed for consistency withRetryConfig. Old key still works withDeprecationWarning - mypy clean: Fixed uvloop/orjson import errors via
pyproject.tomloverrides. mypy now passes with zero errors
Improvements¶
- Bulkhead released during retry sleep: Bulkhead slot is now released during retry backoff and reacquired before the next attempt, preventing slot starvation
- Cooperative timeout cancel: Sync timeout uses
threading.Eventfor cancel signaling instead of no-opfuture.cancel() - Listener errors logged: Broken listeners now emit
logging.warning()instead of being silently swallowed - Bounded MetricsCollector memory: Latency history bounded to 10,000 entries via
collections.deque(maxlen=10000) - Larger default thread pool: Sync timeout pool increased from 4 to
min(32, cpu_count + 4)workers - Decorator introspection: Wrapped functions now expose
_executorattribute for runtime access to circuit breaker, cache, etc. - Config validation:
FallbackConfigandCacheConfignow validate inputs in__post_init__ BulkheadFullErrorimport cleanup: Now imported directly from_exceptionsmodule
Performance¶
- Decorator overhead: 0.73us (tenacity 7.89us = 10.8x faster)
- Throughput: 152K-244K ops/sec (tenacity 67K = 2.3-3.6x faster)
- Async: 0.69us (tenacity 12.14us = 17.6x faster)
- Memory: 1,208KB per 1,000 decorated functions (tenacity 2,181KB = 45% less)
- Circuit breaker: 0.95us (pybreaker 0.64us — trade-off for full thread safety)
- Cache hot path: 0.58us
- Fallback: 0.68us
- Bulkhead: 0.66us
Tests¶
- 276 tests (up from 232), 98% branch coverage
- Removed
test_coverage.py— all tests distributed to relevant feature test files - Added config validation tests for all dataclasses
v0.2.0 (2026-03-19)¶
New Features¶
- Sliding window circuit breaker — Count-based sliding window replaces fragile consecutive failure counting. Configure with
sliding_window_sizeparameter. - Failure rate % threshold —
failure_rate_threshold=0.5trips at 50% failure rate within the sliding window, matching production circuit breaker behavior. - Retry on result predicate —
retry_on_result=lambda r: r.get("status") == 429retries based on return values without requiring exceptions. - Slow call detection — Calls exceeding
slow_call_durationcount toward circuit opening viaslow_call_rate_threshold. Detects slow degradation without errors. - Circuit breaker metrics —
.metricsproperty exposes real-time failure_rate, slow_call_rate, total_calls. SLOW_CALLevent type — New event emitted when a call exceeds the slow call duration threshold.
Performance¶
__slots__on all hot-path classes (executor, circuit breaker, cache, rate limiter, logger)- Cached module-level locals for
time.monotonic,random.random, enum members - Inlined
isinstance()checks, guarded event emission withhas_listenersbool - Pre-computed rate (tokens/sec) in rate limiter
- Lock-free circuit breaker reads for CLOSED state (CPython GIL atomic reference)
atexit+weakrefthread pool lifecycle management- Tuple-based cache keys (fast path for hashable args)
- Integer nanosecond arithmetic in rate limiter (avoids float overhead)
- Fast path: skip retry loop when no retry/timeout/slow-call configured
- Atomic counter with lock for non-waiting bulkhead mode
- Decorator overhead: 0.55us (13x faster than tenacity at 7.02us)
- Circuit breaker overhead: 0.99us (was 1.25us)
- All 7 patterns combined: 0.66us (was 0.94us)
- Memory: 1,104KB per 1,000 decorated functions (51% less than tenacity)
- Throughput: 242K ops/sec (2.8x tenacity)
Code Quality¶
- Error hierarchy:
ResilienceErrorbase class withCircuitOpenError,BulkheadFullError,RateLimitExceededError,ResilienceTimeoutErrorsubclasses - Input validation:
__post_init__validation on all config dataclasses - Preset naming:
max_retries→max_attemptsfor consistency withRetryConfig - Bare
@resilientis now a passthrough (no auto-retry)
Fixes¶
- Replaced deprecated
asyncio.iscoroutinefunctionwithinspect.iscoroutinefunction BaseException→Exceptionin retry loops (no longer catches KeyboardInterrupt/SystemExit)- Full jitter (
random.random() * delay) for better thundering-herd protection - Configurable thread pool size for sync timeouts (
TimeoutConfig(pool_size=N)) - Thread pool lifecycle management with
atexitcleanup (prevents resource leaks) - Cache key collisions fixed with type-qualified string keys for unhashable args
Tests¶
- 232 tests (up from 176), 98.56% branch coverage
- New:
test_sliding_window.py(15 tests),test_retry_on_result.py(11 tests) - Expanded
test_coverage.pywith edge case tests for executor, contrib, presets, and logging modules
v0.1.0 (2026-03-19)¶
Initial release with all 7 resilience patterns.
Core Modules¶
- CircuitBreaker — State machine (CLOSED/OPEN/HALF_OPEN) with configurable thresholds
- Retry — Exponential backoff with jitter, configurable exception types
- Bulkhead — Semaphore-based concurrency limiting (sync + async)
- TimeLimiter — Per-call timeouts (thread-based sync, asyncio.wait_for async)
- RateLimiter — Token bucket algorithm for call rate limiting
- Cache — LRU result cache with TTL and hit/miss statistics
Infrastructure¶
- Registry — Centralized management of named resilience instances with shared state
- Presets —
http_policy(),db_policy(),queue_policy(),strict_policy() - Observability —
JsonEventLogger,MetricsCollector, unified event system - Performance — Optional uvloop + orjson backends via
pip install pyresilience[fast]
Quality¶
- Zero runtime dependencies
- Full async support (auto-detects sync vs async)
- Strict mypy type checking
- 157 tests, 98% coverage
- Python 3.9 — 3.14 support
- Linux, macOS, Windows CI