From 4970dd826eeb796c94963a436b76c06209f34b90 Mon Sep 17 00:00:00 2001 From: Durvesh Pilankar Date: Fri, 26 Jun 2026 23:01:41 -0700 Subject: [PATCH] Fix HttpStore write retries using the read endpoint's config HttpStore.#withRetries takes an `endpoint` parameter (get() passes #getEndpoint, set() passes #setEndpoint) and correctly consults it for the maxAttempts===1 short-circuit. But the backOff options hardcoded this.#getEndpoint for numOfAttempts, retryStatuses, and retryNetworkErrors. When the store is constructed with the documented split { getOptions, setOptions } form, all set()/PUT retries silently used the read endpoint's retry configuration (e.g. setOptions.maxAttempts and setOptions.retryStatuses were ignored). Use the passed-in `endpoint` consistently. Adds a regression test exercising the split form on set() (the existing retry tests only use the single-options form via get(), where the two endpoints are identical). --- packages/metro-cache/src/stores/HttpStore.js | 8 ++--- .../src/stores/__tests__/HttpStore-test.js | 33 +++++++++++++++++++ 2 files changed, 36 insertions(+), 5 deletions(-) diff --git a/packages/metro-cache/src/stores/HttpStore.js b/packages/metro-cache/src/stores/HttpStore.js index 951671b30b..97c90645b5 100644 --- a/packages/metro-cache/src/stores/HttpStore.js +++ b/packages/metro-cache/src/stores/HttpStore.js @@ -396,14 +396,12 @@ export default class HttpStore { return backOff(fn, { jitter: 'full', maxDelay: 30000, - numOfAttempts: this.#getEndpoint.maxAttempts || Number.POSITIVE_INFINITY, + numOfAttempts: endpoint.maxAttempts || Number.POSITIVE_INFINITY, retry: (e: Error) => { if (e instanceof HttpError) { - return this.#getEndpoint.retryStatuses.has(e.code); + return endpoint.retryStatuses.has(e.code); } - return ( - e instanceof NetworkError && this.#getEndpoint.retryNetworkErrors - ); + return e instanceof NetworkError && endpoint.retryNetworkErrors; }, }); } diff --git a/packages/metro-cache/src/stores/__tests__/HttpStore-test.js b/packages/metro-cache/src/stores/__tests__/HttpStore-test.js index e8496ab2b9..6802648844 100644 --- a/packages/metro-cache/src/stores/__tests__/HttpStore-test.js +++ b/packages/metro-cache/src/stores/__tests__/HttpStore-test.js @@ -290,6 +290,39 @@ describe('HttpStore', () => { }); }); + test('set() retries use the set endpoint config, not the get endpoint', async () => { + jest.useRealTimers(); + // Distinct read/write retry config: reads do not retry, writes retry 503s. + const store = new HttpStore({ + getOptions: {endpoint: 'http://example.com', maxAttempts: 1}, + setOptions: { + endpoint: 'http://example.com', + maxAttempts: 2, + retryStatuses: [503], + }, + }); + const {request} = require('http'); + + request.mockImplementation((opts, callback) => { + if (request.mock.calls.length === 1) { + callback(responseHttpError(503)); + } else { + callback(responseHttpOk('')); + } + return new PassThrough(); + }); + + let error; + try { + await store.set(Buffer.from('key-set'), {foo: 42}); + } catch (e) { + error = e; + } + + expect(error).toBeUndefined(); + expect(request).toHaveBeenCalledTimes(2); + }); + test('sets using the network via PUT method', done => { const store = new HttpStore({endpoint: 'http://www.example.com/endpoint'}); const promise = store.set(Buffer.from('key-set'), {foo: 42});