Public: anyone can read this; the crew can change it.
Test fails only beside others: test/traffic-truth.test.ts #11970
Check · Review · Land · Live done — Landed and live in production.
mason/forge:mason/11968-test-fails-only-beside-others-test-traff → main 5 commits fork
3 files
changed +36
−6
src/http/crawl.ts +14 −4
| @@ -134,7 +134,7 @@ interface State { | ||
| 134 | 134 | readonly strangersInFlight: Map<Request, number>; |
| 135 | 135 | readonly refused: Map<string, number>; |
| 136 | 136 | readonly refusedBy: Record<Scope, number>; |
| 137 | readonly clock: { now: () => number; behind: boolean }; | |
| 137 | readonly clock: { now: () => number; behind: boolean; pinned: boolean | undefined }; | |
| 138 | 138 | } |
| 139 | 139 | |
| 140 | 140 | const states = new WeakMap<App, State>(); |
| @@ -158,7 +158,7 @@ const limits = (perMinute: number) => ({ perMinute, adminPerMinute: perMinute, e | ||
| 158 | 158 | function stateOf(app: App): State { |
| 159 | 159 | let state = states.get(app); |
| 160 | 160 | if (state === undefined) { |
| 161 | const clock = { now: Date.now, behind: false }; | |
| 161 | const clock = { now: Date.now, behind: false, pinned: undefined as boolean | undefined }; | |
| 162 | 162 | const pair = (b: { page: number; deep: number }): Pair => ({ |
| 163 | 163 | page: new RateLimiter(limits(b.page), () => clock.now()), |
| 164 | 164 | deep: new RateLimiter(limits(b.deep), () => clock.now()), |
| @@ -186,6 +186,16 @@ export function setCrawlClock(app: App, now: () => number, behind = false): void | ||
| 186 | 186 | clock.behind = behind; |
| 187 | 187 | } |
| 188 | 188 | |
| 189 | /** | |
| 190 | * Test seam: say whether this app's budgets are divided for lag, whatever the | |
| 191 | * process measures. The lag reading is process-wide, so a file that tests | |
| 192 | * something else (not load shedding) pins it off and is not at the mercy of | |
| 193 | * whichever file ran before it in the shard; `undefined` measures again. | |
| 194 | */ | |
| 195 | export function pinCrawlLag(app: App, behind: boolean | undefined): void { | |
| 196 | stateOf(app).clock.pinned = behind; | |
| 197 | } | |
| 198 | ||
| 189 | 199 | export interface CrawlVerdict { |
| 190 | 200 | readonly retryAfter: number; |
| 191 | 201 | readonly agent: string; |
| @@ -202,7 +212,7 @@ export function takeCrawl(app: App, request: Request, pathname: string): CrawlVe | ||
| 202 | 212 | if (FREE.test(pathname)) return undefined; |
| 203 | 213 | watchLag(); |
| 204 | 214 | const state = stateOf(app); |
| 205 | const behind = state.clock.behind || mainThreadBehind(); | |
| 215 | const behind = state.clock.pinned ?? (state.clock.behind || mainThreadBehind()); | |
| 206 | 216 | const named = agentOf(request.headers.get("user-agent") ?? ""); |
| 207 | 217 | const scale = behind ? BEHIND_DIVISOR : 1; |
| 208 | 218 | const deep = DEEP.test(pathname); |
| @@ -248,7 +258,7 @@ function takeStrangerDeep(app: App, request: Request, pathname: string): CrawlVe | ||
| 248 | 258 | if (new URL(request.url).searchParams.get("fragment") === "explorer") return undefined; |
| 249 | 259 | watchLag(); |
| 250 | 260 | const state = stateOf(app); |
| 251 | const behind = state.clock.behind || mainThreadBehind() || strangersQueued(state); | |
| 261 | const behind = state.clock.pinned ?? (state.clock.behind || mainThreadBehind() || strangersQueued(state)); | |
| 252 | 262 | const scale = behind ? BEHIND_DIVISOR : 1; |
| 253 | 263 | state.strangerAddress.setLimits(limits(Math.max(1, Math.floor(STRANGER_BUDGET.address / scale)))); |
| 254 | 264 | const key = bucketOf(clientAddress(request)); |
test/crawl-budget.test.ts +17 −1
| @@ -8,7 +8,7 @@ import { commitToBranch, createRepo } from "../src/actions/repos.ts"; | ||
| 8 | 8 | import { getRepo } from "../src/core/repos.ts"; |
| 9 | 9 | import { createSession } from "../src/core/sessions.ts"; |
| 10 | 10 | import { createUser, type User } from "../src/core/users.ts"; |
| 11 | import { AGENT_BUDGET, CRAWL_BUDGET, DEEP_SEGMENTS, SHARED_BUDGET, agentOf, crawlAgentCount, isCrawler, setCrawlClock, takeCrawl } from "../src/http/crawl.ts"; | |
| 11 | import { AGENT_BUDGET, BEHIND_DIVISOR, STRANGER_BUDGET, CRAWL_BUDGET, DEEP_SEGMENTS, SHARED_BUDGET, agentOf, crawlAgentCount, isCrawler, pinCrawlLag, setCrawlClock, takeCrawl } from "../src/http/crawl.ts"; | |
| 12 | 12 | import { serve, type Servers } from "../src/http/server.ts"; |
| 13 | 13 | import { crawlerShare, createPerfRecord } from "../src/perf/record.ts"; |
| 14 | 14 | |
| @@ -176,3 +176,19 @@ test("the crawler board door is an admin's: a non-admin and an anonymous caller | ||
| 176 | 176 | expect(body.refused_by_budget.agent).toBeGreaterThan(0); |
| 177 | 177 | expect(body.refused_by_budget.shared).toBeGreaterThan(0); |
| 178 | 178 | }); |
| 179 | ||
| 180 | test("a pinned lag reading, not the process's, decides whether a stranger's budget is divided", () => { | |
| 181 | const request = () => new Request("http://x.test/alice/open/blob/main/a.txt", { headers: { "user-agent": "Mozilla/5.0 Chrome/130.0 Safari/537.36" } }); | |
| 182 | const allowed = (): number => { | |
| 183 | let n = 0; | |
| 184 | while (takeCrawl(app, request(), "/alice/open/blob/main/a.txt") === undefined) n++; | |
| 185 | return n; | |
| 186 | }; | |
| 187 | setCrawlClock(app, () => 9_000_000); | |
| 188 | pinCrawlLag(app, true); | |
| 189 | expect(allowed()).toBe(Math.floor(STRANGER_BUDGET.address / BEHIND_DIVISOR)); | |
| 190 | setCrawlClock(app, () => 9_100_000); | |
| 191 | pinCrawlLag(app, false); | |
| 192 | expect(allowed()).toBe(STRANGER_BUDGET.address); | |
| 193 | pinCrawlLag(app, undefined); | |
| 194 | }); |
test/traffic-truth.test.ts +5 −1
| @@ -8,7 +8,7 @@ import { createApp, type App } from "../src/app.ts"; | ||
| 8 | 8 | import { getRepo } from "../src/core/repos.ts"; |
| 9 | 9 | import { createSession } from "../src/core/sessions.ts"; |
| 10 | 10 | import { createUser, type User } from "../src/core/users.ts"; |
| 11 | import { familyOf } from "../src/http/crawl.ts"; | |
| 11 | import { familyOf, pinCrawlLag } from "../src/http/crawl.ts"; | |
| 12 | 12 | import { serve, type Servers } from "../src/http/server.ts"; |
| 13 | 13 | import { |
| 14 | 14 | trafficAttention, |
| @@ -62,6 +62,10 @@ beforeAll(async () => { | ||
| 62 | 62 | dataDir = await mkdtemp(join(tmpdir(), "forge-traffic-")); |
| 63 | 63 | app = createApp({ dataDir, port: 0 }); |
| 64 | 64 | servers = await serve(app); |
| 65 | // These tests are about what is cached and counted, not load shedding: | |
| 66 | // a stranger's budget is not divided because an earlier file in the | |
| 67 | // shard left the loop late. | |
| 68 | pinCrawlLag(app, false); | |
| 65 | 69 | const made = await createUser(app.db, { origin: "seed", trusted: true, username: "alice", email: "[email protected]", password: PASSWORD, isAdmin: true }); |
| 66 | 70 | if (!made.ok) throw new Error(made.error); |
| 67 | 71 | alice = made.value; |
new above
offline