diff --git a/scripts/measure-rpc-rtt.mjs b/scripts/measure-rpc-rtt.mjs index 47480e33dc4e..60cd27bd7cd4 100644 --- a/scripts/measure-rpc-rtt.mjs +++ b/scripts/measure-rpc-rtt.mjs @@ -130,6 +130,7 @@ function formatErrorMessage(error) { async function readyzReportsReady(response, options = {}) { if (!response.ok) { + void response.body?.cancel().catch(() => undefined); return false; } try { @@ -235,6 +236,7 @@ export async function waitForGatewayReady({ `http://127.0.0.1:${port}/healthz`, probeTimeoutMs, ); + void probe.response.body?.cancel().catch(() => undefined); probe.clearTimeout(); } catch { // Liveness is diagnostic only; /readyz is the usable RPC readiness contract. diff --git a/test/scripts/measure-rpc-rtt.test.ts b/test/scripts/measure-rpc-rtt.test.ts index 6f13f4930fec..167004e12c61 100644 --- a/test/scripts/measure-rpc-rtt.test.ts +++ b/test/scripts/measure-rpc-rtt.test.ts @@ -580,4 +580,45 @@ describe("scripts/measure-rpc-rtt.mjs", () => { }), ); }); + + it("cancels unconsumed readiness probe response bodies", async () => { + const child = new EventEmitter(); + let readyzCanceled = false; + let healthzCanceled = false; + const fetchImpl = vi + .fn() + .mockResolvedValueOnce({ + body: { + async cancel() { + readyzCanceled = true; + }, + }, + ok: false, + status: 503, + }) + .mockResolvedValueOnce({ + body: { + async cancel() { + healthzCanceled = true; + }, + }, + ok: true, + status: 200, + }) + .mockResolvedValueOnce(jsonResponse({ failing: [], ready: true })); + + await waitForGatewayReady({ + child, + fetchImpl, + port: 12345, + probeTimeoutMs: 7, + readyTimeoutMs: 50, + sleepMs: 1, + stderrPath: "/no/such/stderr.log", + }); + + expect(fetchImpl).toHaveBeenCalledTimes(3); + expect(readyzCanceled).toBe(true); + expect(healthzCanceled).toBe(true); + }); });