Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 17 additions & 2 deletions src/lib/retry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,10 +57,25 @@ export function retry(action: () => Promise<Response>, { maxRetries, retryWhen }
const nrOfTriesToAttempt = Math.min(MAX_NUMBER_RETRIES, maxRetries ?? DEFAULT_NUMBER_RETRIES);
let nrOfTries = 0;

const retryAndWait = async () => {
const retryAndWait = async (): Promise<Response> => {
let result: Response;

result = await action();
try {
result = await action();
} catch (e: unknown) {
if (!(e instanceof Error && e.name === "TimeoutError") && nrOfTries < nrOfTriesToAttempt) {
nrOfTries++;

let wait = BASE_DELAY * Math.pow(2, nrOfTries - 1);
wait = getRandomInt(wait + 1, wait + MAX_REQUEST_RETRY_JITTER);
wait = Math.min(wait, MAX_REQUEST_RETRY_DELAY);

await pause(wait);

return retryAndWait();
}
throw e;
}

if ((retryWhen || [429]).includes(result.status) && nrOfTries < nrOfTriesToAttempt) {
nrOfTries++;
Expand Down
67 changes: 67 additions & 0 deletions tests/lib/runtime.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -268,6 +268,73 @@ describe("Runtime", () => {
).rejects.toThrowError(expect.objectContaining({ statusCode: 429 }));
});

it("should retry on socket hang up (ECONNRESET) when retry is enabled", async () => {
// issue #1020 — network errors were not retried, only HTTP status codes were
const request = nock(URL, { encodedQueryParams: true })
.get("/clients")
.times(2)
.replyWithError({ code: "ECONNRESET", message: "socket hang up" })
.get("/clients")
.reply(200, [{ client_id: "123" }]);

const client = new TestClient({
baseUrl: URL,
parseError,
});

const response = await client.testRequest({
path: `/clients`,
method: "GET",
});

const data = (await response.json()) as Array<{ client_id: string }>;

expect(data[0].client_id).toBe("123");
expect(request.isDone()).toBe(true);
});

it("should throw after exhausting retries on repeated socket hang up", async () => {
// issue #1020 — should give up after maxRetries attempts
nock(URL, { encodedQueryParams: true })
.get("/clients")
.times(4)
.replyWithError({ code: "ECONNRESET", message: "socket hang up" });

const client = new TestClient({
baseUrl: URL,
parseError,
});

await expect(
client.testRequest({
path: `/clients`,
method: "GET",
}),
).rejects.toThrow();
});

it("should not retry on socket hang up when retry is disabled", async () => {
// issue #1020 — disabled retry should still not retry network errors
nock(URL, { encodedQueryParams: true })
.get("/clients")
.replyWithError({ code: "ECONNRESET", message: "socket hang up" })
.get("/clients")
.reply(200, [{ client_id: "123" }]);

const client = new TestClient({
baseUrl: URL,
parseError,
retry: { enabled: false },
});

await expect(
client.testRequest({
path: `/clients`,
method: "GET",
}),
).rejects.toThrow();
});

it("should timeout after default time", async () => {
nock(URL).get("/clients").delayConnection(10000).reply(200, []);

Expand Down
Loading