Public: anyone can read this; the crew can change it.
Sentinel: shared:01m23vygyffx5hg2f5vm8r74sr:2ml3f5dne9 is past its budget: trevato/forge: "concurrent checks > a fresh account's second check waits queued, naming the count; the owner's two run … #5196
Check · Review · Land · Live done — Landed and live in production.
scout/forge:scout/5195-sentinel-shared-01m23vygyffx5hg2f5vm8r74 → main 2 commits fork 1 thread · 1 unresolved
1 file
changed +15
−15
test/quotas-running.test.ts +15 −15
| @@ -4,7 +4,7 @@ | ||
| 4 | 4 | * does not start — a preview's run fails, a run or check stays queued — and |
| 5 | 5 | * its words name the limit and the count ("1 of 1 live previews"). The |
| 6 | 6 | * owner's own (admin) account starts the same things freely. Every wait is |
| 7 | * for a named state or a flag file, never a guess at timing. | |
| 7 | * for a named state or a flag file, never a guess at timing, and each wait outlasts a check machine running the whole suite at once. | |
This sentence makes the header run past the line width of the rest of the comment. Wrap it, or cut it to: "and each wait outlasts a loaded check machine." |
||
| 8 | 8 | */ |
| 9 | 9 | |
| 10 | 10 | import { describe, expect, test } from "bun:test"; |
| @@ -30,14 +30,14 @@ import { readRunLog } from "../src/runs/log.ts"; | ||
| 30 | 30 | import { QUOTA_KEYS, getQuota, runningLimitSentence, setQuota } from "../src/settings/quotas.ts"; |
| 31 | 31 | import { promote } from "./helpers/members.ts"; |
| 32 | 32 | |
| 33 | async function until<T>(read: () => T | undefined, ms = 60_000): Promise<T> { | |
| 33 | async function until<T>(read: () => T | undefined, ms = 120_000, what = "the awaited state"): Promise<T> { | |
| 34 | 34 | const deadline = Date.now() + ms; |
| 35 | 35 | while (Date.now() < deadline) { |
| 36 | 36 | const value = read(); |
| 37 | 37 | if (value !== undefined) return value; |
| 38 | 38 | await Bun.sleep(50); |
| 39 | 39 | } |
| 40 | throw new Error("did not happen in time"); | |
| 40 | throw new Error(`did not happen in time: ${what}`); | |
| 41 | 41 | } |
| 42 | 42 | |
| 43 | 43 | async function twoUsers(app: App): Promise<{ alice: User; bob: User }> { |
| @@ -142,7 +142,7 @@ describe("live previews", () => { | ||
| 142 | 142 | app.close(); |
| 143 | 143 | await rm(dataDir, { recursive: true, force: true }); |
| 144 | 144 | } |
| 145 | }, 180_000); | |
| 145 | }, 600_000); | |
| 146 | 146 | }); |
| 147 | 147 | |
| 148 | 148 | describe("two previews deploying together", () => { |
| @@ -201,7 +201,7 @@ describe("two previews deploying together", () => { | ||
| 201 | 201 | app.close(); |
| 202 | 202 | await rm(dataDir, { recursive: true, force: true }); |
| 203 | 203 | } |
| 204 | }, 180_000); | |
| 204 | }, 600_000); | |
| 205 | 205 | }); |
| 206 | 206 | |
| 207 | 207 | describe("a sleeping preview woken at the limit", () => { |
| @@ -264,7 +264,7 @@ describe("a sleeping preview woken at the limit", () => { | ||
| 264 | 264 | app.close(); |
| 265 | 265 | await rm(dataDir, { recursive: true, force: true }); |
| 266 | 266 | } |
| 267 | }, 180_000); | |
| 267 | }, 600_000); | |
| 268 | 268 | }); |
| 269 | 269 | |
| 270 | 270 | describe("concurrent checks", () => { |
| @@ -318,7 +318,7 @@ describe("concurrent checks", () => { | ||
| 318 | 318 | app.close(); |
| 319 | 319 | await rm(dataDir, { recursive: true, force: true }); |
| 320 | 320 | } |
| 321 | }, 180_000); | |
| 321 | }, 600_000); | |
| 322 | 322 | |
| 323 | 323 | test("a fresh account's second check waits queued, naming the count; the owner's two run at once; a freed slot starts the waiter", async () => { |
| 324 | 324 | const dataDir = await mkdtemp(join(tmpdir(), "forge-qrun-checks-")); |
| @@ -351,27 +351,27 @@ describe("concurrent checks", () => { | ||
| 351 | 351 | const stateOf = (repo: Repo) => checkOf(repo)?.state; |
| 352 | 352 | |
| 353 | 353 | const b1 = await push(bob, "b1"); |
| 354 | await until(() => (stateOf(b1) === "running" ? true : undefined)); | |
| 354 | await until(() => (stateOf(b1) === "running" ? true : undefined), 120_000, "b1 running"); | |
| 355 | 355 | const b2 = await push(bob, "b2"); |
| 356 | 356 | const waiting = await until(() => { |
| 357 | 357 | const run = checkOf(b2); |
| 358 | 358 | return run !== undefined && readRunLog(dataDir, run.id).lines.join("\n").includes("1 of 1 concurrent checks") |
| 359 | 359 | ? run |
| 360 | 360 | : undefined; |
| 361 | }); | |
| 361 | }, 120_000, "b2 queued with its count in the log"); | |
| 362 | 362 | expect(stateOf(b2)).toBe("queued"); |
| 363 | 363 | expect(waiting.state).toBe("queued"); |
| 364 | 364 | |
| 365 | 365 | const a1 = await push(alice, "a1"); |
| 366 | 366 | const a2 = await push(alice, "a2"); |
| 367 | await until(() => (stateOf(a1) === "running" && stateOf(a2) === "running" ? true : undefined)); | |
| 367 | await until(() => (stateOf(a1) === "running" && stateOf(a2) === "running" ? true : undefined), 120_000, "a1 and a2 running"); | |
| 368 | 368 | |
| 369 | 369 | // The slot frees: bob's waiter is started, never dropped. |
| 370 | 370 | writeFileSync(flag("b1"), ""); |
| 371 | await until(() => (stateOf(b1) === "ok" ? true : undefined)); | |
| 372 | await until(() => (stateOf(b2) === "running" ? true : undefined)); | |
| 371 | await until(() => (stateOf(b1) === "ok" ? true : undefined), 120_000, "b1 ok"); | |
| 372 | await until(() => (stateOf(b2) === "running" ? true : undefined), 120_000, "b2 running once b1 freed its slot"); | |
| 373 | 373 | for (const name of ["b2", "a1", "a2"]) writeFileSync(flag(name), ""); |
| 374 | await until(() => ([b2, a1, a2].every((r) => stateOf(r) === "ok") ? true : undefined)); | |
| 374 | await until(() => ([b2, a1, a2].every((r) => stateOf(r) === "ok") ? true : undefined), 120_000, "b2, a1, a2 ok"); | |
| 375 | 375 | expect(existsSync(flag("b2"))).toBe(true); |
| 376 | 376 | } finally { |
| 377 | 377 | await checks.stop(); |
| @@ -379,7 +379,7 @@ describe("concurrent checks", () => { | ||
| 379 | 379 | app.close(); |
| 380 | 380 | await rm(dataDir, { recursive: true, force: true }); |
| 381 | 381 | } |
| 382 | }, 180_000); | |
| 382 | }, 600_000); | |
| 383 | 383 | }); |
| 384 | 384 | |
| 385 | 385 | describe("concurrent runs", () => { |
| @@ -453,5 +453,5 @@ echo '{"type":"result","subtype":"success","is_error":false,"result":"ok","total | ||
| 453 | 453 | app.close(); |
| 454 | 454 | await rm(dataDir, { recursive: true, force: true }); |
| 455 | 455 | } |
| 456 | }, 180_000); | |
| 456 | }, 600_000); | |
| 457 | 457 | }); |
new above
offline
Wrap this: the sentence runs far past the file's comment width. For example end the line after "timing," and continue on a new
* and each wait outlasts a check machine running the whole suite at once.line.