Skip to content
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

DefaultBackoff does not incorporate timeout which may lead to hanging requests #157

Open
ThomasJanUta opened this issue Mar 9, 2022 · 2 comments · May be fixed by #171
Open

DefaultBackoff does not incorporate timeout which may lead to hanging requests #157

ThomasJanUta opened this issue Mar 9, 2022 · 2 comments · May be fixed by #171
Labels

Comments

@ThomasJanUta
Copy link

ThomasJanUta commented Mar 9, 2022

If a server sets the waiting time for the status "429 TooManyRequests" to 24 hours or even more, the client will hang for this amount of time. This scenario is not uncommon for servers that implement drastic rate limits.

This line in the backoff function

if resp.StatusCode == http.StatusTooManyRequests || resp.StatusCode == http.StatusServiceUnavailable

allows to wait if the server currently does not accept requests.

You can set a timeout for the http.client: client.HTTPClient.Timeout = time.Duration(timeout) * time.Second but it does not apply to the DefaultBackoff function.

Is this the desired default behavior?


This is the fix in my code I use. It currently still retries but at least it's not hanging:

func NewBackoff(min, max time.Duration, attemptNum int, resp *http.Response) time.Duration {
	timeout := int64(30)                                           // define timeout again
	if resp != nil {
		if resp.StatusCode == http.StatusTooManyRequests || resp.StatusCode == http.StatusServiceUnavailable {
			if s, ok := resp.Header["Retry-After"]; ok {
				if sleep, err := strconv.ParseInt(s[0], 10, 64); err == nil {
					if sleep > timeout {             // waiting time supplied by server must not exceed timeout
						return time.Duration(0)
					}
					return time.Second * time.Duration(sleep)
				}
			}
		}
	}

	mult := math.Pow(2, float64(attemptNum)) * float64(min)
	sleep := time.Duration(mult)
	if float64(sleep) != mult || sleep > max {
		sleep = max
	}
	return sleep
}

Is there a better fix? I can offer a pull requests if wanted.

@arlandism
Copy link

Agreed this seems undesirable.

Since max is passed in, it feels like the sleep time should be: min(serverRetryAfter, max)

@SimonRichardson
Copy link

Wouldn't a better fix to pass in a context, so that it can be controlled by the requester via context.WithTimeout or similar. That way we can just cancel it at any point anyway. This gives more flexibility, the only downside is that it's a breaking change.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging a pull request may close this issue.

4 participants