Repository navigation
fix: WithMaxRetryWait bounds one wait, not the total - #48
Merged
Merged
Conversation
WithMaxRetryWait bounded each individual wait and nothing bounded their sum. With the default three retries, a server answering Retry-After just under the budget held the call for roughly three times the number the caller configured, and under context.Background() there is no deadline to rescue it. Measured on main: 5.007s of automatic wait against a 1.2s budget, and 4.005s against a 1.5s budget on the transport-failure path. That is the problem the option was added for in #37, so bounding the total is the fix rather than a new option. doRequestWithHeaders now tracks the wait already spent in this call and makes each decision against what is left: - 429/5xx: a Retry-After longer than the remaining budget stops the retry loop and returns the typed error, exactly as a wait past the whole budget already did. Remaining budget is a stop condition, not a clamp, because waiting less than the server asked would not clear the limit. - Transport failures: the exponential backoff is clamped to the remaining budget, so retries still happen while there is budget for them and stop when there is not. Behaviour change, documented on WithMaxRetryWait and DefaultMaxRetryWait: the number is now the total automatic wait for one call. It can only make a call return sooner than before, never later. A retry that fits in the budget still happens, and the #37 behaviour — a single Retry-After past the budget returns *RateLimitError immediately, carrying the server's requested wait — is unchanged and covered by a test. Closes #42 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015ao5paex73xXvuM424Libo
|
Important
This repository does not receive automatic reviews because it has fewer than 10 stars. ⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Advanced Run ID: Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Merged
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #42 (follow-up to #37)
The defect
WithMaxRetryWaitbounded each individual wait. Nothing bounded their sum. With the default three retries, a server answeringRetry-Afterjust under the budget held the call for roughly three times the number the caller configured — and undercontext.Background()there is no deadline to rescue it.Verified still reproducing on
main(cd51e3a) before any change was written:mainRetry-After: 1, 5 retriesBounding how long a call can sit inside the SDK is the entire reason the option exists (#37), so bounding the total is the fix rather than adding a second option the caller has to discover.
The fix
doRequestWithHeaderstracks the wait already spent in this call and makes every decision against what is left:Retry-Afterlonger than the remaining budget stops the retry loop and returns the typed error, exactly as a wait past the whole budget already did. Remaining budget is a stop condition, not a clamp, because waiting less than the server asked would not clear the limit.Behaviour change
WithMaxRetryWaitandDefaultMaxRetryWaitnow document a total automatic wait budget for one call. This can only make a call return sooner than before, never later. Two behaviours are explicitly preserved and pinned by tests:TestRetryStillHappensWithinTheBudget— one 429, one success, server hit exactly twice).Retry-Afterpast the budget returns*RateLimitErrorimmediately, carrying the server's requested 3600 (TestSingleWaitLongerThanBudgetStillReturnsImmediately).Red
Tests written first, run against unmodified
main:The two passing tests are the regression guards; they are green before and after by design.
The tests measure wall-clock time through the real request path against a real
httptestserver (and, for the transport case, a real listener that accepts and hangs up), not a field on the client — the defect is elapsed time, so elapsed time is what is asserted.Green
5.007s → 1.00s, and 4.005s → 1.50s, each now inside its configured budget.
Full suite
go test ./...ok github.com/OilpriceAPI/oilpriceapi-go 20.148s— 289 pass / 0 fail (285 baseline + 4 new)go test -race ./...ok github.com/OilpriceAPI/oilpriceapi-go 21.900sgo vet ./...gofmt -l .The existing retry-policy suite (
retry_policy_test.go) is unchanged and still passes.Scope
No version bump, no tag, no release.
🤖 Generated with Claude Code
https://claude.ai/code/session_015ao5paex73xXvuM424Libo