114 lines
4.4 KiB
C#
Executable File
114 lines
4.4 KiB
C#
Executable File
using System;
|
|
using System.IO;
|
|
using System.Net.Sockets;
|
|
using System.Threading;
|
|
using System.Threading.Tasks;
|
|
using Microsoft.Extensions.Logging;
|
|
using Microsoft.Extensions.Logging.Abstractions;
|
|
using ZB.MOM.WW.CBDDC.Core.Exceptions;
|
|
using ZB.MOM.WW.CBDDC.Core.Network;
|
|
using TimeoutException = ZB.MOM.WW.CBDDC.Core.Exceptions.TimeoutException;
|
|
|
|
namespace ZB.MOM.WW.CBDDC.Core.Resilience;
|
|
|
|
/// <summary>
|
|
/// Provides retry logic for transient failures.
|
|
/// </summary>
|
|
public class RetryPolicy : IRetryPolicy
|
|
{
|
|
private readonly ILogger<RetryPolicy> _logger;
|
|
private readonly IPeerNodeConfigurationProvider _peerNodeConfigurationProvider;
|
|
|
|
/// <summary>
|
|
/// Initializes a new instance of the <see cref="RetryPolicy" /> class.
|
|
/// </summary>
|
|
/// <param name="peerNodeConfigurationProvider">The provider for retry configuration values.</param>
|
|
/// <param name="logger">The logger instance.</param>
|
|
public RetryPolicy(IPeerNodeConfigurationProvider peerNodeConfigurationProvider,
|
|
ILogger<RetryPolicy>? logger = null)
|
|
{
|
|
_logger = logger ?? NullLogger<RetryPolicy>.Instance;
|
|
_peerNodeConfigurationProvider = peerNodeConfigurationProvider
|
|
?? throw new ArgumentNullException(nameof(peerNodeConfigurationProvider));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Executes an operation with retry logic.
|
|
/// </summary>
|
|
/// <typeparam name="T">The result type returned by the operation.</typeparam>
|
|
/// <param name="operation">The asynchronous operation to execute.</param>
|
|
/// <param name="operationName">The operation name used for logging.</param>
|
|
/// <param name="cancellationToken">A token used to cancel retry delays.</param>
|
|
public async Task<T> ExecuteAsync<T>(
|
|
Func<Task<T>> operation,
|
|
string operationName,
|
|
CancellationToken cancellationToken = default)
|
|
{
|
|
var config = await _peerNodeConfigurationProvider.GetConfiguration();
|
|
Exception? lastException = null;
|
|
|
|
for (var attempt = 1; attempt <= config.RetryAttempts; attempt++)
|
|
try
|
|
{
|
|
_logger.LogDebug("Executing {Operation} (attempt {Attempt}/{Max})",
|
|
operationName, attempt, config.RetryAttempts);
|
|
|
|
return await operation();
|
|
}
|
|
catch (Exception ex) when (attempt < config.RetryAttempts && IsTransient(ex))
|
|
{
|
|
lastException = ex;
|
|
int delay = config.RetryDelayMs * attempt; // Exponential backoff
|
|
|
|
_logger.LogWarning(ex,
|
|
"Operation {Operation} failed (attempt {Attempt}/{Max}). Retrying in {Delay}ms...",
|
|
operationName, attempt, config.RetryAttempts, delay);
|
|
|
|
await Task.Delay(delay, cancellationToken);
|
|
}
|
|
|
|
if (lastException != null)
|
|
_logger.LogError(lastException,
|
|
"Operation {Operation} failed after {Attempts} attempts",
|
|
operationName, config.RetryAttempts);
|
|
else
|
|
_logger.LogError(
|
|
"Operation {Operation} failed after {Attempts} attempts",
|
|
operationName, config.RetryAttempts);
|
|
|
|
throw new CBDDCException("RETRY_EXHAUSTED",
|
|
$"Operation '{operationName}' failed after {config.RetryAttempts} attempts",
|
|
lastException!);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Executes an operation with retry logic (void return).
|
|
/// </summary>
|
|
/// <param name="operation">The asynchronous operation to execute.</param>
|
|
/// <param name="operationName">The operation name used for logging.</param>
|
|
/// <param name="cancellationToken">A token used to cancel retry delays.</param>
|
|
public async Task ExecuteAsync(
|
|
Func<Task> operation,
|
|
string operationName,
|
|
CancellationToken cancellationToken = default)
|
|
{
|
|
await ExecuteAsync(async () =>
|
|
{
|
|
await operation();
|
|
return true;
|
|
}, operationName, cancellationToken);
|
|
}
|
|
|
|
private static bool IsTransient(Exception ex)
|
|
{
|
|
// Network errors are typically transient
|
|
if (ex is NetworkException or SocketException or IOException)
|
|
return true;
|
|
|
|
// Timeout errors are transient
|
|
if (ex is TimeoutException or OperationCanceledException)
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
} |