Initial import of the CBDDC codebase with docs and tests. Add a .NET-focused gitignore to keep generated artifacts out of source control.
Some checks failed
CI / verify (push) Has been cancelled
Some checks failed
CI / verify (push) Has been cancelled
This commit is contained in:
116
src/ZB.MOM.WW.CBDDC.Core/Resilience/RetryPolicy.cs
Executable file
116
src/ZB.MOM.WW.CBDDC.Core/Resilience/RetryPolicy.cs
Executable file
@@ -0,0 +1,116 @@
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using ZB.MOM.WW.CBDDC.Core.Exceptions;
|
||||
using ZB.MOM.WW.CBDDC.Core.Network;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
|
||||
namespace ZB.MOM.WW.CBDDC.Core.Resilience;
|
||||
|
||||
/// <summary>
|
||||
/// Provides retry logic for transient failures.
|
||||
/// </summary>
|
||||
public class RetryPolicy : IRetryPolicy
|
||||
{
|
||||
private readonly IPeerNodeConfigurationProvider _peerNodeConfigurationProvider;
|
||||
private readonly ILogger<RetryPolicy> _logger;
|
||||
|
||||
/// <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 (int 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;
|
||||
var 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 System.Net.Sockets.SocketException or System.IO.IOException)
|
||||
return true;
|
||||
|
||||
// Timeout errors are transient
|
||||
if (ex is Exceptions.TimeoutException or OperationCanceledException)
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user