I have configured a retry based pipeline with Polly.
// Using Polly for retry logicprivate readonly ResiliencePipeline _retryPipeline = new ResiliencePipelineBuilder { TimeProvider = timeProvider } .AddRetry(new RetryStrategyOptions { ShouldHandle = new PredicateBuilder().Handle<ConditionalCheckFailedException>(), Delay = TimeSpan.FromMilliseconds(_backoffFactor), MaxRetryAttempts = _maxAttempts - 1, // Linear backoff increases the delay each time by the backoff factor BackoffType = DelayBackoffType.Linear, OnRetry = onRetryArguments => { logger.LogWarning("Failed to acquire lock. Retrying. {@LogContext}", new { onRetryArguments }); return ValueTask.CompletedTask; } }) .Build();
Which I execute using
// Attempt to store the lock with backoff retry LockResult result = await _retryPipeline.ExecuteAsync( async _ => await AttemptLockStorageAsync(lockId, expiryMilliseconds, attempts++), cancellationTokenSource.Token);
When unit testing, I find that I have to add a Task.Delay(1)
in order for Polly to perform the retries
// ActFunc<Task<DistributedLock>> func = async () =>{ Task<DistributedLock> result = _distributedLockService.AcquireLockAsync(lockId); for (int i = 1; i <= 4; i++) { _timeProvider.Advance(TimeSpan.FromMilliseconds(1000 * i + 1)); await Task.Delay(1); } return await result;};// Assert// We expect that we should be able to attempt 5 full times, rather than getting a TaskCancelledException.(await func.Should().ThrowAsync<TimeoutException>()).WithMessage( $"Could not acquire lock {lockId}. Attempted 5 times.");
Why is the Task.Delay
necessary?
Edit
TimeProvider provided to the SUT via the primary constructor.
public class DistributedLockService( IDistributedLockRepository distributedLockRepository, ILogger<DistributedLockService> logger, TimeProvider timeProvider) : IDisposable, IDistributedLockService
FakeTimer is provided in the unit test constructor
private readonly FakeTimeProvider _timeProvider = new(); public DistributedLockServiceTests() { _timeProvider.SetUtcNow(DateTimeOffset.Parse("2024-01-23", CultureInfo.InvariantCulture)); _distributedLockService = new DistributedLockService( _distributedLockRepository.Object, _logger.Object, _timeProvider); }
Filed a bug report based on minimal reproductionhttps://github.com/App-vNext/Polly/issues/1932