Round #313

08 May 2026, 13:28· csharp· openai/gpt-oss-120b:free· r13-clean-hints
Complexity
band 0
ZP / UO / OBS
2 / 3 / 1
Journey weight
362
Lightshift ΔL
+1.00

Prompt

Implement an async circuit breaker that tracks consecutive failures

Zero-point index (what the engine surfaced)

ZPI-ZPI-0005 | 2026-05-08T13:28:30Z Implement an async circuit breaker that tracks consecutive failures ZERO POINT — 2 entries (structural invariants) ● hsl(296.42, 100%, 39%) ResiliencePipeline [contract] ● hsl(211.57, 100%, 39%) ResiliencePipelineBuilder [contract] UNOBSERVED — 3 entries (implied truths) ○ hsl(27.69, 100%, 50%) async [feature] ○ hsl(49.23, 100%, 50%) try [feature] ○ hsl(144.00, 100%, 30%) Connection resilience [function] OBSERVED — 1 entries (explicit in request) ◉ hsl(77.95, 100%, 30%) async [feature] COMPLEXITY │ 6 entries │ spread: 116° │ band: LOW

Baseline response (no ZPI)

Below is a **complete, production‑ready implementation** of an **asynchronous circuit‑breaker** that tracks *consecutive* failures. It is written for .NET 6+ (but works on any recent .NET version) and does **not** depend on any external NuGet packages – only the BCL. --- ## 1. Design Overview | State | Meaning | Transition | |-------|---------|------------| | **Closed** | Calls are allowed. The breaker counts consecutive failures. When the count reaches `failureThreshold` the breaker **opens**. | `Closed → Open` when `consecutiveFailures >= failureThreshold` | | **Open** | Calls are rejected immediately (or wait for a fallback). After `openTimeout` the breaker moves to **HalfOpen**. | `Open → HalfOpen` after `openTimeout` expires | | **HalfOpen** | A limited number of “test” calls are allowed (by default 1). If a test call succeeds the breaker **closes**; if it fails the breaker **re‑opens**. | `HalfOpen → Closed` on first success, `HalfOpen → Open` on first failure | *Consecutive* failures are tracked – a successful call resets the counter to zero. The breaker is **thread‑safe** and fully asynchronous (`Task<T>` / `Task`). It also supports an optional **fallback** delegate that is invoked when the circuit is open. --- ## 2. Public API ```csharp public class AsyncCircuitBreaker { // Construction public AsyncCircuitBreaker( int failureThreshold, TimeSpan openTimeout, int halfOpenSuccessThreshold = 1, Action? onOpen = null, Action? onClose = null, Action? onHalfOpen = null); // Execute a delegate under the protection of the breaker public Task<T> ExecuteAsync<T>(Func<CancellationToken, Task<T>> action, CancellationToken ct = default, Func<CancellationToken, Task<T>>? fallback = null); public Task ExecuteAsync(Func<CancellationToken, Task> action, CancellationToken ct = default, Func<CancellationToken, Task>? fallback = null); // Current state (read‑only) public CircuitState State { get; } // Statistics (optional, for monitoring) public int ConsecutiveFailureCount { get; } public DateTimeOffset? OpenedAt { get; } } ``` `CircuitState` is an enum (`Closed`, `Open`, `HalfOpen`). --- ## 3. Full Implementation ```csharp using System; using System.Threading; using System.Threading.Tasks; /// <summary> /// Represents the three possible states of a circuit breaker. /// </summary> public enum CircuitState { Closed, // normal operation Open, // short‑circuit, reject calls HalfOpen // test period } /// <summary> /// An asynchronous circuit breaker that tracks *consecutive* failures. /// </summary> public sealed class AsyncCircuitBreaker { // ----- Configuration ---------------------------------------------------- private readonly int _failureThreshold; // how many consecutive failures open the circuit private readonly TimeSpan _openTimeout; // how long to stay open before trying half‑open private readonly int _halfOpenSuccessThreshold; // how many successful calls are needed to close again (usually 1) private readonly Action? _onOpen; private readonly Action? _onClose; private readonly Action? _onHalfOpen; // ----- Runtime state (protected by _sync) ------------------------------- private readonly SemaphoreSlim _sync = new(1, 1); // async lock private CircuitState _state = CircuitState.Closed; private int _consecutiveFailures; private DateTimeOffset? _openedAt; // when we entered Open state private int _halfOpenSuccesses; // successes observed while in HalfOpen // ----------------------------------------------------------------------- public AsyncCircuitBreaker( int failureThreshold, TimeSpan openTimeout, int halfOpenSuccessThreshold = 1, Action? onOpen = null, Action? onClose = null, Action? onHalfOpen = null) { if (failureThreshold <= 0) throw new ArgumentOutOfRangeException(nameof(failureThreshold)); if (openTimeout <= TimeSpan.Zero) throw new ArgumentOutOfRangeException(nameof(openTimeout)); if (halfOpenSuccessThreshold <= 0) throw new ArgumentOutOfRangeException(nameof(halfOpenSuccessThreshold)); _failureThreshold = failureThreshold; _openTimeout = openTimeout; _halfOpenSuccessThreshold = halfOpenSuccessThreshold; _onOpen = onOpen; _onClose = onClose; _onHalfOpen = onHalfOpen; } // ----------------------------------------------------------------------- /// <summary> /// Current state (read‑only, thread‑safe). /// </summary> public CircuitState State { get { // No lock needed – reading a reference‑type enum is atomic. return _state; } } /// <summary> /// Number of consecutive failures observed (only meaningful in Closed state). /// </summary> public int ConsecutiveFailureCount => Volatile.Read(ref _consecutiveFailures); /// <summary> /// Timestamp when the circuit entered the Open state, or null if not open. /// </summary> public DateTimeOffset? OpenedAt => _openedAt; // ----------------------------------------------------------------------- // Public entry point for functions returning a value public async Task<T> ExecuteAsync<T>( Func<CancellationToken, Task<T>> action, CancellationToken ct = default, Func<CancellationToken, Task<T>>? fallback = null) { if (action is null) throw new ArgumentNullException(nameof(action)); // 1️⃣ Check circuit state and possibly short‑circuit if (!await IsCallAllowedAsync(ct).ConfigureAwait(false)) { if (fallback != null) return await fallback(ct).ConfigureAwait(false); throw new CircuitBreakerOpenException(); } // 2️⃣ Execute the real work try { T result = await action(ct).ConfigureAwait(false); await OnSuccessAsync().ConfigureAwait(false); return result; } catch (Exception ex) when (!(ex is OperationCanceledException && ct.IsCancellationRequested)) { await OnFailureAsync().ConfigureAwait(false); throw; } } // Overload for void‑returning async methods public async Task ExecuteAsync( Func<CancellationToken, Task> action, CancellationToken ct = default, Func<CancellationToken, Task>? fallback = null) { if (action is null) throw new ArgumentNullException(nameof(action)); if (!await IsCallAllowedAsync(ct).ConfigureAwait(false)) { if (fallback != null) { await fallback(ct).ConfigureAwait(false); return; } throw new CircuitBreakerOpenException(); } try { await action(ct).ConfigureAwait(false); await OnSuccessAsync().ConfigureAwait(false); } catch (Exception ex) when (!(ex is OperationCanceledException && ct.IsCancellationRequested)) { await OnFailureAsync().ConfigureAwait(false); throw; } } // ----------------------------------------------------------------------- // Determines whether a call should be allowed or short‑circuited. private async Task<bool> IsCallAllowedAsync(CancellationToken ct) { // Fast‑path: if we are Closed we can always proceed. if (_state == CircuitState.Closed) return true; // If we are Open we need to see whether the timeout elapsed. if (_state == CircuitState.Open) { // No lock yet – just read the timestamp. var opened = _openedAt; if (opened.HasValue && DateTimeOffset.UtcNow - opened.Value >= _openTimeout) { // Timeout elapsed → try to move to HalfOpen. await TransitionToHalfOpenAsync(ct).ConfigureAwait(false); return _state == CircuitState.HalfOpen; // should be true } // Still open → reject. return false; } // HalfOpen – allow a limited number of test calls. // We allow the call; the success/failure logic will decide the next state. return true; } // ----------------------------------------------------------------------- // Called when the protected operation succeeded. private async Task OnSuccessAsync() { // Acquire lock because we modify shared state. await _sync.WaitAsync().ConfigureAwait(false); try { switch (_state) { case CircuitState.Closed: // Reset consecutive failures on any success. _consecutiveFailures = 0; break; case CircuitState.HalfOpen: _halfOpenSuccesses++; if (_halfOpenSuccesses >= _halfOpenSuccessThreshold) { // Enough successes → close the circuit. await TransitionToClosedAsync().ConfigureAwait(false); } break; case CircuitState.Open: // Should never happen because calls are blocked while Open. break; } } finally { _sync.Release(); } } // ----------------------------------------------------------------------- // Called when the protected operation threw. private async Task OnFailureAsync() { await _sync.WaitAsync().ConfigureAwait(false); try { switch (_state) { case CircuitState.Closed: _consecutiveFailures++; if (_consecutiveFailures >= _failureThreshold) { await TransitionToOpenAsync().ConfigureAwait(false); } break; case CircuitState.HalfOpen: // Any failure while half‑open forces an immediate re‑open. await TransitionToOpenAsync().ConfigureAwait(false); break; case CircuitState.Open: // No action – we are already open. break; } } finally { _sync.Release(); } } // ----------------------------------------------------------------------- // State transition helpers (all called while holding _sync, except the // Open→HalfOpen check which acquires the lock itself). private async Task TransitionToOpenAsync() { // Assume lock already held. _state = CircuitState.Open; _openedAt = DateTimeOffset.UtcNow; _consecutiveFailures = 0; // reset counter _halfOpenSuccesses = 0; _onOpen?.Invoke(); await Task.CompletedTask; } private async Task TransitionToHalfOpenAsync(CancellationToken ct) { // Ensure only one thread performs the transition. await _sync.WaitAsync(ct).ConfigureAwait(false); try { if (_state != CircuitState.Open) // another thread may have already changed it return; _state = CircuitState.HalfOpen; _halfOpenSuccesses = 0; _openedAt = null; // no longer "open" _onHalfOpen?.Invoke(); } finally { _sync.Release(); } } private async Task TransitionToClosedAsync() { // Assume lock already held. _state = CircuitState.Closed; _consecutiveFailures = 0; _halfOpenSuccesses = 0; _openedAt = null; _onClose?.Invoke(); await Task.CompletedTask; } } // --------------------------------------------------------------------------- // Custom exception – thrown when a call is attempted while the circuit is open. public sealed class CircuitBreakerOpenException : Exception { public CircuitBreakerOpenException() : base("The circuit breaker is open; calls are not allowed.") { } } ``` ### How It Works 1. **Entry point** (`ExecuteAsync`) first checks the current state via `IsCallAllowedAsync`. * If **Closed** → proceed. * If **Open** → if the timeout has elapsed, atomically move to **HalfOpen**; otherwise reject (or run fallback). * If **HalfOpen** → allow the call (the success/failure logic will decide the next state). 2. **After the delegate finishes** * On success → `OnSuccessAsync` resets the failure counter (Closed) or, in HalfOpen, counts successes and may close the circuit. * On failure → `OnFailureAsync` increments the consecutive‑failure counter (Closed) and may open the circuit; any failure in HalfOpen immediately re‑opens it. 3. **State transitions** are performed under a `SemaphoreSlim` lock (`_sync`) to guarantee thread‑safety without blocking threads (async‑compatible). 4. **Fallback** – optional `fallback` delegate is executed when the circuit is open. If no fallback is supplied, a `CircuitBreakerOpenException` is thrown. --- ## 4. Usage Example ```csharp // 1️⃣ Create a breaker: 3 consecutive failures → open for 10 seconds. var breaker = new AsyncCircuitBreaker( failureThreshold: 3, openTimeout: TimeSpan.FromSeconds(10), halfOpenSuccessThreshold: 1, onOpen: () => Console.WriteLine("Circuit opened!"), onClose: () => Console.WriteLine("Circuit closed."), onHalfOpen: () => Console.WriteLine("Circuit half‑open, testing...")); // 2️⃣ The operation we want to protect async Task<string> UnreliableCall(CancellationToken ct) { // Simulate flaky external service await Task.Delay(100, ct); if (Random.Shared.NextDouble() < 0.7) // 70 % chance to fail throw new InvalidOperationException("Remote service failed"); return "Success!"; } // 3️⃣ Optional fallback Task<string> Fallback(CancellationToken ct) => Task.FromResult("fallback result"); // 4️⃣ Run many calls for (int i = 0; i < 20; i++) { try { var result = await breaker.ExecuteAsync(UnreliableCall, fallback: Fallback); Console.WriteLine($"Call {i,2}: {result} (state={breaker.State})"); } catch (CircuitBreakerOpenException) { Console.WriteLine($"Call {i,2}: blocked (circuit open)"); } await Task.Delay(500); // pause a bit between calls } ``` **What you’ll see** * After three consecutive failures the circuit prints “Circuit opened!” and subsequent calls are blocked (or use the fallback). * After 10 seconds the breaker moves to HalfOpen, prints “Circuit half‑open…”, lets one test call through. * If that test succeeds, you get “Circuit closed.” and normal operation resumes; otherwise it re‑opens. --- ## 5. Extending / Instrumenting * **Metrics** – expose `ConsecutiveFailureCount`, `OpenedAt`, or add `long TotalSuccesses/Failures`. * **Logging** – replace the `Action` callbacks with `ILogger` calls. * **Bulkhead** – combine with a semaphore/queue to limit concurrent calls. * **Policy composition** – you can wrap this breaker inside a retry policy, or vice‑versa. --- ### TL;DR *The code above gives you a fully asynchronous, thread‑safe circuit breaker that tracks **consecutive** failures, automatically moves between **Closed → Open → HalfOpen → Closed**, supports a timeout, optional fallback, and simple callbacks for observability.*

Enhanced response (with ZPI)

**Solution Overview** We will create a reusable **asynchronous circuit‑breaker** that can be injected wherever it is needed. The implementation uses the **Microsoft.Extensions.Resilience** package (the official .NET resilience library that ships with the `ResiliencePipeline` and `ResiliencePipelineBuilder` types). The pipeline is built with a **circuit‑breaker policy** that: * Counts consecutive failures (exceptions or results that are considered failures). * Trips the circuit after a configurable number of failures (`failureThreshold`). * Keeps the circuit open for a configurable `breakDuration`. * After the break period, it goes to a **half‑open** state and allows a single trial call. * If the trial succeeds the circuit closes, otherwise it re‑opens. The pipeline is exposed through an interface `ICircuitBreakerService` that hides the underlying `ResiliencePipeline`. Consumers only need to call `ExecuteAsync<T>`. The code includes: 1. **Package reference** – `Microsoft.Extensions.Resilience` (and its dependency `Microsoft.Extensions.DependencyInjection`). 2. **Configuration POCO** – `CircuitBreakerOptions`. 3. **Service interface** – `ICircuitBreakerService`. 4. **Implementation** – `CircuitBreakerService` that builds the pipeline with `ResiliencePipelineBuilder`. 5. **Extension method** – `AddCircuitBreaker` for clean DI registration. 6. **Example usage** – a console app that demonstrates the breaker in action. All code is fully async, thread‑safe and follows idiomatic .NET patterns. --- ## 1. Project file (csproj) ```xml <Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> <TargetFramework>net8.0</TargetFramework> <ImplicitUsings>enable</ImplicitUsings> <Nullable>enable</Nullable> </PropertyGroup> <ItemGroup> <!-- Resilience pipeline library --> <PackageReference Include="Microsoft.Extensions.Resilience" Version="8.0.0" /> <!-- For DI --> <PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="8.0.0" /> </ItemGroup> </Project> ``` --- ## 2. Configuration POCO ```csharp namespace ResilienceDemo; /// <summary> /// Options that control the behaviour of the circuit‑breaker. /// </summary> public sealed class CircuitBreakerOptions { /// <summary> /// Number of consecutive failures required to open the circuit. /// </summary> public int FailureThreshold { get; set; } = 5; /// <summary> /// How long the circuit stays open before transitioning to half‑open. /// </summary> public TimeSpan BreakDuration { get; set; } = TimeSpan.FromSeconds(30); /// <summary> /// Optional: how many trial calls are allowed in half‑open state. /// The default (0) means a single trial call. /// </summary> public int? AllowedTrialCalls { get; set; } = null; } ``` --- ## 3. Service contract ```csharp using System.Threading.Tasks; namespace ResilienceDemo; /// <summary> /// Executes asynchronous operations through a circuit‑breaker pipeline. /// </summary> public interface ICircuitBreakerService { /// <summary> /// Executes the supplied <paramref name="operation"/> inside the circuit‑breaker. /// </summary> /// <typeparam name="TResult">Result type of the operation.</typeparam> /// <param name="operation">The async delegate to execute.</param> /// <returns>The result of the operation if the circuit is closed or the call succeeds.</returns> /// <exception cref="BrokenCircuitException"> /// Thrown when the circuit is open and the call is short‑circuited. /// </exception> Task<TResult> ExecuteAsync<TResult>(Func<CancellationToken, Task<TResult>> operation); } ``` --- ## 4. Implementation – `CircuitBreakerService` ```csharp using Microsoft.Extensions.Options; using Microsoft.Extensions.Resilience; using Microsoft.Extensions.Resilience.CircuitBreaker; using System; using System.Threading; using System.Threading.Tasks; namespace ResilienceDemo; /// <summary> /// Concrete implementation that builds a <see cref="ResiliencePipeline{TResult}"/> /// containing a circuit‑breaker policy. /// </summary> internal sealed class CircuitBreakerService : ICircuitBreakerService, IDisposable { private readonly ResiliencePipeline _pipeline; private readonly ResiliencePipelineBuilder _builder; private readonly CircuitBreakerOptions _options; private bool _disposed; public CircuitBreakerService(IOptions<CircuitBreakerOptions> options) { _options = options?.Value ?? throw new ArgumentNullException(nameof(options)); // Build a pipeline that works for any TResult (non‑generic pipeline) _builder = new ResiliencePipelineBuilder(); // Add the circuit‑breaker policy _builder.AddCircuitBreaker(new CircuitBreakerStrategyOptions { FailureRatio = 1.0, // we care about *consecutive* failures, not ratio SamplingDuration = TimeSpan.MaxValue, // effectively disables time‑window sampling MinimumThroughput = _options.FailureThreshold, BreakDuration = _options.BreakDuration, // The built‑in circuit‑breaker works on *any* exception. // To treat specific results as failures you could add a ResultPredicate. // Here we keep it simple and rely on exceptions. // AllowedTrialCalls is only available in .NET 9 preview; we emulate with // the default behaviour (single trial call) when null. }); // Build the pipeline (non‑generic, can be used for any TResult) _pipeline = _builder.Build(); } public async Task<TResult> ExecuteAsync<TResult>(Func<CancellationToken, Task<TResult>> operation) { if (_disposed) throw new ObjectDisposedException(nameof(CircuitBreakerService)); // The generic overload of ExecuteAsync creates a typed pipeline on‑the‑fly. // This avoids having to create a separate generic pipeline for each TResult. return await _pipeline.ExecuteAsync(operation); } public void Dispose() { if (_disposed) return; _pipeline.Dispose(); // disposes any inner resources _disposed = true; } } ``` **Key points** * `ResiliencePipelineBuilder` is used to add a **circuit‑breaker** strategy. * `FailureRatio = 1.0` together with `MinimumThroughput = FailureThreshold` makes the breaker trigger after *exactly* `FailureThreshold` consecutive failures. * `BreakDuration` controls how long the circuit stays open. * The pipeline is non‑generic; `ExecuteAsync<TResult>` creates a typed execution context internally, so the same pipeline can be reused for any result type. * The service implements `IDisposable` to clean up the pipeline. --- ## 5. DI registration extension ```csharp using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; namespace ResilienceDemo; /// <summary> /// Extension methods to register the circuit‑breaker service in an IServiceCollection. /// </summary> public static class CircuitBreakerServiceCollectionExtensions { /// <summary> /// Registers <see cref="ICircuitBreakerService"/> with the supplied options. /// </summary> /// <param name="services">The service collection.</param> /// <param name="configure">Configuration delegate for <see cref="CircuitBreakerOptions"/>.</param> /// <returns>The same service collection for chaining.</returns> public static IServiceCollection AddCircuitBreaker(this IServiceCollection services, Action<CircuitBreakerOptions> configure) { services.Configure(configure); services.AddSingleton<ICircuitBreakerService, CircuitBreakerService>(); return services; } } ``` --- ## 6. Example console program ```csharp using Microsoft.Extensions.DependencyInjection; using ResilienceDemo; using System; using System.Net.Http; using System.Threading; using System.Threading.Tasks; class Program { static async Task Main() { // ------------------------------------------------- // 1️⃣ Set up DI container // ------------------------------------------------- var services = new ServiceCollection(); services.AddHttpClient(); // for demo purposes services.AddCircuitBreaker(opts => { opts.FailureThreshold = 3; // trip after 3 failures opts.BreakDuration = TimeSpan.FromSeconds(10); }); var provider = services.BuildServiceProvider(); var breaker = provider.GetRequiredService<ICircuitBreakerService>(); var http = provider.GetRequiredService<IHttpClientFactory>().CreateClient(); // ------------------------------------------------- // 2️⃣ Simulate a flaky endpoint // ------------------------------------------------- var rnd = new Random(); async Task<string> UnreliableCall(CancellationToken ct) { // 50 % chance of failure if (rnd.NextDouble() < 0.5) throw new HttpRequestException("Simulated network failure"); // otherwise succeed await Task.Delay(100, ct); return "Success!"; } // ------------------------------------------------- // 3️⃣ Run a loop to see the breaker in action // ------------------------------------------------- for (int i = 1; i <= 12; i++) { try { var result = await breaker.ExecuteAsync<string>(UnreliableCall); Console.WriteLine($"{i,2}: Call succeeded – {result}"); } catch (BrokenCircuitException) { Console.WriteLine($"{i,2}: Call short‑circuited – circuit is OPEN"); } catch (Exception ex) { Console.WriteLine($"{i,2}: Call failed – {ex.GetType().Name}: {ex.Message}"); } await Task.Delay(500); // small pause between attempts } // Keep console open Console.WriteLine("Demo finished. Press any key..."); Console.ReadKey(); } } ``` **What the demo shows** * The first three failures cause the circuit to open. * Subsequent calls within the 10‑second break period immediately throw `BrokenCircuitException`. * After the break duration expires, the next call is allowed (half‑open). If it succeeds, the circuit closes; if it fails, the circuit re‑opens. --- ## 7. Summary * **`ResiliencePipelineBuilder`** is used to configure a circuit‑breaker that tracks *consecutive* failures. * The **`CircuitBreakerService`** wraps the pipeline behind a clean, async‑friendly interface. * **DI registration** (`AddCircuitBreaker`) makes the service easy to consume throughout an application. * The example console app demonstrates the behaviour and can be swapped for any real operation (database calls, HTTP requests, etc.). This implementation satisfies the request for an **asynchronous circuit breaker that tracks consecutive failures** while leveraging the modern .NET `ResiliencePipeline` infrastructure.

Grader chain

Each grader reads the same baseline + enhanced pair and casts an independent verdict. Disagreement is the framework's own signal — not noise to be averaged away.

V1claude-sonnet-4-6
Worse
V4qwen2.5-coder-7b-instruct
Better

Comments

Our grader said what it said. What do you say? Comment as a guest below.

No comments yet. Be the first to say what you make of this round.

← back to all rounds