From ea39580e1bee28e38f8e8a144faaa9d6dd27385a Mon Sep 17 00:00:00 2001 From: axe312ger Date: Tue, 9 Nov 2021 18:46:41 +0100 Subject: [PATCH] add test and retry for network errors --- .../src/__tests__/fetch-remote-file.js | 11 +-- .../src/fetch-remote-file.ts | 94 +++++++++---------- 2 files changed, 48 insertions(+), 57 deletions(-) diff --git a/packages/gatsby-core-utils/src/__tests__/fetch-remote-file.js b/packages/gatsby-core-utils/src/__tests__/fetch-remote-file.js index 57191a7ded0de..86c6485edf7b4 100644 --- a/packages/gatsby-core-utils/src/__tests__/fetch-remote-file.js +++ b/packages/gatsby-core-utils/src/__tests__/fetch-remote-file.js @@ -572,22 +572,15 @@ Fetch details: "Unable to fetch: http://external.com/network-error.svg --- -Reason: Exceeded maximum retry attempts (3) (Response code 503 (Service Unavailable)) +Reason: ECONNREFUSED --- Fetch details: { - \\"attempts\\": 3, + \\"attempt\\": 3, \\"method\\": \\"GET\\", - \\"responseStatusCode\\": 503, - \\"responseStatusMessage\\": \\"Service Unavailable\\", \\"requestHeaders\\": { \\"user-agent\\": \\"got (https://github.com/sindresorhus/got)\\", \\"accept-encoding\\": \\"gzip, deflate, br\\" - }, - \\"responseHeaders\\": { - \\"x-powered-by\\": \\"msw\\", - \\"content-length\\": \\"12\\", - \\"content-type\\": \\"text/html\\" } } ---" diff --git a/packages/gatsby-core-utils/src/fetch-remote-file.ts b/packages/gatsby-core-utils/src/fetch-remote-file.ts index a5204a35aec8d..c0462c0f6c55a 100644 --- a/packages/gatsby-core-utils/src/fetch-remote-file.ts +++ b/packages/gatsby-core-utils/src/fetch-remote-file.ts @@ -59,17 +59,16 @@ function range(start: number, end: number): Array { // Based on the defaults of https://github.com/JustinBeckwith/retry-axios const STATUS_CODES_TO_RETRY = [...range(100, 200), 429, ...range(500, 600)] -// @todo these are not implemented yet -// const ERROR_CODES_TO_RETRY = [ -// `ETIMEDOUT`, -// `ECONNRESET`, -// `EADDRINUSE`, -// `ECONNREFUSED`, -// `EPIPE`, -// `ENOTFOUND`, -// `ENETUNREACH`, -// `EAI_AGAIN`, -// ] +const ERROR_CODES_TO_RETRY = [ + `ETIMEDOUT`, + `ECONNRESET`, + `EADDRINUSE`, + `ECONNREFUSED`, + `EPIPE`, + `ENOTFOUND`, + `ENETUNREACH`, + `EAI_AGAIN`, +] let fetchCache = new Map() let latestBuildId = `` @@ -385,20 +384,25 @@ function requestRemoteNode( fsWriteStream.close() fs.removeSync(tmpFilename) + if (!(error instanceof RequestError)) { + return reject(error) + } + // This is a replacement for the stream retry logic of got // till we can update all got instances to v12 // https://github.com/sindresorhus/got/blob/main/documentation/7-retry.md // https://github.com/sindresorhus/got/blob/main/documentation/3-streams.md#retry + const statusCode = error.response?.statusCode + const errorCode = error.code || error.message // got gives error.code, but msw/node returns the error codes in the message only + if ( // HTTP STATUS CODE ERRORS - error instanceof RequestError && - error.response?.statusCode && - STATUS_CODES_TO_RETRY.includes(error.response.statusCode) - // || + (statusCode && STATUS_CODES_TO_RETRY.includes(statusCode)) || // GENERAL NETWORK ERRORS - // (error.code && ERROR_CODES_TO_RETRY.includes(error.code)) + (errorCode && ERROR_CODES_TO_RETRY.includes(errorCode)) ) { if (attempt < INCOMPLETE_RETRY_LIMIT) { + console.log(`Retrying:`, { url, attempt }) setTimeout(() => { resolve( requestRemoteNode( @@ -412,42 +416,36 @@ function requestRemoteNode( }, BACKOFF_TIME * attempt) return undefined - } else { - if (!(error instanceof RequestError)) { - return reject(error) - } + } + // Throw user friendly error + error.message = [ + `Unable to fetch:`, + url, + `---`, + `Reason: ${error.message}`, + `---`, + ].join(`\n`) + + // Gather details about what went wrong from the error object and the request + const details = Object.entries({ + attempt, + method: error.options?.method, + errorCode: error.code, + responseStatusCode: error.response?.statusCode, + responseStatusMessage: error.response?.statusMessage, + requestHeaders: error.options?.headers, + responseHeaders: error.response?.headers, + }) + // Remove undefined values from the details to keep it clean + .reduce((a, [k, v]) => (v === undefined ? a : ((a[k] = v), a)), {}) - // Throw user friendly error + if (Object.keys(details).length) { error.message = [ - `Unable to fetch:`, - url, - `---`, - `Reason: ${error.message}`, + error.message, + `Fetch details:`, + JSON.stringify(details, null, 2), `---`, ].join(`\n`) - - // Gather details about what went wrong from the error object and the request - const details = Object.entries({ - attempt, - method: error.options?.method, - errorCode: error.code, - responseStatusCode: error.response?.statusCode, - responseStatusMessage: error.response?.statusMessage, - requestHeaders: error.options?.headers, - responseHeaders: error.response?.headers, - }) - // Remove undefined values from the details to keep it clean - .reduce((a, [k, v]) => (v === undefined ? a : ((a[k] = v), a)), {}) - - if (Object.keys(details).length) { - error.message = [ - error.message, - `Fetch details:`, - JSON.stringify(details, null, 2), - `---`, - ].join(`\n`) - } - return reject(error) } }