Skip to content

Commit d8c15b5

Browse files
committed
fix: internal retry should not fail action before the input timeout is exceeded
1 parent 3003423 commit d8c15b5

File tree

3 files changed

+55
-20
lines changed

3 files changed

+55
-20
lines changed

src/api.spec.ts

+31-15
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import {
1010
getWorkflowRunJobSteps,
1111
getWorkflowRunUrl,
1212
init,
13-
retryOrDie,
13+
retryOrTimeout,
1414
} from "./api.ts";
1515

1616
vi.mock("@actions/core");
@@ -427,7 +427,7 @@ describe("API", () => {
427427
});
428428
});
429429

430-
describe("retryOrDie", () => {
430+
describe("retryOrTimeout", () => {
431431
beforeEach(() => {
432432
vi.useFakeTimers();
433433
});
@@ -436,38 +436,54 @@ describe("API", () => {
436436
vi.useRealTimers();
437437
});
438438

439-
it("should return a populated array", async () => {
440-
const attempt = () => Promise.resolve([0]);
441-
expect(await retryOrDie(attempt, 1000)).toHaveLength(1);
439+
it("should return a result", async () => {
440+
const attemptResult = [0];
441+
const attempt = () => Promise.resolve(attemptResult);
442+
443+
const result = await retryOrTimeout(attempt, 1000);
444+
if (result.timeout) {
445+
expect.fail("expected retryOrTimeout not to timeout");
446+
}
447+
448+
expect(result.timeout).toStrictEqual(false);
449+
expect(result.value).toStrictEqual(attemptResult);
442450
});
443451

444-
it("should throw if the given timeout is exceeded", async () => {
452+
it("should return a timeout result if the given timeout is exceeded", async () => {
445453
// Never return data.
446454
const attempt = () => Promise.resolve([]);
447455

448-
const retryOrDiePromise = retryOrDie(attempt, 1000);
449-
vi.advanceTimersByTime(2000);
456+
const retryOrTimeoutPromise = retryOrTimeout(attempt, 1000);
450457
// eslint-disable-next-line @typescript-eslint/no-floating-promises
451458
vi.advanceTimersByTimeAsync(2000);
452459

453-
await expect(retryOrDiePromise).rejects.toThrow(
454-
"Timed out while attempting to fetch data",
455-
);
460+
const result = await retryOrTimeoutPromise;
461+
if (!result.timeout) {
462+
expect.fail("expected retryOrTimeout to timeout");
463+
}
464+
465+
expect(result.timeout).toStrictEqual(true);
456466
});
457467

458468
it("should retry to get a populated array", async () => {
469+
const attemptResult = [0];
459470
const attempt = vi
460471
.fn()
461-
.mockResolvedValue([0])
472+
.mockResolvedValue(attemptResult)
462473
.mockResolvedValueOnce([])
463474
.mockResolvedValueOnce([]);
464475

465-
const retryOrDiePromise = retryOrDie(attempt, 5000);
466-
vi.advanceTimersByTime(3000);
476+
const retryOrDiePromise = retryOrTimeout(attempt, 5000);
467477
// eslint-disable-next-line @typescript-eslint/no-floating-promises
468478
vi.advanceTimersByTimeAsync(3000);
469479

470-
expect(await retryOrDiePromise).toHaveLength(1);
480+
const result = await retryOrDiePromise;
481+
if (result.timeout) {
482+
expect.fail("expected retryOrTimeout not to timeout");
483+
}
484+
485+
expect(result.timeout).toStrictEqual(false);
486+
expect(result.value).toStrictEqual(attemptResult);
471487
expect(attempt).toHaveBeenCalledTimes(3);
472488
});
473489
});

src/api.ts

+15-4
Original file line numberDiff line numberDiff line change
@@ -235,25 +235,36 @@ export async function getWorkflowRunJobSteps(runId: number): Promise<string[]> {
235235
}
236236
}
237237

238+
type RetryOrTimeoutResult<T> = ResultFound<T> | ResultTimeout;
239+
240+
interface ResultFound<T> {
241+
timeout: false;
242+
value: T;
243+
}
244+
245+
interface ResultTimeout {
246+
timeout: true;
247+
}
248+
238249
/**
239250
* Attempt to get a non-empty array from the API.
240251
*/
241-
export async function retryOrDie<T>(
252+
export async function retryOrTimeout<T>(
242253
retryFunc: () => Promise<T[]>,
243254
timeoutMs: number,
244-
): Promise<T[]> {
255+
): Promise<RetryOrTimeoutResult<T[]>> {
245256
const startTime = Date.now();
246257
let elapsedTime = 0;
247258
while (elapsedTime < timeoutMs) {
248259
elapsedTime = Date.now() - startTime;
249260

250261
const response = await retryFunc();
251262
if (response.length > 0) {
252-
return response;
263+
return { timeout: false, value: response };
253264
}
254265

255266
await new Promise<void>((resolve) => setTimeout(resolve, 1000));
256267
}
257268

258-
throw new Error("Timed out while attempting to fetch data");
269+
return { timeout: true };
259270
}

src/main.ts

+9-1
Original file line numberDiff line numberDiff line change
@@ -37,13 +37,21 @@ async function run(): Promise<void> {
3737
core.debug(`Attempting to fetch Run IDs for Workflow ID ${workflowId}`);
3838

3939
// Get all runs for a given workflow ID
40-
const workflowRunIds = await api.retryOrDie(
40+
const fetchWorkflowRunIds = await api.retryOrTimeout(
4141
() => api.getWorkflowRunIds(workflowId),
4242
WORKFLOW_FETCH_TIMEOUT_MS > timeoutMs
4343
? timeoutMs
4444
: WORKFLOW_FETCH_TIMEOUT_MS,
4545
);
46+
if (fetchWorkflowRunIds.timeout) {
47+
core.debug("Timed out while attempting to fetch Workflow Run IDs");
48+
await new Promise((resolve) =>
49+
setTimeout(resolve, WORKFLOW_JOB_STEPS_RETRY_MS),
50+
);
51+
continue;
52+
}
4653

54+
const workflowRunIds = fetchWorkflowRunIds.value;
4755
core.debug(
4856
`Attempting to get step names for Run IDs: [${workflowRunIds.join(", ")}]`,
4957
);

0 commit comments

Comments
 (0)