diff --git a/__tests__/wait.test.ts b/__tests__/wait.test.ts index f7331a3..c88ca3c 100644 --- a/__tests__/wait.test.ts +++ b/__tests__/wait.test.ts @@ -198,6 +198,69 @@ describe("wait", () => { `✋Awaiting run ${input.runId - 1} ...` ); }); + + it("will wait for all previous runs with exponential backoff", async () => { + input.exponentialBackoffRetries = true; + const inProgressRuns = [ + { + id: 1, + status: "in_progress", + html_url: "1", + }, + { + id: 2, + status: "in_progress", + html_url: "2", + }, + { + id: 3, + status: "queued", + html_url: "3", + }, + ]; + // Give the current run an id that makes it the last in the queue. + input.runId = inProgressRuns.length + 1; + // Add an in-progress run to simulate a run getting queued _after_ the one we + // are interested in. + inProgressRuns.push({ + id: input.runId + 1, + status: "in_progress", + html_url: input.runId + 1 + "", + }); + + const mockedRunsFunc = jest.fn(); + mockedRunsFunc + .mockReturnValueOnce(Promise.resolve(inProgressRuns.slice(0))) + .mockReturnValueOnce(Promise.resolve(inProgressRuns.slice(1))) + // Finally return just the run that was queued _after_ the "input" run. + .mockReturnValue( + Promise.resolve(inProgressRuns.slice(inProgressRuns.length - 1)) + ); + + const githubClient = { + runs: mockedRunsFunc, + run: jest.fn(), + workflows: async (owner: string, repo: string) => + Promise.resolve([workflow]), + }; + + const messages: Array = []; + const waiter = new Waiter( + workflow.id, + githubClient, + input, + (message: string) => { + messages.push(message); + } + ); + await waiter.wait(); + assert.deepEqual(messages, [ + `✋Awaiting run ${input.runId - 1} ...`, + `🔁 Attempt 1, next will be in 1 seconds`, + `✋Awaiting run ${input.runId - 1} ...`, + `🔁 Attempt 2, next will be in 2 seconds`, + ]); + }); }); }); }); diff --git a/src/wait.ts b/src/wait.ts index c235799..9c40302 100644 --- a/src/wait.ts +++ b/src/wait.ts @@ -11,6 +11,7 @@ export class Waiter implements Wait { private input: Input; private githubClient: GitHub; private workflowId: any; + private attempt: number; constructor( workflowId: any, @@ -22,9 +23,12 @@ export class Waiter implements Wait { this.input = input; this.githubClient = githubClient; this.info = info; + this.attempt = 0; } wait = async (secondsSoFar?: number) => { + let pollingInterval = this.input.pollIntervalSeconds; + if ( this.input.continueAfterSeconds && (secondsSoFar || 0) >= this.input.continueAfterSeconds @@ -60,9 +64,19 @@ export class Waiter implements Wait { const previousRun = previousRuns[0]; this.info(`✋Awaiting run ${previousRun.html_url} ...`); - await new Promise((resolve) => - setTimeout(resolve, this.input.pollIntervalSeconds * 1000) - ); - return this.wait((secondsSoFar || 0) + this.input.pollIntervalSeconds); + + if (this.input.exponentialBackoffRetries) { + pollingInterval = + this.input.pollIntervalSeconds * (2 * this.attempt || 1); + this.info( + `🔁 Attempt ${ + this.attempt + 1 + }, next will be in ${pollingInterval} seconds` + ); + this.attempt++; + } + + await new Promise((resolve) => setTimeout(resolve, pollingInterval * 1000)); + return this.wait((secondsSoFar || 0) + pollingInterval); }; }