Retry & Failover
Retry policies, circuit breakers, intelligent failover, and observability for resilient proxy rotation.
Retry & Failover
ProxyWhirl combines retry logic, circuit breaker protection, and automatic proxy failover to maximize request success rates.
See also: Circuit Breakers for theory, Clients for async circuit breakers, and Configuration for
max_retriesdefaults.
Architecture
Four components work together:
| Component | Role |
|---|---|
RetryPolicy | Backoff, retryable status codes, timeouts |
RetryExecutor | Orchestrates retries and proxy selection |
CircuitBreaker / AsyncCircuitBreaker | Isolates failing proxies |
RetryMetrics | Observability and hourly aggregation |
When to Retry vs Failover
Retry (same proxy) when the network is temporarily unstable, the target returns 502/503/504, or the request is idempotent (GET, HEAD, OPTIONS, DELETE, PUT).
Failover (different proxy) when authentication fails (407), a circuit breaker opens, geo/performance requirements change, or a proxy hits rate limits.
ProxyWhirl retries with backoff on the current proxy, then fails over using performance-based scoring when retries are exhausted.
RetryPolicy
from proxywhirl import ProxyWhirl, RetryPolicy, BackoffStrategy
policy = RetryPolicy(
max_attempts=5,
backoff_strategy=BackoffStrategy.EXPONENTIAL,
base_delay=1.0,
multiplier=2.0,
max_backoff_delay=30.0,
jitter=True, # AWS decorrelated jitter
retry_status_codes=[502, 503, 504],
timeout=60.0,
retry_non_idempotent=False,
)
rotator = ProxyWhirl(retry_policy=policy)
response = rotator.request("GET", "https://httpbin.org/ip")Backoff strategies
| Strategy | Best for | Behavior |
|---|---|---|
EXPONENTIAL | Network overload | Delays grow exponentially, capped by max_backoff_delay |
LINEAR | Predictable pacing | base_delay, 2×base_delay, 3×base_delay, … |
FIXED | Tests / constant spacing | Same delay every attempt |
Enable jitter in production to avoid thundering herds. ProxyWhirl uses AWS decorrelated jitter: delay = min(cap, random(base_delay, previous_delay × 3)).
Timeouts and idempotency
timeout caps total time across all attempts. POST/PATCH are not retried by default; set retry_non_idempotent=True only when your API is idempotent.
Circuit Breakers
Each proxy has a breaker with states CLOSED → OPEN → HALF_OPEN → CLOSED.
from proxywhirl.circuit_breaker import AsyncCircuitBreaker
# Sync: proxywhirl.CircuitBreaker (threading.Lock)
# Async: AsyncCircuitBreaker (event-loop safe)
cb = rotator.circuit_breakers[str(proxy.id)]
print(cb.failure_threshold) # default: 5
print(cb.window_duration) # default: 60s rolling window
print(cb.timeout_duration) # default: 30s OPEN timeout
cb.reset() # manual resetWarning: Do not use sync CircuitBreaker inside async code — it can block the event loop. Use AsyncCircuitBreaker in production async apps.
Intelligent Failover
When retries exhaust, RetryExecutor scores candidates:
score = (0.7 × success_rate) + (0.3 × (1 - normalized_latency))A 10% geo bonus applies when target_region matches proxy metadata. Failed proxies, OPEN circuits, and pending HALF_OPEN tests are excluded.
RetryMetrics
metrics = rotator.retry_metrics
print(metrics.get_summary())
print(metrics.get_timeseries(hours=24))
print(metrics.get_by_proxy(hours=24))Monitor circuit_breaker_events for OPEN transitions and tune failure_threshold / timeout_duration accordingly.
Integration Example
from proxywhirl import ProxyWhirl, Proxy, RetryPolicy
from proxywhirl.strategies import PerformanceBasedStrategy
from proxywhirl.exceptions import ProxyConnectionError
rotator = ProxyWhirl(
proxies=[Proxy(url="http://proxy1.example.com:8080"), Proxy(url="http://proxy2.example.com:8080")],
strategy=PerformanceBasedStrategy(),
retry_policy=RetryPolicy(max_attempts=3, jitter=True),
)
try:
response = rotator.request("GET", "https://api.example.com/data")
except ProxyConnectionError as e:
print(f"All retries exhausted: {e}")Circuit breakers filter eligible proxies before strategy selection. Metrics inform performance-based routing over time.
Best Practices
- Start with
max_attempts=3, exponential backoff, andjitter=True. - Set
timeoutfor time-sensitive workloads (e.g. 5s) vs batch jobs (e.g. 300s). - Alert when more than ~10% of circuits are OPEN.
- Use
proxywhirl pool statsfor pool-level visibility — see CLI Reference.
Troubleshooting
| Symptom | Likely cause | Action |
|---|---|---|
ProxyConnectionError after N attempts | All proxies unhealthy or blocked | Check breaker states, reset if needed, review per-proxy metrics |
| High latency | Aggressive backoff or slow proxies | Shorten base_delay, set timeout, inspect get_by_proxy() |
NonRetryableError | 4xx or auth failure | Fix credentials; do not retry 407 |
See Also
- Troubleshooting — common runtime issues and error codes
- Advanced strategies — strategy matrix
- Python API —
RetryPolicy,RetryExecutor,CircuitBreaker - Caching — health invalidation with breaker events