Hone logo
Hone
Problems

Robust Service Protection: Implementing a Circuit Breaker in Python

Circuit breakers are a crucial pattern for building resilient distributed systems. They prevent cascading failures by temporarily stopping requests to a failing service, allowing it time to recover. This challenge asks you to implement a basic circuit breaker in Python to protect a simulated external service.

Problem Description

You are tasked with creating a CircuitBreaker class in Python. This class should monitor calls to a function (representing a call to an external service) and automatically "open" the circuit if the function raises an exception a certain number of times within a specified time window. When the circuit is open, subsequent calls to the CircuitBreaker should immediately fail without actually calling the protected function. After a defined "reset timeout," the circuit breaker should enter a "half-open" state, allowing a limited number of test calls. If these test calls succeed, the circuit breaker returns to the "closed" state. If they fail, it returns to the "open" state.

Key Requirements:

  • States: The circuit breaker must manage three states: closed, open, and half-open.
  • Failure Threshold: A configurable number of failures within a time window that triggers the circuit to open.
  • Reset Timeout: A configurable time duration after which the circuit breaker transitions to the half-open state.
  • Test Calls in Half-Open: A configurable number of test calls allowed in the half-open state.
  • Protected Function: The circuit breaker should wrap a function that it will call (or not call, depending on the state) when requested.
  • Thread Safety: While not strictly required for this basic implementation, consider how your solution might be made thread-safe if it were to be used in a multi-threaded environment.

Expected Behavior:

  • When the circuit is closed, calls to the circuit breaker should execute the protected function and record the result (success or failure).
  • When the circuit is open, calls to the circuit breaker should immediately return an exception (e.g., CircuitBreaperOpenError) without executing the protected function.
  • When the circuit is half-open, the protected function should be executed a limited number of times. Successes close the circuit; failures re-open it.
  • The circuit breaker should automatically transition between states based on the configured thresholds and timeout.

Edge Cases to Consider:

  • What happens if the protected function always fails?
  • What happens if the protected function always succeeds?
  • How does the circuit breaker handle exceptions raised within the protected function?
  • What happens if the reset timeout is very short or very long?

Examples

Example 1:

Input: circuit_breaker = CircuitBreaker(failure_threshold=3, reset_timeout=5, test_calls=2, protected_function=lambda: 1/0)
circuit_breaker.call() # Raises CircuitBreakerOpenError (circuit opens immediately)
circuit_breaker.call() # Raises CircuitBreakerOpenError
circuit_breaker.call() # Raises CircuitBreakerOpenError

Output: CircuitBreakerOpenError (raised on each call) Explanation: The circuit opens immediately after three consecutive failures.

Example 2:

Input: circuit_breaker = CircuitBreaker(failure_threshold=2, reset_timeout=3, test_calls=1, protected_function=lambda: 1)
circuit_breaker.call() # Returns 1
circuit_breaker.call() # Returns 1
circuit_breaker.call() # Raises CircuitBreakerOpenError
circuit_breaker.call() # Raises CircuitBreakerOpenError
import time
time.sleep(3.1)
circuit_breaker.call() # Returns 1 (circuit transitions to half-open, test call succeeds)

Output: 1 (after the timeout and successful test call) Explanation: The circuit opens after two failures. After the reset timeout, a test call succeeds, closing the circuit.

Example 3: (Edge Case - Always Failing Function)

Input: circuit_breaker = CircuitBreaker(failure_threshold=1, reset_timeout=2, test_calls=1, protected_function=lambda: 1/0)
circuit_breaker.call() # Raises CircuitBreakerOpenError
import time
time.sleep(2.1)
circuit_breaker.call() # Raises CircuitBreakerOpenError (remains open)

Output: CircuitBreakerOpenError Explanation: The circuit opens immediately and remains open even after the reset timeout because the test call fails.

Constraints

  • failure_threshold: Integer, must be greater than 0.
  • reset_timeout: Float, representing seconds, must be greater than 0.
  • test_calls: Integer, must be greater than 0.
  • The protected_function should be a callable (e.g., a function or lambda expression).
  • The circuit breaker should be implemented without external dependencies beyond the Python standard library.
  • The solution should be reasonably efficient; avoid unnecessary computations or memory usage.

Notes

  • Consider using Python's time module for managing timeouts.
  • Think about how to represent the different states of the circuit breaker (e.g., using an enum or string constants).
  • You can simulate the external service by raising exceptions within the protected_function.
  • Focus on the core logic of the circuit breaker; error handling and logging can be added later.
  • A simple CircuitBreaperOpenError exception can be created for when the circuit is open.
  • This is a simplified implementation; real-world circuit breakers often have more advanced features (e.g., metrics, configurable retry strategies).
Loading editor...
python