Skip to content

Commit a4a5013

Browse files
authored
perf(test): eliminate two more real-wait retry-backoff hotspots (#8573)
* perf(test): eliminate two more real-wait retry-backoff hotspots fetchReesPingWithRetry (REES /v1/ping 503 retry) and netuid-verification's fetchWithRetry both paid their real production backoff delay in tests that exercise the retry path, adding ~4s of pure wall-clock wait across the affected suites. Add the same settable-override pattern already used for the other suite-wide retry delays, defaulting to the real production value and only overridden to 0 in test/helpers/vitest-setup.ts. Also switch the two probeReesSecretAtStartup regression tests off a fixed 1100ms sleep-then-assert (needed because the probe is fire-and-forget) onto vi.waitFor, so they settle as soon as the now-fast retries actually finish instead of always paying the old worst-case wait. * perf(test): eliminate real ECONNRESET backoff sleeps in pg-queue tests retryPoolQuery's real 500ms-multiplier backoff ran for real in the three PG connection-resilience tests in selfhost-pg-queue.test.ts (only Date was faked, not setTimeout) -- ~6.5s of pure wall-clock wait across those three tests alone. Add the same settable-override pattern used elsewhere in this file's fixtures, scoped to this test file only (not the global vitest-setup default) since pg-queue.ts isn't otherwise imported broadly across the suite. Production default and behavior unchanged.
1 parent 858153b commit a4a5013

6 files changed

Lines changed: 46 additions & 8 deletions

File tree

src/review/content-lane/netuid-verification.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,14 @@ const RETRYABLE_STATUS = new Set([408, 425, 429, 500, 502, 503, 504]);
3737

3838
const sleep = (ms: number): Promise<void> => new Promise((resolve) => setTimeout(resolve, ms));
3939

40+
let netuidRetryBaseDelayMsOverride: number | null = null;
41+
42+
/** Test-only: collapses fetchWithRetry's exponential backoff to near-zero so a retry/exhaustion test
43+
* doesn't pay real wall-clock time (the DEFAULT_BASE_DELAY_MS constant and production default are unchanged). */
44+
export function setNetuidRetryBaseDelayMsForTest(value: number | null): void {
45+
netuidRetryBaseDelayMsOverride = value;
46+
}
47+
4048
/** Minimal fetch-with-retry (inlined from reviewbot core/fetch-retry.ts defaults). Retries on a
4149
* thrown error or a retryable status, with exponential backoff + a per-attempt timeout. */
4250
async function fetchWithRetry(
@@ -46,7 +54,7 @@ async function fetchWithRetry(
4654
opts: { retries?: number; baseDelayMs?: number; timeoutMs?: number } = {},
4755
): Promise<Response> {
4856
const retries = opts.retries ?? DEFAULT_RETRIES;
49-
const baseDelayMs = opts.baseDelayMs ?? DEFAULT_BASE_DELAY_MS;
57+
const baseDelayMs = opts.baseDelayMs ?? netuidRetryBaseDelayMsOverride ?? DEFAULT_BASE_DELAY_MS;
5058
const timeoutMs = opts.timeoutMs ?? DEFAULT_TIMEOUT_MS;
5159
let lastError: unknown;
5260
for (let attempt = 0; attempt <= retries; attempt += 1) {

src/review/enrichment-wire.ts

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,18 @@ function sharedSecretWasNormalized(
7373
const REES_PING_NOT_READY_RETRIES = 2;
7474
const REES_PING_NOT_READY_RETRY_DELAY_MS = 500;
7575

76+
let reesPingNotReadyRetryDelayMsOverride: number | null = null;
77+
78+
/** Test-only: collapses the real inter-retry wait so probeReesSecretAtStartup's retry tests don't pay
79+
* REES_PING_NOT_READY_RETRIES * REES_PING_NOT_READY_RETRY_DELAY_MS of real wall-clock time. */
80+
export function setReesPingNotReadyRetryDelayMsForTest(value: number | null): void {
81+
reesPingNotReadyRetryDelayMsOverride = value;
82+
}
83+
84+
function reesPingNotReadyRetryDelayMs(): number {
85+
return reesPingNotReadyRetryDelayMsOverride ?? REES_PING_NOT_READY_RETRY_DELAY_MS;
86+
}
87+
7688
async function fetchReesPingWithRetry(url: string, secret: string): Promise<Response> {
7789
const request = () =>
7890
fetch(url, {
@@ -85,7 +97,7 @@ async function fetchReesPingWithRetry(url: string, secret: string): Promise<Resp
8597
});
8698
let response = await request();
8799
for (let attempt = 0; attempt < REES_PING_NOT_READY_RETRIES && response.status === 503; attempt += 1) {
88-
await new Promise((resolve) => setTimeout(resolve, REES_PING_NOT_READY_RETRY_DELAY_MS));
100+
await new Promise((resolve) => setTimeout(resolve, reesPingNotReadyRetryDelayMs()));
89101
response = await request();
90102
}
91103
return response;

src/selfhost/pg-queue.ts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,17 +86,26 @@ function isPgSqlStateConnectionError(err: unknown): boolean {
8686
return hasErrorCode(err, PG_SQLSTATE_CONNECTION_CODES);
8787
}
8888

89+
let pgRetryPoolQueryDelayMsOverride: number | null = null;
90+
91+
/** Test-only: collapses retryPoolQuery's per-attempt backoff to near-zero so a connection-error retry
92+
* test doesn't pay real wall-clock time (the delayMs default and production behavior are unchanged). */
93+
export function setPgRetryPoolQueryDelayMsForTest(value: number | null): void {
94+
pgRetryPoolQueryDelayMsOverride = value;
95+
}
96+
8997
/** Retry a pool query up to `retries` times on transient connection errors, with a short delay
9098
* between attempts. The pool will establish a new connection automatically. */
9199
async function retryPoolQuery<T>(fn: () => Promise<T>, retries = 3, delayMs = 500): Promise<T> {
100+
const effectiveDelayMs = pgRetryPoolQueryDelayMsOverride ?? delayMs;
92101
let lastErr: unknown;
93102
for (let attempt = 0; attempt <= retries; attempt++) {
94103
try {
95104
return await fn();
96105
} catch (err) {
97106
lastErr = err;
98107
if (!isPgConnectionError(err) || attempt === retries) throw err;
99-
await new Promise((resolve) => setTimeout(resolve, delayMs * (attempt + 1)));
108+
await new Promise((resolve) => setTimeout(resolve, effectiveDelayMs * (attempt + 1)));
100109
}
101110
}
102111
throw lastErr;

test/helpers/vitest-setup.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,11 @@
99
import { setReviewFilesEmptyRetryDelayMsForTest } from "../../src/github/backfill";
1010
import { setGithubRateLimitRetrySleepCapMsForTest } from "../../src/github/client";
1111
import { setMergeStateUnknownRetryDelayMsForTest } from "../../src/queue/ci-resolution";
12+
import { setReesPingNotReadyRetryDelayMsForTest } from "../../src/review/enrichment-wire";
13+
import { setNetuidRetryBaseDelayMsForTest } from "../../src/review/content-lane/netuid-verification";
1214

1315
setReviewFilesEmptyRetryDelayMsForTest(0);
1416
setGithubRateLimitRetrySleepCapMsForTest(0);
1517
setMergeStateUnknownRetryDelayMsForTest(0);
18+
setReesPingNotReadyRetryDelayMsForTest(0);
19+
setNetuidRetryBaseDelayMsForTest(0);

test/unit/enrichment-wire.test.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -150,8 +150,9 @@ describe("probeReesSecretAtStartup", () => {
150150
globalThis.fetch = fetchSpy as unknown as typeof fetch;
151151
const errSpy = vi.spyOn(console, "error").mockImplementation(() => {});
152152
probeReesSecretAtStartup(env({ REES_URL: "https://rees.example", REES_SHARED_SECRET: "s3cret" }));
153-
await new Promise((resolve) => setTimeout(resolve, 1100));
154-
expect(fetchSpy).toHaveBeenCalledTimes(3); // the first attempt + 2 retries, all still 503
153+
// fetchReesPingWithRetry is fire-and-forget; poll instead of sleeping the retry budget's worst case
154+
// (the test's own vitest-setup override collapses the real inter-retry delay to 0, so this settles fast).
155+
await vi.waitFor(() => expect(fetchSpy).toHaveBeenCalledTimes(3)); // the first attempt + 2 retries, all still 503
155156
const parsed = errSpy.mock.calls.map((c) => JSON.parse(c[0] as string));
156157
expect(parsed.some((p) => p.event === "rees_ping_error" && p.status === 503)).toBe(true);
157158
errSpy.mockRestore();
@@ -167,8 +168,7 @@ describe("probeReesSecretAtStartup", () => {
167168
const logSpy = vi.spyOn(console, "log").mockImplementation(() => {});
168169
const errSpy = vi.spyOn(console, "error").mockImplementation(() => {});
169170
probeReesSecretAtStartup(env({ REES_URL: "https://rees.example", REES_SHARED_SECRET: "s3cret" }));
170-
await new Promise((resolve) => setTimeout(resolve, 1100));
171-
expect(fetchSpy).toHaveBeenCalledTimes(2);
171+
await vi.waitFor(() => expect(fetchSpy).toHaveBeenCalledTimes(2));
172172
expect(logSpy.mock.calls.some((c) => JSON.parse(c[0] as string).event === "rees_ping_ok")).toBe(true);
173173
expect(errSpy).not.toHaveBeenCalled();
174174
logSpy.mockRestore();

test/unit/selfhost-pg-queue.test.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
// Real-Postgres integration paths (migrations, pg-adapter translation) live in test/integration/selfhost-pg.test.ts.
33
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
44
import type { Pool, QueryResult } from "pg";
5-
import { createPgQueue } from "../../src/selfhost/pg-queue";
5+
import { createPgQueue, setPgRetryPoolQueryDelayMsForTest } from "../../src/selfhost/pg-queue";
66
import { queueSnapshotFromBinding } from "../../src/selfhost/queue-common";
77
import { renderMetrics, resetMetrics } from "../../src/selfhost/metrics";
88
import { RetryableJobError } from "../../src/queue/retryable";
@@ -15,6 +15,11 @@ import type { JobMessage } from "../../src/types";
1515
// "unavailable" (null, never gates) here; individual host-load tests override the mock explicitly.
1616
vi.mock("../../src/selfhost/host-pressure", () => ({ hostLoadAvg1PerCore: vi.fn(() => null) }));
1717

18+
// The PG connection-resilience tests below deliberately trigger retryPoolQuery's real ECONNRESET retry
19+
// path; collapse its per-attempt backoff to near-zero so they don't pay real wall-clock time for it
20+
// (the delayMs default and production behavior in src/selfhost/pg-queue.ts are unchanged).
21+
setPgRetryPoolQueryDelayMsForTest(0);
22+
1823
const msg = (t: string): JobMessage => ({ type: t }) as unknown as JobMessage;
1924
const webhook = (sender: { login: string; type: string }, eventName = "issue_comment", action = "edited"): JobMessage =>
2025
({

0 commit comments

Comments
 (0)