Skip to content

Commit 23d8e78

Browse files
fix: add ETIMEDOUT (#1203)
Co-authored-by: Joe Becher <[email protected]>
1 parent 73491ef commit 23d8e78

File tree

2 files changed

+116
-3
lines changed

2 files changed

+116
-3
lines changed

src/helpers/web.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -63,10 +63,12 @@ async function requestWithRetry(
6363
return response
6464
} catch (error: unknown) {
6565
if (
66-
((error instanceof errors.UndiciError && error.code == 'ECONNRESET') ||
66+
(
67+
(error instanceof errors.UndiciError && error.code == 'ECONNRESET') ||
68+
(error instanceof errors.UndiciError && error.code == 'ETIMEDOUT') ||
6769
error instanceof errors.ConnectTimeoutError ||
68-
error instanceof errors.SocketError) &&
69-
retryCount < maxRetries
70+
error instanceof errors.SocketError
71+
) && retryCount < maxRetries
7072
) {
7173
const backoffDelay = baseBackoffDelayMs * 2 ** retryCount
7274
await sleep(backoffDelay)

test/helpers/web.test.ts

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -403,6 +403,117 @@ describe('Web Helpers', () => {
403403
expect(response.resultURL.href).toStrictEqual('https://results.codecov.io/')
404404
});
405405
});
406+
407+
describe('Uploader should retry POST on ETIMEDOUT', () => {
408+
it('should retry and return response data when ETIMEDOUT occurs once', async () => {
409+
const envs: UploaderEnvs = {}
410+
const args: UploaderArgs = {
411+
flags: '',
412+
slug: '',
413+
upstream: '',
414+
}
415+
const error = new errors.UndiciError("conn reset error")
416+
error.code = 'ETIMEDOUT'
417+
uploadURL = 'http://codecov.io'
418+
mockAgent.disableNetConnect()
419+
mockClient = mockAgent.get(uploadURL)
420+
421+
mockClient.intercept({
422+
method: 'POST',
423+
path: `/upload/v4?package=uploader-${version}&token=${token}&hello`,
424+
}).replyWithError(new errors.ConnectTimeoutError('timeout error')).times(1)
425+
426+
mockClient.intercept({
427+
method: 'POST',
428+
path: `/upload/v4?package=uploader-${version}&token=${token}&hello`,
429+
}).replyWithError(error).times(1)
430+
431+
mockClient.intercept({
432+
method: 'POST',
433+
path: `/upload/v4?package=uploader-${version}&token=${token}&hello`,
434+
}).reply(200, 'testPOSTHTTP')
435+
436+
const responseData = await uploadToCodecovPOST(new URL(uploadURL), token, query, source, envs, args);
437+
try {
438+
expect(responseData).toBe('testPOSTHTTP')
439+
} catch (error) {
440+
throw new Error(`${responseData} - ${error}`)
441+
}
442+
});
443+
444+
it('should fail with error if ETIMEDOUT happens 5 times', async () => {
445+
const mockSleep = jest.spyOn(utilModule, 'sleep').mockResolvedValue(42)
446+
const envs: UploaderEnvs = {}
447+
const args: UploaderArgs = {
448+
flags: '',
449+
slug: '',
450+
upstream: '',
451+
}
452+
const error = new errors.UndiciError("conn reset error")
453+
error.code = 'ETIMEDOUT'
454+
uploadURL = 'http://codecov.io'
455+
mockAgent.disableNetConnect()
456+
mockClient = mockAgent.get(uploadURL)
457+
458+
mockClient.intercept({
459+
method: 'POST',
460+
path: `/upload/v4?package=uploader-${version}&token=${token}&hello`,
461+
}).replyWithError(error).times(5)
462+
463+
mockClient.intercept({
464+
method: 'POST',
465+
path: `/upload/v4?package=uploader-${version}&token=${token}&hello`,
466+
}).reply(200, 'testPOSTHTTP')
467+
468+
try {
469+
await uploadToCodecovPOST(new URL(uploadURL), token, query, source, envs, args);
470+
expect(true).toBe(false)
471+
} catch (error) {
472+
expect(error).toBeInstanceOf(errors.UndiciError)
473+
expect(mockSleep).toBeCalledTimes(4)
474+
}
475+
})
476+
});
477+
478+
describe('Uploader should retry PUT on ETIMEDOUT', () => {
479+
it('should retry and return response data when ETIMEDOUT occurs once', async () => {
480+
// Replace the original setTimeout with fakeSetTimeout
481+
const envs: UploaderEnvs = {}
482+
const args: UploaderArgs = {
483+
flags: '',
484+
slug: '',
485+
upstream: '',
486+
}
487+
jest.spyOn(console, 'log').mockImplementation(() => void {})
488+
const postResults: PostResults = {
489+
putURL: new URL('https://codecov.io'),
490+
resultURL: new URL('https://results.codecov.io'),
491+
}
492+
493+
const error = new errors.UndiciError("conn reset error")
494+
error.code = 'ETIMEDOUT'
495+
mockAgent.disableNetConnect()
496+
mockClient = mockAgent.get(postResults.putURL.origin)
497+
498+
mockClient.intercept({
499+
method: 'PUT',
500+
path: `/`,
501+
}).replyWithError(new errors.ConnectTimeoutError('timeout error')).times(1)
502+
503+
mockClient.intercept({
504+
method: 'PUT',
505+
path: `/`,
506+
}).replyWithError(error).times(1)
507+
508+
mockClient.intercept({
509+
method: 'PUT',
510+
path: `/`,
511+
}).reply(200, postResults.resultURL.href)
512+
513+
const response = await uploadToCodecovPUT(postResults, uploadFile, envs, args)
514+
expect(response.resultURL.href).toStrictEqual('https://results.codecov.io/')
515+
});
516+
});
406517
})
407518

408519
describe('displayChangelog()', () => {

0 commit comments

Comments
 (0)