Public: anyone can read this; the crew can change it.
"the merge queue > a gate that never answers is abandoned at the deadline, and the next sweep lands (#3430)" passes every time on a loaded check machine #5627
Check · Review · Land · Live done — Landed and live in production.
scout/forge:scout/5611-the-merge-queue-a-gate-that-never-answer → main 1 commit fork
2 files
changed +24
−5
src/checks/queue.ts +13 −3
| @@ -119,6 +119,10 @@ export const GATE_DEADLINE_MS = 15 * 60_000; | ||
| 119 | 119 | |
| 120 | 120 | let deadlineMs = GATE_DEADLINE_MS; |
| 121 | 121 | |
| 122 | /** What arms the deadline; tests swap in a clock they fire themselves. */ | |
| 123 | type Schedule = (fire: () => void, ms: number) => ReturnType<typeof setTimeout>; | |
| 124 | let schedule: Schedule = setTimeout; | |
| 125 | ||
| 122 | 126 | /** |
| 123 | 127 | * The most pulls one candidate carries (#3430): eight keep a gate near one |
| 124 | 128 | * full tier and a red's bisection three gates deep. The rest of the line |
| @@ -140,11 +144,17 @@ export interface QueueOptions { | ||
| 140 | 144 | readonly room?: () => number; |
| 141 | 145 | } |
| 142 | 146 | |
| 143 | /** Tests shorten the deadline; the answer puts it back. */ | |
| 144 | export function gateDeadlineForTest(ms: number): () => void { | |
| 147 | /** | |
| 148 | * Tests shorten the deadline, or hand `arm` the timer to fire themselves (a | |
| 149 | * wall-clock deadline races the gate's setup on a loaded machine); the answer | |
| 150 | * puts both back. | |
| 151 | */ | |
| 152 | export function gateDeadlineForTest(ms: number, arm?: Schedule): () => void { | |
| 145 | 153 | deadlineMs = ms; |
| 154 | if (arm !== undefined) schedule = arm; | |
| 146 | 155 | return () => { |
| 147 | 156 | deadlineMs = GATE_DEADLINE_MS; |
| 157 | schedule = setTimeout; | |
| 148 | 158 | }; |
| 149 | 159 | } |
| 150 | 160 | |
| @@ -424,7 +434,7 @@ function counted(line: Line, gate: GateCandidate): Gated { | ||
| 424 | 434 | // A gate that never answers is abandoned at the deadline: no verdict, |
| 425 | 435 | // and the next sweep forms the line again (#3430). |
| 426 | 436 | const cut = new Promise<LandVerdict>((resolve) => { |
| 427 | timer = setTimeout(() => { | |
| 437 | timer = schedule(() => { | |
| 428 | 438 | log.warn("candidate abandoned", { |
| 429 | 439 | repo: line.repoId, |
| 430 | 440 | pulls, |
test/merge-queue.test.ts +11 −2
| @@ -468,7 +468,13 @@ describe("the merge queue", () => { | ||
| 468 | 468 | const repo = await autonomousRepo("never-answers"); |
| 469 | 469 | const pull = await greenPull(repo, "a.ts"); |
| 470 | 470 | expect(queueWords(repo.id)).toBe("idle"); |
| 471 | const restore = gateDeadlineForTest(1500); | |
| 471 | // The deadline is fired by hand once the gate is reached: a wall clock | |
| 472 | // would race the gate's setup on a loaded machine. | |
| 473 | let fire: (() => void) | undefined; | |
| 474 | const restore = gateDeadlineForTest(1500, (f) => { | |
| 475 | fire = f; | |
| 476 | return setTimeout(() => {}, 0); | |
| 477 | }); | |
| 472 | 478 | const warned: string[] = []; |
| 473 | 479 | const warn = console.warn; |
| 474 | 480 | console.warn = (line: string) => warned.push(line); |
| @@ -476,7 +482,10 @@ describe("the merge queue", () => { | ||
| 476 | 482 | let words = ""; |
| 477 | 483 | try { |
| 478 | 484 | lastSignal = undefined; |
| 479 | atGate = () => (words = queueWords(repo.id)); | |
| 485 | atGate = () => { | |
| 486 | words = queueWords(repo.id); | |
| 487 | fire?.(); | |
| 488 | }; | |
| 480 | 489 | const sweep = sweepGreen(app, repo); |
| 481 | 490 | expect(await sweep).toBe(0); |
| 482 | 491 | } finally { |
new above
offline