-
Notifications
You must be signed in to change notification settings - Fork 20
feat: add support for handling Retry-After
header
#267
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
base: main
Are you sure you want to change the base?
Conversation
Important Review skippedAuto incremental reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the You can disable this status message by setting the Pre-merge checks and finishing touches❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
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 |
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #267 +/- ##
==========================================
+ Coverage 88.43% 88.60% +0.17%
==========================================
Files 23 23
Lines 1219 1264 +45
Branches 198 233 +35
==========================================
+ Hits 1078 1120 +42
- Misses 85 87 +2
- Partials 56 57 +1 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
This is generated using the existing sdk-generator PR openfga/sdk-generator#504 |
Retry-After
header
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 2
🧹 Nitpick comments (5)
common.ts (5)
145-147
: Allow Retry-After: 0 and handle small negative skewRFC allows Retry-After of 0 (immediate retry). Also clamp small negative delays due to clock skew to 0.
Apply this diff:
-function isValidRetryDelay(delayMs: number): boolean { - return delayMs > 0 && delayMs <= 1800 * 1000; -} +function isValidRetryDelay(delayMs: number): boolean { + return delayMs >= 0 && delayMs <= 1800 * 1000; +}- const dateValue = new Date(retryAfterValue); - const now = new Date(); - const delayMs = dateValue.getTime() - now.getTime(); + const dateValue = new Date(retryAfterValue); + const now = new Date(); + const delayMs = Math.max(0, dateValue.getTime() - now.getTime()); - if (isValidRetryDelay(delayMs)) { + if (isValidRetryDelay(delayMs)) { return delayMs; }Also applies to: 172-179
139-144
: Avoid 0ms backoff when minWaitInMs is unset (burst risk)With minWaitInMs=0, randomTime returns 0ms. Use a small floor to prevent tight loops.
Apply this diff:
-function randomTime(loopCount: number, minWaitInMs: number): number { - const min = Math.ceil(2 ** loopCount * minWaitInMs); - const max = Math.ceil(2 ** (loopCount + 1) * minWaitInMs); +function randomTime(loopCount: number, minWaitInMs: number): number { + const base = Math.max(minWaitInMs, 50); // 50ms floor + const min = Math.ceil(2 ** loopCount * base); + const max = Math.ceil(2 ** (loopCount + 1) * base); const calculatedTime = Math.floor(Math.random() * (max - min) + min); return Math.min(calculatedTime, 120 * 1000); }
276-279
: Default retry params can yield 0ms backoffIf configuration.retryParams is undefined and options omit retryParams, minWaitInMs becomes 0. Ensure sane defaults when maxRetry > 0.
Apply this diff:
- const retryParams = axiosArgs.options?.retryParams ? axiosArgs.options?.retryParams : configuration.retryParams; - const maxRetry:number = retryParams ? retryParams.maxRetry : 0; - const minWaitInMs:number = retryParams ? retryParams.minWaitInMs : 0; + const retryParams = axiosArgs.options?.retryParams ?? configuration.retryParams; + const maxRetry: number = retryParams?.maxRetry ?? 0; + const minWaitInMs: number = maxRetry > 0 ? Math.max(50, retryParams?.minWaitInMs ?? 200) : 0;
230-266
: Optional: include 408 (Request Timeout) in retryable statuses408 is commonly safe to retry with backoff.
Apply this diff:
- } else if (status === 429 || (status >= 500 && status !== 501)) { + } else if (status === 408 || status === 429 || (status >= 500 && status !== 501)) {
200-201
: Optional: narrow return type (never returns undefined after fixes)Control flow always returns a WrappedAxiosResponse or throws. Simplify the signature.
Apply this diff:
-): Promise<WrappedAxiosResponse<R> | undefined> { +): Promise<WrappedAxiosResponse<R>> {
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (1)
package-lock.json
is excluded by!**/package-lock.json
📒 Files selected for processing (4)
CHANGELOG.md
(1 hunks)common.ts
(7 hunks)tests/helpers/nocks.ts
(1 hunks)tests/index.test.ts
(9 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
tests/index.test.ts (3)
tests/helpers/default-config.ts (2)
OPENFGA_API_TOKEN_ISSUER
(20-20)baseConfig
(54-67)configuration.ts (1)
GetDefaultRetryParams
(48-53)api.ts (1)
OpenFgaApi
(1284-1508)
common.ts (2)
base.ts (1)
RequestArgs
(29-32)errors.ts (5)
FgaError
(27-40)FgaApiValidationError
(120-134)FgaApiNotFoundError
(142-155)FgaApiRateLimitExceededError
(165-179)FgaApiInternalError
(187-203)
🔇 Additional comments (1)
common.ts (1)
250-263
: Good: honors Retry-After header with sensible fallbackParsing Retry-After and falling back to capped exponential backoff is correct and improves resilience.
Consider adding tests for:
- Retry-After: "0", very large value (clamped), HTTP-date in past (treated as 0), invalid string (fallback to backoff).
- Network error retry path.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you check what's happening with the spaces?
Also can you add tests for:
- errors on token exchange
- invalid or missing retry-after header
- retry after header with time > max time
- retry after header with 0 time
- No retries for 501 errors
tests/index.test.ts
Outdated
writes: { tuple_keys: [tuple] }, | ||
authorization_model_id: "01GXSA8YR785C4FYS3C0RTG7B1", | ||
}); | ||
baseConfig.storeId!, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These spacing changes are weird - did you run npm run lint:fix
?
In a separate PR, we should take another look at our lint and prettier configs and make sure to enforce it at the CI level.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oops, I think my IDE did the spaces, I had them hidden so didn't catch them, I will fix those
common.ts
Outdated
retries: iterationCount - 1, | ||
}; | ||
} catch (err: any) { | ||
// Non-Axios errors are not retryable here |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we change this a bit?
Can we have a helper function to decide whether to retry, e.g.
checkIfRetryableError
, and that checks on what the type is and how many iterations have passed.
That way here all we have to do is:
isRetryable?
yes => retry
no => throw appropriate error
might make this a bit easier to reason about
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
agreed, will address
common.ts
Outdated
return Math.min(calculatedTime, 120 * 1000); | ||
} | ||
function isValidRetryDelay(delayMs: number): boolean { | ||
return delayMs > 0 && delayMs <= 1800 * 1000; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
return delayMs > 0 && delayMs <= 1800 * 1000; | |
return delayMs >= 1_000 && delayMs <= 1800 * 1000; |
Retry-After enforces >= 1 sec.
Can you move the numbers to named variables or add comments indicating what the intention behind them is? Someone revisiting this later will have a hard time understanding why we have 1000 * 1000
common.ts
Outdated
} | ||
|
||
// If Retry-After header isn't valid or not present, use exponential backoff | ||
if (retryDelayMs === undefined) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if (retryDelayMs === undefined) { | |
if (!retryDelayMs) { |
Description
For #208
What problem is being solved?
How is it being solved?
What changes are made to solve it?
References
Review Checklist
main
Summary by CodeRabbit
New Features
Documentation
Tests