Skip to content

Commit

Permalink
feat(client): adjust retry behavior to be exponential backoff (#192)
Browse files Browse the repository at this point in the history
  • Loading branch information
stainless-bot committed Oct 24, 2023
1 parent bfddc46 commit 747afe2
Show file tree
Hide file tree
Showing 2 changed files with 10 additions and 14 deletions.
20 changes: 8 additions & 12 deletions src/core.ts
Expand Up @@ -27,8 +27,6 @@ export {
type Uploadable,
} from './uploads';

const MAX_RETRIES = 2;

export type Fetch = (url: RequestInfo, init?: RequestInit) => Promise<Response>;

type PromiseOrValue<T> = T | Promise<T>;
Expand Down Expand Up @@ -162,7 +160,7 @@ export abstract class APIClient {

constructor({
baseURL,
maxRetries,
maxRetries = 2,
timeout = 600000, // 10 minutes
httpAgent,
fetch: overridenFetch,
Expand All @@ -174,7 +172,7 @@ export abstract class APIClient {
fetch: Fetch | undefined;
}) {
this.baseURL = baseURL;
this.maxRetries = validatePositiveInteger('maxRetries', maxRetries ?? MAX_RETRIES);
this.maxRetries = validatePositiveInteger('maxRetries', maxRetries);
this.timeout = validatePositiveInteger('timeout', timeout);
this.httpAgent = httpAgent;

Expand Down Expand Up @@ -513,8 +511,6 @@ export abstract class APIClient {
retriesRemaining: number,
responseHeaders?: Headers | undefined,
): Promise<APIResponseProps> {
retriesRemaining -= 1;

// About the Retry-After header: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Retry-After
let timeoutMillis: number | undefined;
const retryAfterHeader = responseHeaders?.['retry-after'];
Expand All @@ -540,22 +536,22 @@ export abstract class APIClient {
}
await sleep(timeoutMillis);

return this.makeRequest(options, retriesRemaining);
return this.makeRequest(options, retriesRemaining - 1);
}

private calculateDefaultRetryTimeoutMillis(retriesRemaining: number, maxRetries: number): number {
const initialRetryDelay = 0.5;
const maxRetryDelay = 2;
const maxRetryDelay = 8.0;

const numRetries = maxRetries - retriesRemaining;

// Apply exponential backoff, but not more than the max.
const sleepSeconds = Math.min(initialRetryDelay * Math.pow(numRetries - 1, 2), maxRetryDelay);
const sleepSeconds = Math.min(initialRetryDelay * Math.pow(2, numRetries), maxRetryDelay);

// Apply some jitter, plus-or-minus half a second.
const jitter = Math.random() - 0.5;
// Apply some jitter, take up to at most 25 percent of the retry time.
const jitter = 1 - Math.random() * 0.25;

return (sleepSeconds + jitter) * 1000;
return sleepSeconds * jitter * 1000;
}

private getUserAgent(): string {
Expand Down
4 changes: 2 additions & 2 deletions tests/index.test.ts
Expand Up @@ -141,8 +141,8 @@ describe('instantiate client', () => {
});

test('maxRetries option is correctly set', () => {
const client = new Anthropic({ maxRetries: 1, apiKey: 'my-anthropic-api-key' });
expect(client.maxRetries).toEqual(1);
const client = new Anthropic({ maxRetries: 4, apiKey: 'my-anthropic-api-key' });
expect(client.maxRetries).toEqual(4);

// default
const client2 = new Anthropic({ apiKey: 'my-anthropic-api-key' });
Expand Down

0 comments on commit 747afe2

Please sign in to comment.