Skip to content
This repository was archived by the owner on Oct 10, 2022. It is now read-only.

Retry on ETIMEDOUT timeout #171

Merged
merged 1 commit into from
Oct 8, 2020
Merged
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
20 changes: 19 additions & 1 deletion src/index.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -553,6 +553,24 @@ test('Gives up retrying on API rate limiting after a timeout', async t => {
t.false(scope.isDone())
})

test('Retries on ETIMEDOUT connection errors', async t => {
const account_id = '17'
const retryAtMs = Date.now() + TEST_RATE_LIMIT_DELAY
const expectedResponse = { test: 'test' }
const scope = nock(origin)
.get(`${pathPrefix}/accounts/${account_id}`)
.replyWithError({ code: 'ETIMEDOUT' })
.get(`${pathPrefix}/accounts/${account_id}`)
.reply(200, expectedResponse)

const client = getClient()
const response = await client.getAccount({ account_id })

t.true(Date.now() >= retryAtMs)
t.deepEqual(response, expectedResponse)
t.true(scope.isDone())
})

test('Recreates a function body when handling API rate limiting', async t => {
const deploy_id = '3'
const path = 'testPath'
Expand Down Expand Up @@ -590,7 +608,7 @@ test('(Proxy) agent is passed as request option', async t => {
})

test('(Proxy) agent is not passed as request option if not set', async t => {
const account_id = '15'
const account_id = '16'
const scope = nock(origin)
.get(`${pathPrefix}/accounts/${account_id}`)
.reply(200)
Expand Down
19 changes: 13 additions & 6 deletions src/methods/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -69,21 +69,28 @@ const addAgent = function(NetlifyApi, opts) {
const makeRequestOrRetry = async function({ url, method, NetlifyApi, requestParams, opts }) {
for (let index = 0; index <= MAX_RETRY; index++) {
const optsA = getOpts(method, NetlifyApi, requestParams, opts)
const response = await makeRequest(url, optsA)
const { response, error } = await makeRequest(url, optsA)

if (shouldRetry(response, index)) {
if (shouldRetry({ response, error }) && index !== MAX_RETRY) {
await waitForRetry(response)
} else {
return response
continue
}

if (error !== undefined) {
throw error
}

return response
}
}

const makeRequest = async function(url, opts) {
try {
return await fetch(url, opts)
const response = await fetch(url, opts)
return { response }
} catch (error) {
throw getFetchError(error, url, opts)
const errorA = getFetchError(error, url, opts)
return { error: errorA }
}
}

Expand Down
17 changes: 12 additions & 5 deletions src/methods/retry.js
Original file line number Diff line number Diff line change
@@ -1,15 +1,21 @@
// When the API is rate limiting, the request is retried later
const shouldRetry = function(response, index) {
return response.status === RATE_LIMIT_STATUS && index !== MAX_RETRY
// We retry:
// - when receiving a rate limiting response
// - on network failures due to timeouts
const shouldRetry = function({ response = {}, error = {} }) {
return response.status === RATE_LIMIT_STATUS || RETRY_ERROR_CODES.includes(error.code)
}

const waitForRetry = async function(response) {
const delay = getDelay(response)
await sleep(delay)
}

const getDelay = function({ headers }) {
const rateLimitReset = headers.get(RATE_LIMIT_HEADER)
const getDelay = function(response) {
if (response === undefined) {
return DEFAULT_RETRY_DELAY
}

const rateLimitReset = response.headers.get(RATE_LIMIT_HEADER)

if (!rateLimitReset) {
return DEFAULT_RETRY_DELAY
Expand All @@ -28,5 +34,6 @@ const SECS_TO_MSECS = 1e3
const MAX_RETRY = 10
const RATE_LIMIT_STATUS = 429
const RATE_LIMIT_HEADER = 'X-RateLimit-Reset'
const RETRY_ERROR_CODES = ['ETIMEDOUT']

module.exports = { shouldRetry, waitForRetry, MAX_RETRY }