Skip to content
Open
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
37 changes: 19 additions & 18 deletions lib/recorder.js
Original file line number Diff line number Diff line change
Expand Up @@ -202,34 +202,35 @@ export default {
debug(chalk.gray(`${currentQueue()} Queued | ${taskName}`))

return (promise = Promise.resolve(promise).then(res => {
// prefer options for non-conditional retries
const retryOpts = this.retries
.sort((r1, r2) => r1.when && !r2.when)
.slice(-1)
.pop()
// no retries or unnamed tasks
debug(`${currentQueue()} Running | ${taskName} | Timeout: ${timeout || 'None'}`)
if (retryOpts) debug(`${currentQueue()} Retry opts`, JSON.stringify(retryOpts))

if (!retryOpts || !taskName || !retry) {
const runTask = () => {
const [promise, timer] = getTimeoutPromise(timeout, taskName)
return Promise.race([promise, Promise.resolve(res).then(fn)]).finally(() => clearTimeout(timer))
}

// no retries or unnamed tasks
if (!this.retries.length || !taskName || !retry) return runTask()

const retryRules = this.retries.slice().reverse()
return promiseRetry(Object.assign({}, defaultRetryOptions, retryOpts), (retry, number) => {
if (number > 1) output.log(`${currentQueue()}Retrying... Attempt #${number}`)
const [promise, timer] = getTimeoutPromise(timeout, taskName)
return Promise.race([promise, Promise.resolve(res).then(fn)])
.finally(() => clearTimeout(timer))
.catch(err => {
// prefer options for non-conditional retries, otherwise use the latest rule matching the error
const findRetryOpts = err => retryRules.find(r => !r.when) || retryRules.find(r => r.when(err))

return runTask().catch(firstErr => {
if (ignoredErrs.includes(firstErr)) return
const retryOpts = findRetryOpts(firstErr)
if (!retryOpts) throw firstErr
debug(`${currentQueue()} Retry opts`, JSON.stringify(retryOpts))

return promiseRetry(Object.assign({}, defaultRetryOptions, retryOpts), (retry, number) => {
if (number === 1) return retry(firstErr)
output.log(`${currentQueue()}Retrying... Attempt #${number}`)
return runTask().catch(err => {
if (ignoredErrs.includes(err)) return
for (const retryObj of retryRules) {
if (!retryObj.when) return retry(err)
if (retryObj.when && retryObj.when(err)) return retry(err)
}
if (findRetryOpts(err)) return retry(err)
throw err
})
})
})
}))
},
Expand Down
22 changes: 22 additions & 0 deletions test/unit/recorder_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,28 @@ describe('Recorder', () => {
expect(attempts[1] - attempts[0]).to.be.lessThan(500, 'second retry must use default minTimeout, not leaked 800ms')
})

it('should use timing opts of the retry rule matching the error', async function () {
this.timeout(5000)

recorder.retries = []
const attempts = []
recorder.retry({ retries: 1, minTimeout: 600, factor: 1, when: err => err.message === 'not found' })
recorder.retry({ retries: 1, minTimeout: 10, factor: 1, when: err => err.message.includes('context') })
recorder.add(
() => {
attempts.push(Date.now())
if (attempts.length < 2) throw new Error('not found')
},
undefined,
undefined,
true,
)
await recorder.promise()

expect(attempts).to.have.length(2)
expect(attempts[1] - attempts[0]).to.be.greaterThan(500)
})

it('should prefer opts for non-when retry when possible', () => {
let counter = 0
const errorText = 'noerror'
Expand Down
Loading