Quantcast
Channel: Active questions tagged retry-logic - Stack Overflow
Viewing all articles
Browse latest Browse all 950

Why is Task.Delay(1) necessary to advance clock when unit testing with .NET TimeProvider?

$
0
0

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


Viewing all articles
Browse latest Browse all 950

Trending Articles



<script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>