From c1317d55085560b0fe05f3edd609fcf603208858 Mon Sep 17 00:00:00 2001 From: Amit Singh Date: Mon, 13 Apr 2026 14:57:57 +0530 Subject: [PATCH 1/2] ix: implement retry logic for socket hang up errors in HTTP requests --- src/lib/retry.ts | 19 +++++++++-- tests/lib/runtime.test.ts | 67 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 84 insertions(+), 2 deletions(-) diff --git a/src/lib/retry.ts b/src/lib/retry.ts index 3540ff73e4..96afb51bbc 100644 --- a/src/lib/retry.ts +++ b/src/lib/retry.ts @@ -57,10 +57,25 @@ export function retry(action: () => Promise, { maxRetries, retryWhen } const nrOfTriesToAttempt = Math.min(MAX_NUMBER_RETRIES, maxRetries ?? DEFAULT_NUMBER_RETRIES); let nrOfTries = 0; - const retryAndWait = async () => { + const retryAndWait = async (): Promise => { let result: Response; - result = await action(); + try { + result = await action(); + } catch (e: any) { + if (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++; diff --git a/tests/lib/runtime.test.ts b/tests/lib/runtime.test.ts index 29ad35aee7..40ac20b19b 100644 --- a/tests/lib/runtime.test.ts +++ b/tests/lib/runtime.test.ts @@ -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, []); From 813295bbb5da8deae7f25bf2c51f1ce932550cbe Mon Sep 17 00:00:00 2001 From: Amit Singh Date: Mon, 13 Apr 2026 15:14:42 +0530 Subject: [PATCH 2/2] ix: resolve lint warning - use unknown instead of any in catch block --- src/lib/retry.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lib/retry.ts b/src/lib/retry.ts index 96afb51bbc..fa2f01a2f0 100644 --- a/src/lib/retry.ts +++ b/src/lib/retry.ts @@ -62,8 +62,8 @@ export function retry(action: () => Promise, { maxRetries, retryWhen } try { result = await action(); - } catch (e: any) { - if (e.name !== "TimeoutError" && nrOfTries < nrOfTriesToAttempt) { + } catch (e: unknown) { + if (!(e instanceof Error && e.name === "TimeoutError") && nrOfTries < nrOfTriesToAttempt) { nrOfTries++; let wait = BASE_DELAY * Math.pow(2, nrOfTries - 1);