diff --git a/src/runtime/server/RequestContext.rs b/src/runtime/server/RequestContext.rs index 1c2c1d969248..d92edf9c0a66 100644 --- a/src/runtime/server/RequestContext.rs +++ b/src/runtime/server/RequestContext.rs @@ -2379,7 +2379,7 @@ where if let Some(resp) = self.resp.take() { if self.flags.request_body_paused() { self.flags.set_request_body_paused(false); - resp.resume_(); + resp.resume(); } if self.flags.is_waiting_for_request_body() { self.flags.set_is_waiting_for_request_body(false); @@ -4123,7 +4123,7 @@ where return; } if let Some(resp) = self.resp { - resp.resume_(); + resp.resume(); } } @@ -4179,7 +4179,7 @@ where } } if let Some(resp) = (*this).resp { - resp.resume_(); + resp.resume(); } } } diff --git a/src/runtime/webcore/streams.rs b/src/runtime/webcore/streams.rs index a98f8cdeb8f9..4e8d0fd5bff7 100644 --- a/src/runtime/webcore/streams.rs +++ b/src/runtime/webcore/streams.rs @@ -1405,7 +1405,7 @@ impl HTTPServerWritable { if let Some(res) = self.any_res() { res.clear_on_writable(); // Release any request-body pause while `res` is live (see `end_already_responded_stream`). - res.resume_(); + res.resume(); } // `send_readable` drained the parked `try_end`, so uWS has // `markDone()`d the response and dropped its `onAborted`. @@ -1782,7 +1782,7 @@ impl HTTPServerWritable { if let Some(res) = self.any_res() { // Release any request-body pause while `res` is live (see `end_already_responded_stream`). - res.resume_(); + res.resume(); } // Both branches above fully ended the response through uWS, which // `markDone()`s it and drops its `onAborted`. @@ -1870,7 +1870,7 @@ impl HTTPServerWritable { if let Some(res) = self.any_res() { res.clear_on_writable(); // Release any request-body pause while `res` is live (see `end_already_responded_stream`). - res.resume_(); + res.resume(); } // `send_readable` drained the parked `try_end`/`end`, so uWS has // `markDone()`d the response and dropped its `onAborted`. diff --git a/test/js/bun/http/serve.test.ts b/test/js/bun/http/serve.test.ts index 1b6f58ce4fec..22b00d0dbb27 100644 --- a/test/js/bun/http/serve.test.ts +++ b/test/js/bun/http/serve.test.ts @@ -3497,6 +3497,54 @@ describe("request body backpressure", () => { } }); } + + it("releases a paused request body when the handler responds without reading it", async () => { + // The handler never touches req.body, so the pre-stream pause engages and is + // released by detach_response()'s resume once the response is sent. Without + // that resume the socket stays paused and the client never sees the response. + const TOTAL = 32 * 1024 * 1024; + const gate = Promise.withResolvers(); + const serverDone = Promise.withResolvers(); + + using server = serve({ + port: 0, + idleTimeout: 0, + maxRequestBodySize: TOTAL + 1, + error(e) { + serverDone.reject(e); + }, + async fetch() { + await gate.promise; + serverDone.resolve(); + return new Response("ignored"); + }, + }); + + const { sock, sentBeforeGate } = await pumpUploadUntilPlateau(server.port, TOTAL, 2); + try { + expect(sentBeforeGate).toBeGreaterThan(0); + expect(sentBeforeGate).toBeLessThan(TOTAL); + + const response = new Promise((resolve, reject) => { + let buf = ""; + sock.removeAllListeners("data"); + sock.on("data", d => { + buf += d.toString("latin1"); + if (buf.includes("\r\n\r\n")) resolve(buf); + }); + sock.once("error", reject); + sock.once("close", () => resolve(buf)); + }); + + gate.resolve(); + await serverDone.promise; + const resp = await response; + expect(resp).toStartWith("HTTP/1.1 200 "); + expect(resp).toContain("ignored"); + } finally { + sock.destroy(); + } + }); }); // https://github.com/oven-sh/bun/issues/32469