-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
9c6f963
commit 537fc1c
Showing
8 changed files
with
198 additions
and
4 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
4 changes: 3 additions & 1 deletion
4
src/SnD.Sdk/Logs/Providers/Configurations/DefaultLoggingConfiguration.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,68 @@ | ||
using System; | ||
using System.Threading; | ||
using System.Threading.Tasks; | ||
using Microsoft.Extensions.Logging; | ||
using Polly; | ||
using Polly.RateLimit; | ||
using Polly.Retry; | ||
using Snd.Sdk.Tasks; | ||
|
||
namespace Snd.Sdk.Storage.Cql; | ||
|
||
/// <summary> | ||
/// Provides extension methods for CQL API. | ||
/// </summary> | ||
public static class CqlApiExtensions | ||
{ | ||
/// <summary> | ||
/// Executes a CQL API call with retry and rate limit policies. | ||
/// </summary> | ||
/// <typeparam name="TResult">The type of the result produced by the CQL API call.</typeparam> | ||
/// <typeparam name="TCaller">The type of the caller for logging purposes.</typeparam> | ||
/// <param name="cqlApiCall">The CQL API call to be executed.</param> | ||
/// <param name="logger">The logger to log retry and rate limit information.</param> | ||
/// <param name="rateLimit">The rate limit (number of requests) per specified period.</param> | ||
/// <param name="rateLimitPeriod">The time period for the rate limit.</param> | ||
/// <param name="cancellationToken">The cancellation token to cancel the operation.</param> | ||
/// <returns>A task that represents the asynchronous operation, which produces the result of the CQL API call.</returns> | ||
public static Task<TResult> ExecuteWithRetryAndRateLimit<TResult, TCaller>( | ||
this Func<CancellationToken, Task<TResult>> cqlApiCall, | ||
ILogger<TCaller> logger, | ||
int rateLimit, TimeSpan rateLimitPeriod, | ||
CancellationToken cancellationToken = default | ||
) | ||
{ | ||
var wrapPolicy = CreateRetryPolicy(logger).WrapAsync(Policy.RateLimitAsync(rateLimit, rateLimitPeriod)); | ||
var wrappedTask = cqlApiCall.WithWrapPolicy(wrapPolicy, cancellationToken); | ||
|
||
return wrappedTask; | ||
} | ||
|
||
private static AsyncRetryPolicy CreateRetryPolicy(ILogger logger) | ||
{ | ||
return Policy | ||
.Handle<RateLimitRejectedException>() | ||
.WaitAndRetryAsync( | ||
retryCount: 5, | ||
sleepDurationProvider: (retryAttempt, exception, context) => | ||
{ | ||
// Respect the retry after time provided by the rate limiter | ||
if (exception is RateLimitRejectedException rateLimitException) | ||
{ | ||
logger.LogWarning("Rate limit hit. Retrying after {RetryAfter} milliseconds", | ||
rateLimitException.RetryAfter.TotalMilliseconds); | ||
return rateLimitException.RetryAfter; | ||
} | ||
|
||
// Exponential backoff for other exceptions | ||
return TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)); | ||
}, | ||
onRetryAsync: (exception, timeSpan, retryCount, _) => | ||
{ | ||
logger.LogWarning(exception, | ||
"Retrying batch after {SleepDuration}. Retry attempt {RetryCount}", | ||
timeSpan, retryCount); | ||
return Task.CompletedTask; | ||
}); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
using System; | ||
using System.Threading; | ||
using System.Threading.Tasks; | ||
using Microsoft.Extensions.Logging; | ||
using Moq; | ||
using Snd.Sdk.Storage.Cql; | ||
using Xunit; | ||
|
||
namespace Snd.Sdk.Tests.Storage | ||
{ | ||
public class CqlTests : IClassFixture<AkkaFixture>, IClassFixture<LoggerFixture> | ||
{ | ||
private readonly AkkaFixture akkaFixture; | ||
private readonly LoggerFixture loggerFixture; | ||
|
||
public CqlTests(AkkaFixture akkaFixture, LoggerFixture loggerFixture) | ||
{ | ||
this.akkaFixture = akkaFixture; | ||
this.loggerFixture = loggerFixture; | ||
} | ||
|
||
[Theory] | ||
[InlineData(1000, 1, true)] | ||
[InlineData(50000, 1, true)] | ||
public async Task ExecuteWithRetryAndRateLimit_ExecutesSuccessfully(int rateLimit, int rateLimitPeriodSeconds, bool expectedResult) | ||
{ | ||
var loggerMock = new Mock<ILogger<object>>(); | ||
var cqlApiCallMock = new Mock<Func<CancellationToken, Task<bool>>>(); | ||
cqlApiCallMock.Setup(c => c(It.IsAny<CancellationToken>())).ReturnsAsync(expectedResult); | ||
var cancellationToken = CancellationToken.None; | ||
|
||
var result = await cqlApiCallMock.Object.ExecuteWithRetryAndRateLimit(loggerMock.Object, rateLimit, TimeSpan.FromSeconds(rateLimitPeriodSeconds), cancellationToken); | ||
|
||
Assert.True(result); | ||
} | ||
} | ||
} |