Pattern 10: Error Handling¶
Retry for TCP request-response¶
For request-reply adapters built on TcpProtocolBase/ProtocolChannel, prefer SendOptions.RetryPolicy and keep retry behavior at the channel level.
Avoid wrapping Send(...) in custom retry loops. That duplicates queue/timeout behavior and can produce hard-to-debug retry storms.
var response = await Send(request, new SendOptions
{
Timeout = TimeSpan.FromSeconds(2),
RetryPolicy = RetryPolicy.Exponential(
maxAttempts: 4,
initialDelay: TimeSpan.FromMilliseconds(100))
}, ct);
Choose policy by failure mode:
RetryPolicy.Nonefor strict single-attempt commandsRetryPolicy.Fixed(...)for stable but occasionally busy linksRetryPolicy.Exponential(...)for bursty load or temporary device pressurenew RetryPolicy(maxAttempts, attempt => ...)for protocol-specific delay rules
Circuit Breaker¶
internal sealed class CircuitBreaker
{
private int _failureCount;
private DateTimeOffset _lastFailureTime;
private readonly TimeProvider _timeProvider;
private readonly int _threshold = 5;
private readonly TimeSpan _resetTimeout = TimeSpan.FromMinutes(1);
public CircuitBreaker(TimeProvider timeProvider)
{
_timeProvider = timeProvider;
}
public async Task<T> Execute<T>(Func<Task<T>> operation)
{
if (_failureCount >= _threshold)
{
if (_timeProvider.GetUtcNow() - _lastFailureTime < _resetTimeout)
throw new InvalidOperationException("Circuit breaker open");
// try reset
_failureCount = 0;
}
try
{
var result = await operation();
_failureCount = 0; // success - reset counter
return result;
}
catch
{
_failureCount++;
_lastFailureTime = _timeProvider.GetUtcNow();
throw;
}
}
}
Graceful Degradation¶
Handle partial failures without stopping the adapter:
protected override async Task OnEventReceived(DeviceEvent evt)
{
try
{
var door = FindDoor(evt.DoorId);
if (door == null)
{
_logger.LogWarning("Door {DoorId} not found for event {Code}",
evt.DoorId, evt.Code);
return; // continue processing other events
}
await ProcessEvent(door, evt);
}
catch (Exception ex)
{
_logger.LogError(ex, "Failed to process event {Code} for door {DoorId}",
evt.Code, evt.DoorId);
// don't rethrow - continue processing
}
}
Best Practices¶
- Log at appropriate levels:
- Trace: Detailed flow
- Debug: Diagnostic info
- Info: Normal operations
- Warning: Recoverable issues
- Error: Failed operations
-
Critical: System failures
-
Preserve context:
-
Use structured logging:
-
Timeout long operations:
See Also¶
- Pattern 3: Connection Management - Connection retry