feat(lmxproxy): phase 1 — v2 protocol types and domain model
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,193 @@
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Polly;
|
||||
using Polly.Timeout;
|
||||
using Serilog;
|
||||
|
||||
namespace ZB.MOM.WW.LmxProxy.Host.Services
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides retry policies for resilient operations
|
||||
/// </summary>
|
||||
public static class RetryPolicies
|
||||
{
|
||||
private static readonly ILogger Logger = Log.ForContext(typeof(RetryPolicies));
|
||||
|
||||
/// <summary>
|
||||
/// Creates a retry policy with exponential backoff for read operations
|
||||
/// </summary>
|
||||
public static IAsyncPolicy<T> CreateReadPolicy<T>()
|
||||
{
|
||||
return Policy<T>
|
||||
.Handle<Exception>(ex => !(ex is ArgumentException || ex is InvalidOperationException))
|
||||
.WaitAndRetryAsync(
|
||||
3,
|
||||
retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt - 1)),
|
||||
(outcome, timespan, retryCount, context) =>
|
||||
{
|
||||
Exception? exception = outcome.Exception;
|
||||
Logger.Warning(exception,
|
||||
"Read operation retry {RetryCount} after {DelayMs}ms. Operation: {Operation}",
|
||||
retryCount,
|
||||
timespan.TotalMilliseconds,
|
||||
context.ContainsKey("Operation") ? context["Operation"] : "Unknown");
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a retry policy with exponential backoff for write operations
|
||||
/// </summary>
|
||||
public static IAsyncPolicy CreateWritePolicy()
|
||||
{
|
||||
return Policy
|
||||
.Handle<Exception>(ex => !(ex is ArgumentException || ex is InvalidOperationException))
|
||||
.WaitAndRetryAsync(
|
||||
3,
|
||||
retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)),
|
||||
(exception, timespan, retryCount, context) =>
|
||||
{
|
||||
Logger.Warning(exception,
|
||||
"Write operation retry {RetryCount} after {DelayMs}ms. Operation: {Operation}",
|
||||
retryCount,
|
||||
timespan.TotalMilliseconds,
|
||||
context.ContainsKey("Operation") ? context["Operation"] : "Unknown");
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a retry policy for connection operations with longer delays
|
||||
/// </summary>
|
||||
public static IAsyncPolicy CreateConnectionPolicy()
|
||||
{
|
||||
return Policy
|
||||
.Handle<Exception>()
|
||||
.WaitAndRetryAsync(
|
||||
5,
|
||||
retryAttempt =>
|
||||
{
|
||||
// 2s, 4s, 8s, 16s, 32s
|
||||
var delay = TimeSpan.FromSeconds(Math.Min(32, Math.Pow(2, retryAttempt)));
|
||||
return delay;
|
||||
},
|
||||
(exception, timespan, retryCount, context) =>
|
||||
{
|
||||
Logger.Warning(exception,
|
||||
"Connection retry {RetryCount} after {DelayMs}ms",
|
||||
retryCount,
|
||||
timespan.TotalMilliseconds);
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a circuit breaker policy for protecting against repeated failures
|
||||
/// </summary>
|
||||
public static IAsyncPolicy<T> CreateCircuitBreakerPolicy<T>()
|
||||
{
|
||||
return Policy<T>
|
||||
.Handle<Exception>()
|
||||
.CircuitBreakerAsync(
|
||||
5,
|
||||
TimeSpan.FromSeconds(30),
|
||||
(result, timespan) =>
|
||||
{
|
||||
Logger.Error(result.Exception,
|
||||
"Circuit breaker opened for {BreakDurationSeconds}s due to repeated failures",
|
||||
timespan.TotalSeconds);
|
||||
},
|
||||
() => { Logger.Information("Circuit breaker reset - resuming normal operations"); },
|
||||
() => { Logger.Information("Circuit breaker half-open - testing operation"); });
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a combined policy with retry and circuit breaker
|
||||
/// </summary>
|
||||
public static IAsyncPolicy<T> CreateCombinedPolicy<T>()
|
||||
{
|
||||
IAsyncPolicy<T> retry = CreateReadPolicy<T>();
|
||||
IAsyncPolicy<T> circuitBreaker = CreateCircuitBreakerPolicy<T>();
|
||||
|
||||
// Wrap retry around circuit breaker
|
||||
// This means retry happens first, and if all retries fail, it counts toward the circuit breaker
|
||||
return Policy.WrapAsync(retry, circuitBreaker);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a timeout policy for operations
|
||||
/// </summary>
|
||||
public static IAsyncPolicy CreateTimeoutPolicy(TimeSpan timeout)
|
||||
{
|
||||
return Policy
|
||||
.TimeoutAsync(
|
||||
timeout,
|
||||
TimeoutStrategy.Pessimistic,
|
||||
async (context, timespan, task) =>
|
||||
{
|
||||
Logger.Warning(
|
||||
"Operation timed out after {TimeoutMs}ms. Operation: {Operation}",
|
||||
timespan.TotalMilliseconds,
|
||||
context.ContainsKey("Operation") ? context["Operation"] : "Unknown");
|
||||
|
||||
if (task != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
await task;
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Ignore exceptions from the timed-out task
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a bulkhead policy to limit concurrent operations
|
||||
/// </summary>
|
||||
public static IAsyncPolicy CreateBulkheadPolicy(int maxParallelization, int maxQueuingActions = 100)
|
||||
{
|
||||
return Policy
|
||||
.BulkheadAsync(
|
||||
maxParallelization,
|
||||
maxQueuingActions,
|
||||
context =>
|
||||
{
|
||||
Logger.Warning(
|
||||
"Bulkhead rejected operation. Max parallelization: {MaxParallel}, Queue: {MaxQueue}",
|
||||
maxParallelization,
|
||||
maxQueuingActions);
|
||||
return Task.CompletedTask;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Extension methods for applying retry policies
|
||||
/// </summary>
|
||||
public static class RetryPolicyExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Executes an operation with retry policy
|
||||
/// </summary>
|
||||
public static async Task<T> ExecuteWithRetryAsync<T>(
|
||||
this IAsyncPolicy<T> policy,
|
||||
Func<Task<T>> operation,
|
||||
string operationName)
|
||||
{
|
||||
var context = new Context { ["Operation"] = operationName };
|
||||
return await policy.ExecuteAsync(async ctx => await operation(), context);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Executes an operation with retry policy (non-generic)
|
||||
/// </summary>
|
||||
public static async Task ExecuteWithRetryAsync(
|
||||
this IAsyncPolicy policy,
|
||||
Func<Task> operation,
|
||||
string operationName)
|
||||
{
|
||||
var context = new Context { ["Operation"] = operationName };
|
||||
await policy.ExecuteAsync(async ctx => await operation(), context);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user