Hone logo
Hone
Problems

Implement Circuit Breaker in Go

A circuit breaker is a design pattern used to detect failures and prevent an application from trying to execute an operation that's likely to fail. This is crucial for building resilient systems, especially when dealing with external services that can become slow or unavailable. Your task is to implement a circuit breaker in Go that can gracefully handle such situations.

Problem Description

You need to create a CircuitBreaker type in Go that wraps a function (or a method) and controls its execution based on its success and failure rates. The circuit breaker should have three states:

  1. Closed: Requests are allowed to pass through to the wrapped function. If a certain threshold of failures is reached, the circuit breaker transitions to the "Open" state.
  2. Open: Requests are immediately rejected without executing the wrapped function. After a configured timeout, the circuit breaker transitions to the "Half-Open" state.
  3. Half-Open: A limited number of requests are allowed to pass through. If these requests succeed, the circuit breaker transitions back to "Closed." If they fail, it returns to "Open."

Key Requirements:

  • State Management: Implement the three states (Closed, Open, Half-Open) and the transitions between them.
  • Failure Threshold: Configure a maximum number of consecutive failures before opening the circuit.
  • Success Threshold: Configure a minimum number of consecutive successes in the Half-Open state to close the circuit.
  • Reset Timeout: Configure a duration after which the circuit breaker transitions from Open to Half-Open.
  • Concurrency Safety: The circuit breaker must be safe to use concurrently from multiple goroutines.
  • Error Handling: The circuit breaker should return a specific error when the circuit is open or when the wrapped function itself returns an error.

Expected Behavior:

  • When the circuit is Closed:
    • Calls to the wrapped function are executed.
    • If the wrapped function returns an error, increment the failure count.
    • If the failure count reaches the threshold, open the circuit and record the current time.
    • If the wrapped function succeeds, reset the failure count.
  • When the circuit is Open:
    • Any incoming call immediately returns an "circuit breaker open" error.
    • The wrapped function is NOT executed.
    • If the reset timeout has elapsed since opening the circuit, transition to the Half-Open state.
  • When the circuit is Half-Open:
    • Allow a configurable number of requests (e.g., 1) to pass through.
    • If a request succeeds, reset the failure count, set the success count, and transition to Closed if the success count reaches its threshold.
    • If a request fails, transition back to Open and record the current time.

Examples

Example 1: Normal Operation (Circuit Stays Closed)

Imagine a service that occasionally fails.

// Mock function that succeeds most of the time
func reliableOperation() error {
    // Simulate success
    return nil
}

// Configure circuit breaker:
// Max failures: 5
// Reset timeout: 1 minute
// Success threshold (for Half-Open): 1

If reliableOperation is called 10 times, and it only fails once, the circuit breaker will remain in the Closed state because the failure threshold is not met. Subsequent calls will continue to execute reliableOperation.

Example 2: Circuit Opens Due to Failures

Consider a scenario where a downstream service becomes unavailable.

// Mock function that fails consistently
func flakyOperation() error {
    return fmt.Errorf("downstream service unavailable")
}

// Configure circuit breaker:
// Max failures: 3
// Reset timeout: 10 seconds
// Success threshold (for Half-Open): 1

If flakyOperation is called 5 times:

  1. Call 1: flakyOperation fails. Failures: 1.
  2. Call 2: flakyOperation fails. Failures: 2.
  3. Call 3: flakyOperation fails. Failures: 3. Circuit breaker transitions to Open.
  4. Call 4: Circuit breaker is Open. Returns "circuit breaker open" error. flakyOperation is NOT called.
  5. Call 5: Circuit breaker is Open. Returns "circuit breaker open" error. flakyOperation is NOT called.

After 10 seconds (the reset timeout), the circuit breaker will transition to Half-Open.

Example 3: Circuit Reopens in Half-Open State

Continuing from Example 2, after the reset timeout.

// Circuit breaker is now in Half-Open state (10 seconds have passed since opening)

If flakyOperation is called again:

  1. Call 6: Circuit breaker is Half-Open. One allowed request passes through. flakyOperation is called.
    • flakyOperation returns an error.
    • Circuit breaker transitions back to Open. Failures reset, and the timer for the reset timeout starts again.
  2. Call 7: Circuit breaker is Open. Returns "circuit breaker open" error. flakyOperation is NOT called.

Example 4: Circuit Closes Successfully in Half-Open State

Continuing from Example 2, after the reset timeout.

// Circuit breaker is now in Half-Open state (10 seconds have passed since opening)

If reliableOperation (from Example 1) is called:

  1. Call 6: Circuit breaker is Half-Open. One allowed request passes through. reliableOperation is called.
    • reliableOperation succeeds.
    • Success count increments. Since the success threshold is 1, the circuit breaker transitions back to Closed. Failures are reset.
  2. Call 7: Circuit breaker is Closed. reliableOperation is called.
    • reliableOperation succeeds.
    • Failures remain 0.

Constraints

  • The circuit breaker must be implemented using Go's standard library.
  • The wrapped function will be of type func() error.
  • The circuit breaker configuration parameters (failure threshold, reset timeout, success threshold) should be configurable at creation time.
  • The circuit breaker must handle concurrent access from multiple goroutines safely.
  • The "circuit breaker open" error message should be consistent.

Notes

  • Consider using sync.Mutex for protecting shared state within the circuit breaker.
  • The time package will be essential for managing the reset timeout.
  • The Half-Open state might allow for a configurable number of attempts, not just one. For this challenge, assume a single attempt is allowed in Half-Open.
  • Think about how to effectively log or signal state transitions for debugging purposes (though explicit logging implementation is not required for the core functionality).
  • The wrapped function should be easily replaceable with any function that fits the func() error signature.
Loading editor...
go