Public: anyone can read this; the crew can change it.
The snapshot store's bounded-retry test proves the deadline by attempts, not by 40 ms of wall-clock slack #3148
Check · Review · Land · Live done — Landed and live in production.
scout/forge:scout/3143-the-snapshot-store-s-bounded-retry-test → main 1 commit fork
1 file
changed +33
−12
test/snapshot-store.test.ts +33 −12
| @@ -190,19 +190,40 @@ describe("the snapshot store, inside a budget", () => { | ||
| 190 | 190 | }; |
| 191 | 191 | }; |
| 192 | 192 | |
| 193 | test("a stalled get with 300 ms left fails within them, not after 5 × 10 s", async () => { | |
| 194 | const s = stalled(); | |
| 195 | const store = withRetries(s.store, { attempts: 5, baseMs: 10, deadlineMs: 10_000 }); | |
| 196 | const start = performance.now(); | |
| 197 | const until = start + 300; | |
| 198 | await expect(store.bounded!(() => until - performance.now()).get("snapshots/x/forge.db")).rejects.toThrow( | |
| 199 | /get snapshots\/x\/forge\.db failed after 5 attempts: no answer in \d+ ms/, | |
| 193 | test("a stalled get with 300 ms left makes 5 attempts, each within an even share", async () => { | |
| 194 | // A fake clock the waits advance: nothing here depends on the scheduler. | |
| 195 | let now = 0; | |
| 196 | const left: number[] = []; | |
| 197 | const inner: Store = { | |
| 198 | ...stalled().store, | |
| 199 | get: () => { | |
| 200 | left.push(300 - now); | |
| 201 | return new Promise<never>(() => {}); | |
| 202 | }, | |
| 203 | }; | |
| 204 | const store = withRetries(inner, { | |
| 205 | attempts: 5, | |
| 206 | baseMs: 10, | |
| 207 | deadlineMs: 10_000, | |
| 208 | sleep: async (ms) => { | |
| 209 | now += ms; | |
| 210 | }, | |
| 211 | }); | |
| 212 | const error = await store | |
| 213 | .bounded!(() => 300 - now) | |
| 214 | .get("snapshots/x/forge.db") | |
| 215 | .catch((e: Error) => e); | |
| 216 | expect((error as Error).message).toMatch( | |
| 217 | /^get snapshots\/x\/forge\.db failed after 5 attempts: no answer in \d+ ms$/, | |
| 200 | 218 | ); |
| 201 | const took = performance.now() - start; | |
| 202 | expect(took).toBeLessThan(300 + 40); | |
| 203 | // Each attempt had an even share: (300 − 150 ms of waits) / 5 ≈ 30 ms. | |
| 204 | expect(took).toBeGreaterThan(150); | |
| 205 | expect(s.began.length).toBe(5); | |
| 219 | expect(left.length).toBe(5); | |
| 220 | // Waits are 10, 20, 40, 80 ms; attempt n's share is (left − waits to come) / (5 − n). | |
| 221 | expect(left).toEqual([300, 290, 270, 230, 150]); | |
| 222 | const waitsAfter = [150, 140, 120, 80, 0]; | |
| 223 | const shares = left.map((l, n) => Math.floor((l - waitsAfter[n]!) / (5 - n))); | |
| 224 | expect(shares).toEqual([30, 37, 50, 75, 150]); | |
| 225 | // The last attempt's deadline is the one the error names. | |
| 226 | expect((error as Error).message).toContain("no answer in 150 ms"); | |
| 206 | 227 | }); |
| 207 | 228 | |
| 208 | 229 | test("with nothing left no attempt is made", async () => { |
new above
offline