Public: anyone can read this; the crew can change it.

Why did /explore answer 200 with an empty body once during release 0.7.374 proof? #9353

Merged opened this

Check · Review · Land · Live done — Landed and live in production.

Merged

mason/forge:mason/9343-why-did-explore-answer-200-with-an-empty → main 3 commits fork 1 thread · 1 unresolved

  • Merged. mason/9343-why-did-explore-answer-200-with-an-empty was merged into main as 06b09c4.

Review

Sign in to review this change.
2 files changed +31 −2
src/release/smoke.ts +15 −1
@@ -49,6 +49,20 @@ export function forgeMs(res: Response): number | undefined {
4949 return app && Number.isFinite(app.ms) ? app.ms : undefined;
5050 }
5151
52 /**
53 * What an empty 200 says about which layer lost the body (#9343): the forge
54 * writes `Server-Timing` and `X-Forge-Version` and never an empty page, so
55 * their absence points at the edge or the wire; a declared length with no
56 * bytes points at a body cut short.
57 */
58 export function emptyEvidence(res: Response): string {
59 const seen = ["content-length", "content-encoding", "cf-cache-status", "age", "server-timing", "x-forge-version"]
60 .map((name) => [name, res.headers.get(name)] as const)
61 .filter((pair): pair is readonly [string, string] => pair[1] !== null)
62 .map(([name, value]) => `${name}: ${value.slice(0, 80)}`);
63 return seen.length === 0 ? " (no forge or edge headers)" : ` (${seen.join("; ")})`;

The empty-evidence branch (no headers seen) is untested; delete it and every test still passes. Add a test for it next to the cf-cache-status one.

64 }
65
5266 export interface Heavy {
5367 watchMs?: number;
5468 pollMs?: number;
@@ -180,7 +194,7 @@ async function watch(root: URL, token: string, heavy: Heavy, fetch: typeof globa
180194 return gone(`/healthz reported X-Forge-Version ${res.headers.get("x-forge-version") ?? "none"}, wanted ${heavy.version}`, res.status);
181195 if (path === "/boards" && anonymousBoard && !token && isSignInRedirect(url, res)) return { failure: null, slow: false };
182196 if (res.status !== 200) return gone(`${path} expected 200`, res.status);
183 if (!body.trim()) return { ...gone(`${path} answered with an empty body`, res.status), empty: true };
197 if (!body.trim()) return { ...gone(`${path} answered with an empty body${emptyEvidence(res)}`, res.status), empty: true };
184198 return { failure: null, slow: false };
185199 } catch (error) {
186200 return { ...gone(`${path} did not answer within ${slowMs * 2} ms (${error})`), hung: true };
test/release-smoke.test.ts +16 −1
@@ -1,5 +1,5 @@
11 import { afterEach, expect, test } from "bun:test";
2 import { HEAVY_PAGES, forgeMs, smoke } from "../src/release/smoke.ts";
2 import { HEAVY_PAGES, emptyEvidence, forgeMs, smoke } from "../src/release/smoke.ts";
33
44 let server: ReturnType<typeof Bun.serve> | undefined;
55 afterEach(() => server?.stop(true));
@@ -153,6 +153,15 @@ test("a /healthz that gets no answer twice in a row in the watch fails (#7724)",
153153 expect(failures.some((f) => f.url.endsWith("/healthz") && f.why.includes("did not answer"))).toBe(true);
154154 });
155155
156 test("an empty 200 names the headers that say which layer lost the body (#9343)", async () => {
157 const empty = (p: string) => (p === "/explore" ? new Response("", { headers: { "cf-cache-status": "HIT", "server-timing": "app;dur=3" } }) : undefined);
158 const failures = await smoke(forge(0, [], empty), "t", { heavy });
159 const f = failures.find((x) => x.url.endsWith("/explore"));
160 expect(f?.why).toContain("answered with an empty body");
161 expect(f?.why).toContain("cf-cache-status: HIT");
162 expect(f?.why).toContain("server-timing: app;dur=3");
163 });
164
156165 test("a heavy page that answers an empty body once is asked again and passes (#9334)", async () => {
157166 let n = 0;
158167 const flaky = (p: string) => (p === "/explore" && ++n === 1 ? new Response("", { status: 200 }) : undefined);
@@ -180,3 +189,9 @@ test("a polled heavy page that answers an empty body twice in a row in the watch
180189 const failures = await smoke(forge(0, [], flaky), "t", { heavy });
181190 expect(failures.some((f) => f.url.endsWith("/explore") && f.why.includes("empty body"))).toBe(true);
182191 });
192
193 test("an empty 200 with no forge or edge headers says so, pointing past the forge (#9343)", () => {
194 // Called directly: a real server adds a content-length of its own to any body.
195 expect(emptyEvidence(new Response(""))).toBe(" (no forge or edge headers)");
196 expect(emptyEvidence(new Response("", { headers: { "x-forge-version": "v1" } }))).toBe(" (x-forge-version: v1)");
197 });

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs