Skip to content

Commit c4f21b7

Browse files
authored
Merge pull request #247 from Codex-/backport_retry_fix
Prevent internal API retries for timing out action run before input timeout is exceeded
2 parents 446bb1d + d46d451 commit c4f21b7

File tree

4 files changed

+94
-36
lines changed

4 files changed

+94
-36
lines changed

dist/index.mjs

Lines changed: 34 additions & 9 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/api.spec.ts

Lines changed: 31 additions & 15 deletions
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

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -219,41 +219,52 @@ export async function getWorkflowRunJobSteps(runId: number): Promise<string[]> {
219219
"Fetched Workflow Run Job Steps:\n" +
220220
` Repository: ${config.owner}/${config.repo}\n` +
221221
` Workflow Run ID: ${runId}\n` +
222-
` Jobs Fetched: [${jobs.map((job) => job.id).join(", ")}]` +
222+
` Jobs Fetched: [${jobs.map((job) => job.id).join(", ")}]\n` +
223223
` Steps Fetched: [${steps.join(", ")}]`,
224224
);
225225

226226
return steps;
227227
} catch (error) {
228228
if (error instanceof Error) {
229229
core.error(
230-
`getWorkflowRunJobs: An unexpected error has occurred: ${error.message}`,
230+
`getWorkflowRunJobSteps: An unexpected error has occurred: ${error.message}`,
231231
);
232232
core.debug(error.stack ?? "");
233233
}
234234
throw error;
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

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -37,13 +37,18 @@ 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),
42-
WORKFLOW_FETCH_TIMEOUT_MS > timeoutMs
43-
? timeoutMs
44-
: WORKFLOW_FETCH_TIMEOUT_MS,
42+
Math.max(WORKFLOW_FETCH_TIMEOUT_MS, timeoutMs),
4543
);
44+
if (fetchWorkflowRunIds.timeout) {
45+
core.debug(
46+
`Timed out while attempting to fetch Workflow Run IDs, waited ${Date.now() - startTime}ms`,
47+
);
48+
break;
49+
}
4650

51+
const workflowRunIds = fetchWorkflowRunIds.value;
4752
core.debug(
4853
`Attempting to get step names for Run IDs: [${workflowRunIds.join(", ")}]`,
4954
);
@@ -88,10 +93,11 @@ async function run(): Promise<void> {
8893
);
8994
}
9095

91-
throw new Error("Timeout exceeded while attempting to get Run ID");
96+
core.error("Failed: Timeout exceeded while attempting to get Run ID");
97+
core.setFailed("Timeout exceeded while attempting to get Run ID");
9298
} catch (error) {
9399
if (error instanceof Error) {
94-
core.error(`Failed to complete: ${error.message}`);
100+
core.error(`Failed: ${error.message}`);
95101
core.warning("Does the token have the correct permissions?");
96102
core.debug(error.stack ?? "");
97103
core.setFailed(error.message);

0 commit comments

Comments
 (0)