f808abff86
Fix JSON syntax errors in .devcontainer/devcontainer.json (removed
invalid JS-style // comments) and .devcontainer/opencode.json (removed
90+ trailing commas). Apply auto-fixes for end-of-file and trailing
whitespace issues across 100+ files. Fix SIM105 ruff violations in
benchmarks/core_circuit_breaker_bench.py (use contextlib.suppress).
Note: The security fix from issue #7478 (validate_path startswith bypass)
was already delivered to master in commit e18ac5f2. This PR as currently
structured is non-atomic (35 commits across 10+ issues) and needs
significant restructure before merge. This commit only addresses the
CI/pre-commit failures.
ISSUES CLOSED: #7478
250 lines
7.8 KiB
Python
250 lines
7.8 KiB
Python
"""ASV benchmarks for circuit breaker pattern.
|
|
|
|
Measures the overhead of circuit breaker state management,
|
|
state transitions, and fast-fail latency in open state.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import asyncio
|
|
import contextlib
|
|
import time
|
|
|
|
from cleveragents.core.circuit_breaker import (
|
|
CircuitBreaker,
|
|
CircuitBreakerOpen,
|
|
CircuitBreakerState,
|
|
)
|
|
|
|
|
|
class CircuitBreakerClosedStateBench:
|
|
"""Benchmark circuit breaker overhead in closed state."""
|
|
|
|
timeout = 60
|
|
|
|
def setup(self) -> None:
|
|
"""Set up circuit breaker in closed state."""
|
|
self.breaker = CircuitBreaker(
|
|
failure_threshold=5,
|
|
recovery_timeout=60.0,
|
|
expected_exception=Exception,
|
|
half_open_max_successes=2,
|
|
cooldown_seconds=30.0,
|
|
name="test_service",
|
|
)
|
|
|
|
def time_call_success(self) -> None:
|
|
"""Benchmark successful call overhead in closed state."""
|
|
|
|
def dummy_func() -> str:
|
|
return "success"
|
|
|
|
self.breaker.call(dummy_func)
|
|
|
|
def time_call_with_args(self) -> None:
|
|
"""Benchmark call with arguments in closed state."""
|
|
|
|
def dummy_func(x: int, y: str) -> str:
|
|
return f"{x}:{y}"
|
|
|
|
self.breaker.call(dummy_func, 42, "test")
|
|
|
|
def time_state_check(self) -> None:
|
|
"""Benchmark state property access."""
|
|
_ = self.breaker.state
|
|
|
|
def time_failure_count_check(self) -> None:
|
|
"""Benchmark failure count property access."""
|
|
_ = self.breaker.failure_count
|
|
|
|
|
|
class CircuitBreakerOpenStateBench:
|
|
"""Benchmark circuit breaker fast-fail latency in open state."""
|
|
|
|
timeout = 60
|
|
|
|
def setup(self) -> None:
|
|
"""Set up circuit breaker in open state."""
|
|
self.breaker = CircuitBreaker(
|
|
failure_threshold=2,
|
|
recovery_timeout=60.0,
|
|
expected_exception=Exception,
|
|
half_open_max_successes=2,
|
|
cooldown_seconds=30.0,
|
|
name="test_service",
|
|
)
|
|
# Force the breaker into open state
|
|
for _ in range(3):
|
|
with contextlib.suppress(ZeroDivisionError):
|
|
self.breaker.call(lambda: 1 / 0) # Raise ZeroDivisionError
|
|
|
|
def time_fast_fail(self) -> None:
|
|
"""Benchmark fast-fail latency when circuit is open."""
|
|
with contextlib.suppress(CircuitBreakerOpen):
|
|
self.breaker.call(lambda: "should not execute")
|
|
|
|
def time_open_state_check(self) -> None:
|
|
"""Benchmark state check when open."""
|
|
assert self.breaker.state == CircuitBreakerState.OPEN
|
|
|
|
|
|
class CircuitBreakerClosedToOpenTransitionBench:
|
|
"""Benchmark closed-to-open state transition overhead."""
|
|
|
|
timeout = 60
|
|
|
|
def setup(self) -> None:
|
|
"""Set up a fresh circuit breaker in closed state."""
|
|
self.breaker = CircuitBreaker(
|
|
failure_threshold=2,
|
|
recovery_timeout=60.0,
|
|
expected_exception=Exception,
|
|
half_open_max_successes=1,
|
|
cooldown_seconds=30.0,
|
|
name="test_service",
|
|
)
|
|
|
|
def time_closed_to_open_transition(self) -> None:
|
|
"""Benchmark transition from closed to open state."""
|
|
# Trigger failures to transition to open
|
|
for _ in range(3):
|
|
with contextlib.suppress(ZeroDivisionError):
|
|
self.breaker.call(lambda: 1 / 0)
|
|
|
|
|
|
class CircuitBreakerOpenToHalfOpenTransitionBench:
|
|
"""Benchmark open-to-half-open state transition overhead."""
|
|
|
|
timeout = 60
|
|
|
|
def setup(self) -> None:
|
|
"""Set up circuit breaker already in open state."""
|
|
self.breaker = CircuitBreaker(
|
|
failure_threshold=2,
|
|
recovery_timeout=0.05, # Very short timeout
|
|
expected_exception=Exception,
|
|
half_open_max_successes=1,
|
|
cooldown_seconds=0.01,
|
|
name="test_service",
|
|
)
|
|
# Force to open state
|
|
for _ in range(3):
|
|
with contextlib.suppress(ZeroDivisionError):
|
|
self.breaker.call(lambda: 1 / 0)
|
|
# Wait for recovery timeout so transition to half-open is possible
|
|
time.sleep(0.1)
|
|
|
|
def time_open_to_half_open_transition(self) -> None:
|
|
"""Benchmark transition from open to half-open state."""
|
|
# Attempt call to trigger half-open transition
|
|
with contextlib.suppress(CircuitBreakerOpen):
|
|
self.breaker.call(lambda: "success")
|
|
|
|
|
|
class CircuitBreakerHalfOpenToClosedTransitionBench:
|
|
"""Benchmark half-open-to-closed state transition overhead."""
|
|
|
|
timeout = 60
|
|
|
|
def setup(self) -> None:
|
|
"""Set up circuit breaker already in half-open state."""
|
|
self.breaker = CircuitBreaker(
|
|
failure_threshold=2,
|
|
recovery_timeout=0.05,
|
|
expected_exception=Exception,
|
|
half_open_max_successes=1,
|
|
cooldown_seconds=0.01,
|
|
name="test_service",
|
|
)
|
|
# Force to open state
|
|
for _ in range(3):
|
|
with contextlib.suppress(ZeroDivisionError):
|
|
self.breaker.call(lambda: 1 / 0)
|
|
# Wait for recovery timeout so breaker enters half-open on next call
|
|
time.sleep(0.1)
|
|
|
|
def time_half_open_to_closed_transition(self) -> None:
|
|
"""Benchmark transition from half-open to closed state."""
|
|
# Successful call in half-open should close the breaker
|
|
self.breaker.call(lambda: "success")
|
|
|
|
|
|
class CircuitBreakerAsyncBench:
|
|
"""Benchmark async circuit breaker operations."""
|
|
|
|
timeout = 60
|
|
|
|
def setup(self) -> None:
|
|
"""Set up async circuit breakers."""
|
|
self.breaker = CircuitBreaker(
|
|
failure_threshold=5,
|
|
recovery_timeout=60.0,
|
|
expected_exception=Exception,
|
|
half_open_max_successes=2,
|
|
cooldown_seconds=30.0,
|
|
name="test_service",
|
|
)
|
|
# Set up a separate breaker already in open state for fast-fail benchmark
|
|
self.open_breaker = CircuitBreaker(
|
|
failure_threshold=2,
|
|
recovery_timeout=60.0,
|
|
expected_exception=Exception,
|
|
half_open_max_successes=2,
|
|
cooldown_seconds=30.0,
|
|
name="test_service_open",
|
|
)
|
|
for _ in range(3):
|
|
with contextlib.suppress(ZeroDivisionError):
|
|
self.open_breaker.call(lambda: 1 / 0)
|
|
|
|
def time_async_call_success(self) -> None:
|
|
"""Benchmark successful async call overhead."""
|
|
|
|
async def dummy_async() -> str:
|
|
return "success"
|
|
|
|
asyncio.run(self.breaker.async_call(dummy_async))
|
|
|
|
def time_async_call_with_args(self) -> None:
|
|
"""Benchmark async call with arguments."""
|
|
|
|
async def dummy_async(x: int, y: str) -> str:
|
|
return f"{x}:{y}"
|
|
|
|
asyncio.run(self.breaker.async_call(dummy_async, 42, "test"))
|
|
|
|
def time_async_fast_fail(self) -> None:
|
|
"""Benchmark async fast-fail when open."""
|
|
|
|
async def dummy_async() -> str:
|
|
return "should not execute"
|
|
|
|
with contextlib.suppress(CircuitBreakerOpen):
|
|
asyncio.run(self.open_breaker.async_call(dummy_async))
|
|
|
|
|
|
class CircuitBreakerInitializationBench:
|
|
"""Benchmark circuit breaker initialization overhead."""
|
|
|
|
timeout = 60
|
|
|
|
def time_default_initialization(self) -> None:
|
|
"""Benchmark default initialization."""
|
|
CircuitBreaker()
|
|
|
|
def time_custom_initialization(self) -> None:
|
|
"""Benchmark initialization with custom parameters."""
|
|
CircuitBreaker(
|
|
failure_threshold=10,
|
|
recovery_timeout=120.0,
|
|
expected_exception=ValueError,
|
|
half_open_max_successes=3,
|
|
cooldown_seconds=45.0,
|
|
name="custom_service",
|
|
)
|
|
|
|
def time_multiple_breakers(self) -> None:
|
|
"""Benchmark creating multiple breakers."""
|
|
for i in range(10):
|
|
CircuitBreaker(name=f"service_{i}")
|