-
-
Notifications
You must be signed in to change notification settings - Fork 1.2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[already possible] Respect Retry-After HTTP header #414
Comments
Hi @nmurf! You can already do this with current Polly. You can use a wait-and-retry overload taking the returned response as input to the IAsyncPolicy<HttpResponseMessage> retryHonouringRetryAfter =
Policy.Handle<HttpRequestException>
.OrResult<HttpResponseMessage>(r => /* clauses for other status codes you want to handle */
|| r.StatusCode == (HttpStatusCode)429) // RetryAfter
.WaitAndRetryAsync(
retryCount: 3,
sleepDurationProvider: (retryCount, response, context) => {
// taking the pseudocode from the question
var serverWaitDuration = getServerWaitDuration(response.Result?.Headers.Get("Retry-After")); // explaining response.Result? : The response is a Polly type DelegateResult<HttpResponseMessage>. Use null defence, as response.Result will be null if the cause for retry was an exception (in which case there was on result).
var waitDuration = Math.Max(clientWaitDuration.TotalMilliseconds, serverWaitDuration.TotalMilliseconds);
return TimeSpan.FromMilliseconds(waitDuration);
},
onRetryAsync: async (response, timespan, retryCount, context) => {
/* perhaps some logging, eg the retry count, the timespan delaying for */
}
); This is pseudo-code (for speed of response) : you may have to vary some details. For example, Azure Cosmos DB client SDK will wrap this response up in an Exception with a RetryAfter property after making its own retries first. We'd love to see your final version! This is documented in the Polly wiki here but I'll aim to reference that also from the main Polly readme, to make it more discoverable. More info also in this similar question. Let us know how you get on! |
Thanks for the quick response, @reisenberger! Great library -- I did not notice the provider overload until you pointed it out. For posterity, here is an implementation of the method to obtain the header value as a TimeSpan (assumes server is in UTC):
Here's the rest:
|
Thanks @nmurf for sharing this! One thought: enumerating I see the challenge though created by the fact that responding to One approach to solve this could be to split the original policy into two retry policies, and then combine both retry policies in a _retryAfterPolicy = Policy
.HandleResult<HttpResponseMessage>(r => r?.Headers?.RetryAfter != null) // EDIT: .Net Framework only: https://msdn.microsoft.com/en-us/library/system.net.http.headers.httpresponseheaders.aspx; not yet in this form in .NET Core?
.WaitAndRetryAsync(
retryCount: numRetries,
sleepDurationProvider: (retryCount, response, context) => getServerWaitDuration(response)
);
_generalRetryPolicy = Policy.Handle<HttpRequestException>()
.OrResult(filter) // omit the RetryAfter case now from 'filter'!
.WaitAndRetryAsync(sleepDurations: DecorrelatedJitter(numRetries, firstWaitDuration.Value, maxWaitDuration.Value));
_policy = _generalRetryPolicy.WrapAsync(_retryAfterPolicy);
A minor edge-case with the two retry policies nested in a If anyone can suggest further refinements, please do add them to this thread! |
@reisenberger, good catch with the I think the correct solution here is to generate the sleep durations as needed. I don't have time to test this at the moment, but this is what I am thinking:
Here's the new jitter method. It could be tweaked if you need to support 64 bit millisecond durations.
|
@nmurf Yes, I figured yesterday that the two policies could be joined back together by sharing state through |
How can we use retryafter with retry forever + timeout? |
@ikabar Use PolicyWrap (see that wiki about it), and wrap a TimeoutPolicy (for X minutes) outside of the retry-after retry policy. This is what PolicyWrap is designed for: combining policies. |
What about 429's that include the retry after in the JSON message body? It doesn't appear that there is a |
@WorldMaker Yes, that's correct: current Polly does not offer a You should still be able to handle that case with current Polly by placing that read-body logic, for the 429 case, in the delegate passed to At present, we are preferring not to add new feature-variants like Thanks again for the question! |
What would be the updated way to handle this? I see on May 6th these are out of date, and I'm getting an error when trying it. I wrote a custom policy and added it with: Here is my attempted implementation to get access to
The error it gives me is: Extra context if it helps: Slack API returns a 429 when being rate limited with a delta in seconds to wait. I'd love to use that delta instead of doing just exponential waits. Currently I'm just using a for loop and reading the header without Polly to make it work. |
@zstarr This should achieve your goal - the missing bit is that you haven't specified the static IAsyncPolicy<HttpResponseMessage> GetRetryAfterPolicy()
{
return Policy.HandleResult<HttpResponseMessage>
(msg => msg.Headers.RetryAfter is not null)
.WaitAndRetryAsync(
retryCount: 3,
sleepDurationProvider: (_, response, _) => response.Result.Headers.RetryAfter.Delta.Value,
onRetryAsync: (_, _, _, _) => Task.CompletedTask
);
} |
That fixed it, thanks! |
The Retry-After response header is used to indicate how long a client should wait before sending the next request. It would be great if RetryPolicy took this into account.
Pseudocode:
The text was updated successfully, but these errors were encountered: