Skip to content

Commit 8293f64

Browse files
authored
SimpleRetryHandler - sleep now uses CancellationToken. (#1032)
closes #1031
1 parent b305b6d commit 8293f64

File tree

2 files changed

+53
-7
lines changed

2 files changed

+53
-7
lines changed

SpotifyAPI.Web.Tests/RetryHandlers/SimpleRetryHandlerTest.cs

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,51 @@ public async Task HandleRetry_DirectSuccess()
182182
setup.Sleep.Verify(s => s(TimeSpan.FromMilliseconds(50)), Times.Exactly(0));
183183
}
184184

185+
[Test]
186+
public async Task HandleRetry_CancellationTokenHonoredInSleep()
187+
{
188+
var setup = new Setup();
189+
setup.Response.SetupGet(r => r.StatusCode).Returns(HttpStatusCode.TooManyRequests);
190+
setup.Response.SetupGet(r => r.Headers).Returns(new Dictionary<string, string> {
191+
{ "Retry-After", "5" }
192+
});
193+
194+
var retryCalled = 0;
195+
setup.Retry = (request, ct) =>
196+
{
197+
retryCalled++;
198+
return Task.FromResult(setup.Response.Object);
199+
};
200+
201+
var handler = new SimpleRetryHandler()
202+
{
203+
TooManyRequestsConsumesARetry = true,
204+
RetryTimes = 0,
205+
RetryAfter = TimeSpan.FromSeconds(1)
206+
};
207+
208+
var cancellationTokenSource = new CancellationTokenSource();
209+
210+
var startTime = DateTimeOffset.UtcNow;
211+
try
212+
{
213+
var attemptTask = handler.HandleRetry(setup.Request.Object, setup.Response.Object, setup.Retry, cancellationTokenSource.Token);
214+
215+
// Wait enough that we're probably in the sleep
216+
await Task.Delay(TimeSpan.FromMilliseconds(100));
217+
218+
cancellationTokenSource.Cancel();
219+
220+
await attemptTask;
221+
}
222+
catch (OperationCanceledException)
223+
{
224+
//Expected
225+
}
226+
227+
Assert.That(DateTimeOffset.UtcNow - startTime, Is.LessThan(TimeSpan.FromSeconds(4)));
228+
}
229+
185230
private class Setup
186231
{
187232
public Mock<Func<TimeSpan, Task>> Sleep { get; set; } = new Mock<Func<TimeSpan, Task>>();

SpotifyAPI.Web/RetryHandlers/SimpleRetryHandler.cs

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,10 @@ namespace SpotifyAPI.Web
1010
{
1111
public class SimpleRetryHandler : IRetryHandler
1212
{
13-
private readonly Func<TimeSpan, Task> _sleep;
13+
private readonly Func<TimeSpan, CancellationToken, Task> _sleep;
1414

1515
/// <summary>
16-
/// Specifies after how many miliseconds should a failed request be retried.
16+
/// Specifies after how many milliseconds should a failed request be retried.
1717
/// </summary>
1818
public TimeSpan RetryAfter { get; set; }
1919

@@ -38,10 +38,11 @@ public class SimpleRetryHandler : IRetryHandler
3838
/// the Retry-After header
3939
/// </summary>
4040
/// <returns></returns>
41-
public SimpleRetryHandler() : this(Task.Delay) { }
42-
public SimpleRetryHandler(Func<TimeSpan, Task> sleep)
41+
public SimpleRetryHandler() : this(sleepWithCancel: Task.Delay) { }
42+
public SimpleRetryHandler(Func<TimeSpan, Task> sleep) : this((t, _) => sleep(t)) { }
43+
public SimpleRetryHandler(Func<TimeSpan, CancellationToken, Task> sleepWithCancel)
4344
{
44-
_sleep = sleep;
45+
_sleep = sleepWithCancel;
4546
RetryAfter = TimeSpan.FromMilliseconds(50);
4647
RetryTimes = 10;
4748
TooManyRequestsConsumesARetry = false;
@@ -88,15 +89,15 @@ private async Task<IResponse> HandleRetryInternally(
8889
var secondsToWait = ParseTooManyRetries(response);
8990
if (secondsToWait != null && (!TooManyRequestsConsumesARetry || triesLeft > 0))
9091
{
91-
await _sleep(secondsToWait.Value).ConfigureAwait(false);
92+
await _sleep(secondsToWait.Value, cancel).ConfigureAwait(false);
9293
response = await retry(request, cancel).ConfigureAwait(false);
9394
var newTriesLeft = TooManyRequestsConsumesARetry ? triesLeft - 1 : triesLeft;
9495
return await HandleRetryInternally(request, response, retry, newTriesLeft, cancel).ConfigureAwait(false);
9596
}
9697

9798
while (RetryErrorCodes.Contains(response.StatusCode) && triesLeft > 0)
9899
{
99-
await _sleep(RetryAfter).ConfigureAwait(false);
100+
await _sleep(RetryAfter, cancel).ConfigureAwait(false);
100101
response = await retry(request, cancel).ConfigureAwait(false);
101102
return await HandleRetryInternally(request, response, retry, triesLeft - 1, cancel).ConfigureAwait(false);
102103
}

0 commit comments

Comments
 (0)