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

Sentinel: release was degraded 611 minutes today: Release 0.7.374 is unproven: https://forge.open-platform.sh/explore as desktop /explore answered with an empty body (st… #9365

Merged opened this

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

Merged

mason/forge:mason/9358-sentinel-release-was-degraded-611-minute → main 1 commit fork

  • Merged. mason/9358-sentinel-release-was-degraded-611-minute was merged into main as 2b26b97.

Review

Sign in to review this change.
3 files changed +24 −3
src/release/prove.ts +2 −2
@@ -41,7 +41,7 @@ export interface Proof {
4141 readonly since?: string;
4242 /** How many proofs of this version have failed in a row; 1 when unset. */
4343 readonly failures?: number;
44 /** The first failing check was only a slow answer (`Failure.slow`); retried sooner. */
44 /** The first failing check was only a slow or empty answer (`Failure.slow`, `Failure.empty`); retried sooner (#9358). */
4545 readonly slow?: boolean;
4646 }
4747
@@ -160,7 +160,7 @@ export async function proveRelease(o: ProveOptions): Promise<Proof | undefined>
160160 anonymousBoard: true,
161161 heavy: { version: o.version, probe: ownProbeHeaders(o.dataDir), ...o.heavy }, ...(o.appHost ? { appHost: o.appHost } : {}),
162162 });
163 return finish(failures[0] === undefined ? undefined : checkLine(failures[0]), failures[0]?.slow === true);
163 return finish(failures[0] === undefined ? undefined : checkLine(failures[0]), failures[0]?.slow === true || failures[0]?.empty === true);
164164 } catch (error) {
165165 return finish(`the smoke checks could not run: ${String(error)}`);
166166 }
src/release/smoke.ts +6 −1
@@ -83,6 +83,8 @@ export interface Failure {
8383 why: string;
8484 /** The check failed only because the answer was slower than allowed. */
8585 slow?: boolean;
86 /** The check failed only because a 200 answered with no body: a lost answer, retried like a slow one (#9358). */
87 empty?: boolean;
8688 }
8789
8890 /** The first stylesheet href in a page, or null. */
@@ -194,7 +196,10 @@ async function watch(root: URL, token: string, heavy: Heavy, fetch: typeof globa
194196 return gone(`/healthz reported X-Forge-Version ${res.headers.get("x-forge-version") ?? "none"}, wanted ${heavy.version}`, res.status);
195197 if (path === "/boards" && anonymousBoard && !token && isSignInRedirect(url, res)) return { failure: null, slow: false };
196198 if (res.status !== 200) return gone(`${path} expected 200`, res.status);
197 if (!body.trim()) return { ...gone(`${path} answered with an empty body${emptyEvidence(res)}`, res.status), empty: true };
199 if (!body.trim()) {
200 const lost = gone(`${path} answered with an empty body${emptyEvidence(res)}`, res.status);
201 return { failure: { ...lost.failure, empty: true }, slow: false, empty: true };
202 }
198203 return { failure: null, slow: false };
199204 } catch (error) {
200205 return { ...gone(`${path} did not answer within ${slowMs * 2} ms (${error})`), hung: true };
test/release-prove.test.ts +16
@@ -263,3 +263,19 @@ describe("a slow-only proof is asked again sooner (#9238)", () => {
263263 }
264264 });
265265 });
266
267 describe("an empty-body proof is asked again sooner (#9358)", () => {
268 test("a version whose /explore answers empty twice is due again after a minute, not five", async () => {
269 const dir = mkdtempSync(join(tmpdir(), "forge-prove-"));
270 try {
271 const { fake } = site("5.0.0", { "/explore": () => new Response("", { status: 200 }) });
272 const p = await proveRelease({ dataDir: dir, version: "5.0.0", url: "https://forge.test", fetch: fake, sleep: async () => {}, heavy: { watchMs: 30, pollMs: 5 } });
273 expect(p?.ok).toBe(false);
274 expect(p?.failure).toContain("empty body");
275 expect(p?.slow).toBe(true);
276 expect(proofDue(p, "5.0.0", Date.parse(p!.at) + 61_000)).toBe(true);
277 } finally {
278 rmSync(dir, { recursive: true, force: true });
279 }
280 });
281 });

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs