Add request retry backoff handling

This commit is contained in:
Eric Bailey
2024-05-22 13:49:47 -05:00
parent 0c50a36053
commit 22bd262c99
+51 -3
View File
@@ -6,6 +6,8 @@ export const RETRYABLE_NETWORK_STATUS_CODES = [
1, 408, 425, 429, 500, 502, 503, 504, 522, 524, 1, 408, 425, 429, 500, 502, 503, 504, 522, 524,
] ]
export const DEFAULT_BACKOFF_DELAY = 500
function isRetryableError(e: unknown) { function isRetryableError(e: unknown) {
if (e instanceof XRPCError) { if (e instanceof XRPCError) {
if (RETRYABLE_NETWORK_STATUS_CODES.includes(e.status)) { if (RETRYABLE_NETWORK_STATUS_CODES.includes(e.status)) {
@@ -16,37 +18,83 @@ function isRetryableError(e: unknown) {
return isNetworkError(e) return isNetworkError(e)
} }
function computeBackoff({
exception: e,
retryCount,
}: {
exception: any
retryCount: number
}) {
if (e instanceof XRPCError) {
if (e.status === 429) {
const retryAfter = parseInt(
e.headers?.['Retry-After'] || e.headers?.['retry-after'] || '',
)
if (typeof retryAfter === 'number' && !Number.isNaN(retryAfter)) {
return retryAfter * 1000
}
}
}
return DEFAULT_BACKOFF_DELAY * Math.pow(2, retryCount)
}
export async function retry<P>( export async function retry<P>(
fn: () => Promise<P>, fn: () => Promise<P>,
{ {
retries, retries: retriesRemaining,
checkIsRetryableError = isRetryableError, checkIsRetryableError = isRetryableError,
backoff,
}: { }: {
retries: number retries: number
checkIsRetryableError?: (err: any) => boolean checkIsRetryableError?: (err: any) => boolean
backoff?: boolean
}, },
): Promise<P> { ): Promise<P> {
let lastErr let lastErr
while (retries > 0) { let retryCount = 0
let backoffDelay = DEFAULT_BACKOFF_DELAY
while (retriesRemaining > 0) {
try { try {
if (retryCount > 0 && backoff) {
await sleep(backoffDelay)
}
return await fn() return await fn()
} catch (e: any) { } catch (e: any) {
lastErr = e lastErr = e
if (checkIsRetryableError(e)) { if (checkIsRetryableError(e)) {
retries-- backoffDelay = computeBackoff({
exception: e,
retryCount: retryCount,
})
retryCount++
retriesRemaining--
continue continue
} }
throw e throw e
} }
} }
throw lastErr throw lastErr
} }
export async function networkRetry<P>( export async function networkRetry<P>(
retries: number, retries: number,
fn: () => Promise<P>, fn: () => Promise<P>,
options: {
backoff?: boolean
},
): Promise<P> { ): Promise<P> {
return retry(fn, { return retry(fn, {
retries, retries,
backoff: options.backoff,
}) })
} }
export function sleep(time: number) {
return new Promise(y => setTimeout(y, time))
}