Visitar URL original
fix(app): audit-log-store stress test could trip vitest's internal 60… · voidstackloop/modelforge@9758571 · GitHub
Skip to content

Commit 9758571

Browse files
voidstackloopclaude
andcommitted
fix(app): audit-log-store stress test could trip vitest's internal 60s RPC watchdog on slow Windows CI
The 'caps retained events' test does 5,000+ synchronous recordEvent() calls with no yield point. On a slow Windows CI runner this crossed 60s of unbroken synchronous work (61.7s in the v1.4.0 release build), tripping vitest 3.x's own internal birpc RPC heartbeat — a hardcoded 60s timeout, independent of and not covered by testTimeout (only removed in vitest 4, see vitest-dev/vitest#8164/#8297). The whole run then fails with "[vitest-worker]: Timeout calling onTaskUpdate" even though every assertion passes. Fixed by yielding to the event loop via setImmediate every 250 iterations so the RPC channel stays serviced regardless of total wall-clock time. Applied the same hardening to the other 5,000+-iteration loop in this file (the addon-gated 2x-cap stress test) as cheap insurance against the same failure mode. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
1 parent 2193736 commit 9758571

1 file changed

Lines changed: 38 additions & 4 deletions

File tree

‎app/src/audit-log-store.test.ts‎

Lines changed: 38 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -39,13 +39,32 @@ describe("audit-log-store", () => {
3939

4040
it(
4141
"caps retained events instead of growing without bound",
42-
() => {
42+
async () => {
4343
// The cap is soft (see MAX_EVENTS/TRIM_BATCH in audit-log-store.ts):
4444
// the file may transiently hold up to MAX_EVENTS + TRIM_BATCH
4545
// events between trims, so the loop must actually cross that
4646
// ceiling for this test to mean anything.
4747
const iterations = auditLogStore.MAX_EVENTS + auditLogStore.TRIM_BATCH + 10;
48-
for (let i = 0; i < iterations; i++) auditLogStore.recordEvent("case-viewed", { targetId: String(i) });
48+
// Yielding every YIELD_EVERY iterations is load-bearing, not
49+
// cosmetic: this loop is a real, synchronous fs read-modify-
50+
// write on Windows CI (see the timeout comment below) and a
51+
// fully unyielded synchronous stretch past ~60s trips Vitest
52+
// 3.x's own internal worker RPC heartbeat — a hardcoded,
53+
// `testTimeout`-independent birpc timeout (default 60s,
54+
// unconfigurable pre-Vitest-4:
55+
// https://github.com/vitest-dev/vitest/issues/8164) — which
56+
// fails the whole run with "[vitest-worker]: Timeout calling
57+
// 'onTaskUpdate'" even though every assertion here passes.
58+
// `setImmediate` (a real macrotask yield, not just `await
59+
// Promise.resolve()`) gives the worker's event loop a chance to
60+
// service that RPC channel periodically so the heartbeat never
61+
// goes quiet for 60s straight, however long the test's total
62+
// wall-clock time ends up being.
63+
const YIELD_EVERY = 250;
64+
for (let i = 0; i < iterations; i++) {
65+
auditLogStore.recordEvent("case-viewed", { targetId: String(i) });
66+
if (i % YIELD_EVERY === 0) await new Promise((resolve) => setImmediate(resolve));
67+
}
4968
expect(auditLogStore.listEvents().length).toBeLessThanOrEqual(auditLogStore.MAX_EVENTS + auditLogStore.TRIM_BATCH);
5069
},
5170
// With the Rust addon built, recordEvent() appends in O(1) both below
@@ -225,9 +244,24 @@ describe("audit-log-store", () => {
225244
describe("fast-append path: stress and self-healing", () => {
226245
it.skipIf(!nativeAddonPresent)(
227246
"stays correct across multiple trim cycles (2x the cap)",
228-
() => {
247+
async () => {
229248
const iterations = auditLogStore.MAX_EVENTS * 2 + 10;
230-
for (let i = 0; i < iterations; i++) auditLogStore.recordEvent("case-viewed", { targetId: String(i) });
249+
// Same periodic macrotask yield as the "caps retained
250+
// events" test above, and for the same reason: a long
251+
// unyielded synchronous stretch here can trip Vitest 3.x's
252+
// own internal 60s worker RPC heartbeat independently of
253+
// this test's own timeout (see that test's comment for the
254+
// full explanation and the upstream issue link). Cheap
255+
// insurance even when the addon's O(1) path keeps this fast
256+
// in practice — `nativeAddonPresent` only checks that the
257+
// addon directory exists, not that it actually loaded at
258+
// runtime, so a silent fallback to the slow path here would
259+
// otherwise hit the exact same failure mode.
260+
const YIELD_EVERY = 250;
261+
for (let i = 0; i < iterations; i++) {
262+
auditLogStore.recordEvent("case-viewed", { targetId: String(i) });
263+
if (i % YIELD_EVERY === 0) await new Promise((resolve) => setImmediate(resolve));
264+
}
231265
const events = auditLogStore.listEvents();
232266
expect(events.length).toBeLessThanOrEqual(auditLogStore.MAX_EVENTS + auditLogStore.TRIM_BATCH);
233267

0 commit comments

Comments
 (0)