Skip to content

Commit

Permalink
fix: retry requests on error until timeout reached
Browse files Browse the repository at this point in the history
  • Loading branch information
Codex- committed Jan 30, 2024
1 parent 43c38e8 commit 8073adb
Show file tree
Hide file tree
Showing 4 changed files with 145 additions and 3 deletions.
29 changes: 28 additions & 1 deletion dist/index.mjs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

80 changes: 79 additions & 1 deletion src/api.spec.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,22 @@
import * as core from "@actions/core";
import * as github from "@actions/github";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import {
afterEach,
beforeEach,
describe,
expect,
it,
vi,
type MockInstance,
} from "vitest";

import {
getWorkflowRunActiveJobUrl,
getWorkflowRunActiveJobUrlRetry,
getWorkflowRunFailedJobs,
getWorkflowRunState,
init,
retryOnError,
} from "./api.ts";

vi.mock("@actions/core");
Expand Down Expand Up @@ -361,4 +370,73 @@ describe("API", () => {
});
});
});

describe("retryOnError", () => {
let warningLogSpy: MockInstance<
[
message: string | Error,
properties?: core.AnnotationProperties | undefined,
],
void
>;

beforeEach(() => {
vi.useFakeTimers();
warningLogSpy = vi.spyOn(core, "warning");
});

afterEach(() => {
vi.useRealTimers();
warningLogSpy.mockRestore();
});

it("should retry a function if it throws an error", async () => {
const funcName = "testFunc";
const errorMsg = "some error";
const testFunc = vi
.fn()
.mockImplementation(async () => "completed")
.mockImplementationOnce(async () => {
throw new Error(errorMsg);
});

const retryPromise = retryOnError(() => testFunc(), funcName);

// Progress timers to first failure
vi.advanceTimersByTime(500);
await vi.advanceTimersByTimeAsync(500);

expect(warningLogSpy).toHaveBeenCalledOnce();
expect(warningLogSpy).toHaveBeenCalledWith(
"retryOnError: An unexpected error has occurred:\n" +
` name: ${funcName}\n` +
` error: ${errorMsg}`,
);

// Progress timers to second success
vi.advanceTimersByTime(500);
await vi.advanceTimersByTimeAsync(500);
const result = await retryPromise;

expect(warningLogSpy).toHaveBeenCalledOnce();
expect(result).toStrictEqual("completed");
});

it("should throw the original error if timed out while calling the function", async () => {
const funcName = "testFunc";
const errorMsg = "some error";
const testFunc = vi.fn().mockImplementation(async () => {
await new Promise((resolve) => setTimeout(resolve, 1000));
throw new Error(errorMsg);
});

const retryPromise = retryOnError(() => testFunc(), funcName, 500);

vi.advanceTimersByTime(500);
// eslint-disable-next-line @typescript-eslint/no-floating-promises
vi.advanceTimersByTimeAsync(500);

await expect(retryPromise).rejects.toThrowError("some error");
});
});
});
32 changes: 32 additions & 0 deletions src/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -239,3 +239,35 @@ export async function getWorkflowRunActiveJobUrlRetry(

return "Unable to fetch URL";
}

export async function retryOnError<T>(
func: () => Promise<T>,
name: string,
timeout: number = 5000,
): Promise<T> {
const startTime = Date.now();
let elapsedTime = Date.now() - startTime;

while (elapsedTime < timeout) {
elapsedTime = Date.now() - startTime;
try {
return await func();
} catch (error) {
if (error instanceof Error) {
// We now exceed the time, so throw the error up
if (Date.now() - startTime >= timeout) {
throw error;
}

core.warning(
"retryOnError: An unexpected error has occurred:\n" +
` name: ${name}\n` +
` error: ${error.message}`,
);
}
await new Promise((resolve) => setTimeout(resolve, 1000));
}
}

throw new Error(`Timeout exceeded while attempting to retry ${name}`);
}
7 changes: 6 additions & 1 deletion src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
getWorkflowRunFailedJobs,
getWorkflowRunState,
init,
retryOnError,
WorkflowRunConclusion,
WorkflowRunStatus,
} from "./api.ts";
Expand Down Expand Up @@ -54,7 +55,11 @@ async function run(): Promise<void> {
attemptNo++;
elapsedTime = Date.now() - startTime;

const { status, conclusion } = await getWorkflowRunState(config.runId);
const { status, conclusion } = await retryOnError(
async () => getWorkflowRunState(config.runId),
"getWorkflowRunState",
400,
);

if (status === WorkflowRunStatus.Completed) {
switch (conclusion) {
Expand Down

0 comments on commit 8073adb

Please sign in to comment.