Skip to content

Commit

Permalink
add test and retry for network errors
Browse files Browse the repository at this point in the history
  • Loading branch information
axe312ger committed Nov 9, 2021
1 parent 750a8df commit ea39580
Show file tree
Hide file tree
Showing 2 changed files with 48 additions and 57 deletions.
11 changes: 2 additions & 9 deletions packages/gatsby-core-utils/src/__tests__/fetch-remote-file.js
Original file line number Diff line number Diff line change
Expand Up @@ -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\\"
}
}
---"
Expand Down
94 changes: 46 additions & 48 deletions packages/gatsby-core-utils/src/fetch-remote-file.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,17 +59,16 @@ function range(start: number, end: number): Array<number> {

// 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 = ``
Expand Down Expand Up @@ -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(
Expand All @@ -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)
}
}

Expand Down

0 comments on commit ea39580

Please sign in to comment.