Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 9 additions & 9 deletions src/DNS-BLM.Infrastructure/Services/RetryService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,20 +9,20 @@
/// </summary>
/// <typeparam name="TResult">The return type of the function.</typeparam>
/// <param name="func">The asynchronous function to execute.</param>
/// <param name="maxAttempts">The maximum number of attempts to make. Must be 1 or higher. Defaults to 3</param>
/// <param name="maxRetrys">The maximum number of attempts to make. Must be 1 or higher. Defaults to 3</param>
/// <param name="cancellationToken">A <see cref="CancellationToken"/> to observe while waiting for the task to complete.</param>
/// <returns>The result of the function if successful, or the result of the last attempt if all retries fail.</returns>
/// <remarks>
/// This method retries the provided function up to <paramref name="maxAttempts"/> times.
/// This method retries the provided function up to <paramref name="maxRetrys"/> times.
/// It swallows exceptions on intermediate attempts and applies an exponential backoff delay before retrying.
/// </remarks>
public async Task<TResult?> Retry<TResult>(Func<Task<RetryResult<TResult>?>> func, int maxAttempts = 3, CancellationToken cancellationToken = default)
public async Task<TResult?> Retry<TResult>(Func<Task<RetryResult<TResult>?>> func, int maxRetrys = 3, CancellationToken cancellationToken = default)
{
if (maxAttempts <= 0)
throw new ArgumentOutOfRangeException(nameof(maxAttempts));
if (maxRetrys <= 0)
throw new ArgumentOutOfRangeException(nameof(maxRetrys));

RetryResult<TResult>? result = new() { };
for (int attempt = 1; attempt <= maxAttempts; attempt++)
for (int attempt = 0; attempt <= maxRetrys; attempt++)
{
try
{
Expand All @@ -33,12 +33,12 @@
return result.Result;
}
}
catch when (attempt < maxAttempts)
catch when (attempt < maxRetrys)
{
// Swallow exception and retry
}

if (attempt < maxAttempts)
if (attempt < maxRetrys)
{
var delay = CalculateBackoffTimeSeconds(attempt);
logger.LogDebug("Retry not successful - Delay for {Delay} seconds", delay);
Expand All @@ -57,7 +57,7 @@
/// <returns></returns>
private int CalculateBackoffTimeSeconds(int numberOfAttempts)
{
numberOfAttempts += 1; // Increase attempt to skip small delays
numberOfAttempts += 2; // Increase attempt to skip small delays
int totalSeconds = 0;

for (int attempt = 1; attempt <= numberOfAttempts; attempt++)
Expand All @@ -72,6 +72,6 @@

public class RetryResult<T>()
{
public T Result { get; set; }

Check warning on line 75 in src/DNS-BLM.Infrastructure/Services/RetryService.cs

View workflow job for this annotation

GitHub Actions / build

Non-nullable property 'Result' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the property as nullable.
public bool IsSuccess { get; set; }
}
Expand Down
24 changes: 12 additions & 12 deletions test/Tests/Test/RetryServiceTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ public async Task Retry_ExecutesFunctionSuccessfullyOnFirstAttempt()
};

// Act
var result = await _retryService.Retry(func, 3);
var result = await _retryService.Retry(func, 1);

// Assert
Assert.Equal(expectedResult, result);
Expand All @@ -52,7 +52,7 @@ public async Task Retry_ExecutesFunctionSuccessfullyAfterRetries()
};

// Act
var result = await _retryService.Retry(func, 3);
var result = await _retryService.Retry(func, 2);

// Assert
Assert.Equal(expectedResult, result);
Expand All @@ -72,9 +72,9 @@ public async Task Retry_ThrowsExceptionOnLastAttemptIfAllFailViaException()
};

// Act & Assert
var exception = await Assert.ThrowsAsync<InvalidOperationException>(() => _retryService.Retry(func, 3));
var exception = await Assert.ThrowsAsync<InvalidOperationException>(() => _retryService.Retry(func, 1));
Assert.Equal(expectedExceptionMessage, exception.Message);
Assert.Equal(3, callCount);
Assert.Equal(2, callCount);
}

[Fact]
Expand All @@ -89,11 +89,11 @@ public async Task Retry_ReturnsDefaultOnLastAttemptIfAllFailViaIsSuccessFalse()
};

// Act
var result = await _retryService.Retry(func, 3);
var result = await _retryService.Retry(func, 1);

// Assert
Assert.Null(result); // Default for string is null
Assert.Equal(3, callCount); // Called maxAttempts times based on the retry logic
Assert.Equal(2, callCount); // Called maxAttempts times based on the retry logic
}

[Fact]
Expand All @@ -115,7 +115,7 @@ public async Task Retry_NoDelayOnLastAttemptOrSuccess()
};

var startTime = DateTime.UtcNow;
var result = await _retryService.Retry(func , 2); // 1 unsuccessful, 1 successful attempt
var result = await _retryService.Retry(func , 1); // 1 unsuccessful, 1 successful attempt

// Assert
Assert.Equal(expectedResult, result);
Expand All @@ -138,7 +138,7 @@ public async Task Retry_WhenFuncReturnsIsSuccessTrueOnFirstTry_NoFurtherCalls()
return Task.FromResult<RetryResult<string>?>(new RetryResult<string> { Result = "Result", IsSuccess = true });
};
// Act
var result = await _retryService.Retry(func, 5);
var result = await _retryService.Retry(func, 1);
// Assert
Assert.NotNull(result);
Assert.Equal("Result", result);
Expand All @@ -159,7 +159,7 @@ public async Task Retry_WhenFuncReturnsIsSuccessFalseOnFirstTry_RetriesUntilIsSu
};

// Act
var result = await _retryService.Retry(func, 3);
var result = await _retryService.Retry(func, 1);

// Assert
Assert.Equal("Final Result", result);
Expand All @@ -178,11 +178,11 @@ public async Task Retry_WhenFuncAlwaysReturnsIsSuccessFalse_ReturnsDefaultOnMaxA
};

// Act
var result = await _retryService.Retry(func, 3);
var result = await _retryService.Retry(func, 1);

// Assert
Assert.Null(result); // Default for string
Assert.Equal(3, callCount); // Called maxAttempts times, always returning IsSuccess=false.
Assert.Equal(2, callCount); // Called maxAttempts times, always returning IsSuccess=false.
}

[Fact]
Expand All @@ -202,7 +202,7 @@ public async Task Retry_WhenFuncReturnsNullRetryResult_RetriesUntilNonNullReturn
};

// Act
var result = await _retryService.Retry(func, 3);
var result = await _retryService.Retry(func, 2);

// Assert
Assert.Equal("Actual Result", result);
Expand Down
Loading