Round #489
09 May 2026, 19:44· csharp· qwen2.5-coder-7b-instruct· r13-clean-hints
- Complexity
- band 0
- ZP / UO / OBS
- 4 / 2 / 1
- Journey weight
- 384
- Lightshift ΔL
- +0.50
Prompt
Write a worker that retries failed outbox messages with exponential backoff
Zero-point index (what the engine surfaced)
ZPI-ZPI-0013 | 2026-05-09T19:44:03Z
Write a worker that retries failed outbox messages with exponential backoff
ZERO POINT — 4 entries (structural invariants)
● hsl(296.42, 100%, 46%) ResiliencePipeline [contract]
● hsl(211.57, 100%, 46%) ResiliencePipelineBuilder [contract]
● hsl(136.10, 100%, 50%) DbContext [contract]
● hsl(61.72, 100%, 43%) BackgroundService [contract]
UNOBSERVED — 2 entries (implied truths)
○ hsl(27.69, 100%, 50%) async [feature]
○ hsl(3.75, 100%, 38%) CancellationToken [feature]
OBSERVED — 1 entries (explicit in request)
◉ hsl(98.46, 100%, 44%) with [feature]
COMPLEXITY │ 7 entries │ spread: 95° │ band: LOW
Baseline response (no ZPI)
Certainly! Below is an example of a worker in C# that retries failed outbox messages using exponential backoff. This example assumes you have a basic understanding of how to handle outbox messages and a message queue system (e.g., RabbitMQ, Azure Service Bus).
First, let's define the necessary classes and interfaces:
1. `IOutboxMessage`: Represents an outbox message.
2. `IMessageQueue`: Represents a message queue system.
```csharp
public interface IOutboxMessage
{
Guid Id { get; }
string Content { get; }
DateTime? RetryAt { get; set; }
}
public interface IMessageQueue
{
void SendMessage(IOutboxMessage message);
IEnumerable<IOutboxMessage> GetFailedMessages();
}
```
Next, let's implement the worker:
```csharp
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
public class OutboxWorker
{
private readonly IMessageQueue _messageQueue;
private readonly int _maxRetries;
private readonly TimeSpan _initialDelay;
public OutboxWorker(IMessageQueue messageQueue, int maxRetries = 5, TimeSpan initialDelay = default)
{
_messageQueue = messageQueue ?? throw new ArgumentNullException(nameof(messageQueue));
_maxRetries = maxRetries;
_initialDelay = initialDelay == default ? TimeSpan.FromSeconds(1) : initialDelay;
}
public async Task RunAsync()
{
while (true)
{
var failedMessages = _messageQueue.GetFailedMessages().ToList();
foreach (var message in failedMessages)
{
if (message.RetryAt.HasValue && DateTime.UtcNow < message.RetryAt.Value)
continue;
try
{
// Process the message (e.g., send it to a queue, database, etc.)
await ProcessMessageAsync(message);
_messageQueue.SendMessage(message); // Re-enqueue the message for confirmation
}
catch (Exception ex)
{
HandleFailure(message, ex);
}
}
await Task.Delay(_initialDelay);
}
}
private async Task ProcessMessageAsync(IOutboxMessage message)
{
// Implement your message processing logic here
Console.WriteLine($"Processing message: {message.Id}");
// Simulate a failure for demonstration purposes
if (DateTime.UtcNow.Second % 2 == 0)
throw new InvalidOperationException("Simulated processing error");
}
private void HandleFailure(IOutboxMessage message, Exception ex)
{
if (message.RetryCount >= _maxRetries)
{
Console.WriteLine($"Max retries reached for message: {message.Id}. Giving up.");
return;
}
var retryDelay = CalculateExponentialBackoff(message.RetryCount);
message.RetryAt = DateTime.UtcNow.Add(retryDelay);
message.RetryCount++;
Console.WriteLine($"Failed to process message: {message.Id}. Retrying in {retryDelay.TotalSeconds} seconds.");
}
private TimeSpan CalculateExponentialBackoff(int retryCount)
{
return TimeSpan.FromSeconds(Math.Pow(2, retryCount) * _initialDelay.TotalSeconds);
}
}
```
Finally, let's implement a simple `IMessageQueue` implementation using an in-memory list for demonstration purposes:
```csharp
public class InMemoryMessageQueue : IMessageQueue
{
private readonly List<IOutboxMessage> _messages = new List<IOutboxMessage>();
public void SendMessage(IOutboxMessage message)
{
_messages.Add(message);
}
public IEnumerable<IOutboxMessage> GetFailedMessages()
{
return _messages.Where(m => m.RetryAt.HasValue && DateTime.UtcNow >= m.RetryAt.Value);
}
}
```
To use the worker, you can create an instance of `OutboxWorker` and call its `RunAsync` method:
```csharp
public class Program
{
public static async Task Main(string[] args)
{
var messageQueue = new InMemoryMessageQueue();
var worker = new OutboxWorker(messageQueue);
// Simulate sending a failed outbox message
var message = new OutboxMessage { Id = Guid.NewGuid(), Content = "Test message" };
message.RetryCount = 0;
message.RetryAt = DateTime.UtcNow.AddSeconds(-1); // Mark as failed immediately
messageQueue.SendMessage(message);
await worker.RunAsync();
}
}
```
This example demonstrates a basic implementation of an outbox worker with exponential backoff. You can extend and customize it according to your specific requirements, such as integrating with a real message queue system or handling different types of exceptions.
Enhanced response (with ZPI)
To implement a worker that retries failed outbox messages with exponential backoff, we can use the `BackgroundService` class from the .NET framework. This class allows us to create a long-running service that performs background tasks.
We will also use the `ResiliencePipelineBuilder` and `ResiliencePipeline` classes from the Polly library to implement the retry logic with exponential backoff.
Here's the implementation:
```csharp
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Polly;
using Polly.Retry;
using System;
using System.Threading;
using System.Threading.Tasks;
public class OutboxWorker : BackgroundService
{
private readonly IOutboxRepository _outboxRepository;
private readonly ResiliencePipeline _retryPolicy;
public OutboxWorker(IOutboxRepository outboxRepository, ResiliencePipeline retryPolicy)
{
_outboxRepository = outboxRepository ?? throw new ArgumentNullException(nameof(outboxRepository));
_retryPolicy = retryPolicy ?? throw new ArgumentNullException(nameof(retryPolicy));
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
try
{
var failedMessages = await _outboxRepository.GetFailedMessagesAsync(stoppingToken);
foreach (var message in failedMessages)
{
await _retryPolicy.ExecuteAsync(async () =>
{
// Process the message here
await _outboxRepository.MarkMessageAsProcessedAsync(message.Id, stoppingToken);
}, stoppingToken);
}
}
catch (Exception ex)
{
// Log the exception and retry later
Console.WriteLine($"Error processing outbox messages: {ex}");
}
// Wait for a short period before checking again
await Task.Delay(TimeSpan.FromSeconds(10), stoppingToken);
}
}
}
public static class OutboxWorkerExtensions
{
public static IServiceCollection AddOutboxWorker(this IServiceCollection services)
{
services.AddHostedService<OutboxWorker>();
// Configure Polly retry policy with exponential backoff
services.AddSingleton<ResiliencePipeline>(provider =>
Policy
.Handle<Exception>()
.WaitAndRetryAsync(5, retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt))));
return services;
}
}
```
Explanation:
1. **OutboxWorker Class**:
- This class inherits from `BackgroundService`, which provides the infrastructure for running a long-running background task.
- It takes an `IOutboxRepository` and a `ResiliencePipeline` as dependencies.
- The `ExecuteAsync` method is overridden to implement the worker's logic. It continuously retrieves failed outbox messages, retries processing them using the Polly policy, and logs any exceptions.
2. **OutboxWorkerExtensions Class**:
- This class provides an extension method to register the `OutboxWorker` and configure the Polly retry policy.
- The `AddHostedService<OutboxWorker>` method registers the worker as a hosted service.
- The `ResiliencePipeline` is configured with exponential backoff using Polly's `WaitAndRetryAsync` method.
3. **IOutboxRepository Interface**:
- This interface defines methods for interacting with the outbox repository, such as retrieving failed messages and marking them as processed.
4. **DI Registration**:
- The `AddOutboxWorker` extension method registers the worker and configures the Polly retry policy in the dependency injection container.
This implementation provides a robust solution for retrying failed outbox messages with exponential backoff using a background service and Polly's resilience features.
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.
V1samba/Meta-Llama-3.3-70B-Instruct
BetterV4qwen2.5-coder-3b-instruct
BetterV5openrouter/qwen/qwen3-235b-a22b-2507
BetterV7qwen2.5-coder-7b-instruct
BetterV8openai/gpt-4.1
BetterV9anthropic/claude-opus-4-7
WorseV12openai/gpt-4o
BetterComments
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.