-
Notifications
You must be signed in to change notification settings - Fork 1.4k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
update downloadTool to handle errors from response stream and retry
- Loading branch information
1 parent
6459481
commit eab6335
Showing
7 changed files
with
320 additions
and
51 deletions.
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,87 @@ | ||
import * as core from '@actions/core' | ||
import {RetryHelper} from '../src/retry-helper' | ||
|
||
let info: string[] | ||
let retryHelper: RetryHelper | ||
|
||
describe('retry-helper tests', () => { | ||
beforeAll(() => { | ||
// Mock @actions/core info() | ||
jest.spyOn(core, 'info').mockImplementation((message: string) => { | ||
info.push(message) | ||
}) | ||
|
||
retryHelper = new RetryHelper(3, 0, 0) | ||
}) | ||
|
||
beforeEach(() => { | ||
// Reset info | ||
info = [] | ||
}) | ||
|
||
afterAll(() => { | ||
// Restore | ||
jest.restoreAllMocks() | ||
}) | ||
|
||
it('first attempt succeeds', async () => { | ||
const actual = await retryHelper.execute(async () => { | ||
return 'some result' | ||
}) | ||
expect(actual).toBe('some result') | ||
expect(info).toHaveLength(0) | ||
}) | ||
|
||
it('second attempt succeeds', async () => { | ||
let attempts = 0 | ||
const actual = await retryHelper.execute(async () => { | ||
if (++attempts === 1) { | ||
throw new Error('some error') | ||
} | ||
|
||
return Promise.resolve('some result') | ||
}) | ||
expect(attempts).toBe(2) | ||
expect(actual).toBe('some result') | ||
expect(info).toHaveLength(2) | ||
expect(info[0]).toBe('some error') | ||
expect(info[1]).toMatch(/Waiting .+ seconds before trying again/) | ||
}) | ||
|
||
it('third attempt succeeds', async () => { | ||
let attempts = 0 | ||
const actual = await retryHelper.execute(async () => { | ||
if (++attempts < 3) { | ||
throw new Error(`some error ${attempts}`) | ||
} | ||
|
||
return Promise.resolve('some result') | ||
}) | ||
expect(attempts).toBe(3) | ||
expect(actual).toBe('some result') | ||
expect(info).toHaveLength(4) | ||
expect(info[0]).toBe('some error 1') | ||
expect(info[1]).toMatch(/Waiting .+ seconds before trying again/) | ||
expect(info[2]).toBe('some error 2') | ||
expect(info[3]).toMatch(/Waiting .+ seconds before trying again/) | ||
}) | ||
|
||
it('all attempts fail succeeds', async () => { | ||
let attempts = 0 | ||
let error: Error = (null as unknown) as Error | ||
try { | ||
await retryHelper.execute(() => { | ||
throw new Error(`some error ${++attempts}`) | ||
}) | ||
} catch (err) { | ||
error = err | ||
} | ||
expect(error.message).toBe('some error 3') | ||
expect(attempts).toBe(3) | ||
expect(info).toHaveLength(4) | ||
expect(info[0]).toBe('some error 1') | ||
expect(info[1]).toMatch(/Waiting .+ seconds before trying again/) | ||
expect(info[2]).toBe('some error 2') | ||
expect(info[3]).toMatch(/Waiting .+ seconds before trying again/) | ||
}) | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
import * as core from '@actions/core' | ||
|
||
/** | ||
* Internal class for retries | ||
*/ | ||
export class RetryHelper { | ||
private maxAttempts: number | ||
private minSeconds: number | ||
private maxSeconds: number | ||
|
||
constructor(maxAttempts: number, minSeconds: number, maxSeconds: number) { | ||
if (maxAttempts < 1) { | ||
throw new Error('max attempts should be greater than or equal to 1') | ||
} | ||
|
||
this.maxAttempts = maxAttempts | ||
this.minSeconds = Math.floor(minSeconds) | ||
this.maxSeconds = Math.floor(maxSeconds) | ||
if (this.minSeconds > this.maxSeconds) { | ||
throw new Error('min seconds should be less than or equal to max seconds') | ||
} | ||
} | ||
|
||
async execute<T>(action: () => Promise<T>): Promise<T> { | ||
let attempt = 1 | ||
while (attempt < this.maxAttempts) { | ||
// Try | ||
try { | ||
return await action() | ||
} catch (err) { | ||
core.info(err.message) | ||
} | ||
|
||
// Sleep | ||
const seconds = this.getSleepAmount() | ||
core.info(`Waiting ${seconds} seconds before trying again`) | ||
await this.sleep(seconds) | ||
attempt++ | ||
} | ||
|
||
// Last attempt | ||
return await action() | ||
} | ||
|
||
private getSleepAmount(): number { | ||
return ( | ||
Math.floor(Math.random() * (this.maxSeconds - this.minSeconds + 1)) + | ||
this.minSeconds | ||
) | ||
} | ||
|
||
private async sleep(seconds: number): Promise<void> { | ||
return new Promise(resolve => setTimeout(resolve, seconds * 1000)) | ||
} | ||
} |
Oops, something went wrong.