Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(server) handle ignored body and node:http destroy #10268

Merged
merged 34 commits into from
May 1, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
8a6c07d
if Locked and never consumed we still need to handle it
cirospaciari Apr 14, 2024
db6b2a8
memory leak test
cirospaciari Apr 15, 2024
862ab39
body leak tests
cirospaciari Apr 15, 2024
17f3ef7
ops
cirospaciari Apr 15, 2024
1ec7090
rename lol
cirospaciari Apr 15, 2024
e0992c0
process for ignored streams
cirospaciari Apr 15, 2024
9402c19
stream not consumed case
cirospaciari Apr 15, 2024
f2a356c
undo
cirospaciari Apr 15, 2024
8437630
call cancel instead of done
cirospaciari Apr 15, 2024
0425551
fix peak memory usage
cirospaciari Apr 15, 2024
bc5ee9b
tweeaks
cirospaciari Apr 15, 2024
d73ae5f
add a test for this fix too
cirospaciari Apr 15, 2024
a237677
cleanup
cirospaciari Apr 15, 2024
1f5342b
echo test
cirospaciari Apr 15, 2024
ee589a7
expected in ignore
cirospaciari Apr 15, 2024
15957ef
simplify
cirospaciari Apr 15, 2024
c484e80
cleanup
cirospaciari Apr 15, 2024
6bf9383
make cancel actually cancel
cirospaciari Apr 15, 2024
a3bff6a
more time
cirospaciari Apr 15, 2024
fc2e017
oopsie
cirospaciari Apr 15, 2024
8efc161
skip + time
cirospaciari Apr 15, 2024
f479fc4
done actually mark source as done now
cirospaciari Apr 15, 2024
0d689a2
reduce test load
cirospaciari Apr 30, 2024
8320ebc
still slow
cirospaciari Apr 30, 2024
1e9947f
less flaky
cirospaciari Apr 30, 2024
111c476
tweaks
cirospaciari May 1, 2024
634c197
WIP end current request properly
cirospaciari May 1, 2024
a7a735f
oops
cirospaciari May 1, 2024
3c425fa
add comment
cirospaciari May 1, 2024
35ae823
revert
cirospaciari May 1, 2024
552f9b1
fix http destroy
cirospaciari May 1, 2024
58ae8db
fix abort call
cirospaciari May 1, 2024
87d4aad
opsie make this test actual be a test
cirospaciari May 1, 2024
64ba7f3
change test hostname
cirospaciari May 1, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion src/bun.js/api/server.zig
Original file line number Diff line number Diff line change
Expand Up @@ -1846,10 +1846,15 @@ fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, comp

if (this.request_body) |body| {
ctxLog("finalizeWithoutDeinit: request_body != null", .{});
// Case 1:
// User called .blob(), .json(), text(), or .arrayBuffer() on the Request object
// but we received nothing or the connection was aborted
// the promise is pending
if (body.value == .Locked and body.value.Locked.hasPendingPromise()) {
// Case 2:
// User ignored the body and the connection was aborted or ended
// Case 3:
// Stream was not consumed and the connection was aborted or ended
if (body.value == .Locked) {
body.value.toErrorInstance(JSC.toTypeError(.ABORT_ERR, "Request aborted", .{}, this.server.globalThis), this.server.globalThis);
}
}
Expand Down
3 changes: 2 additions & 1 deletion src/bun.js/webcore/body.zig
Original file line number Diff line number Diff line change
Expand Up @@ -828,6 +828,7 @@ pub const Body = struct {
}

pub fn toErrorInstance(this: *Value, error_instance: JSC.JSValue, global: *JSGlobalObject) void {
error_instance.ensureStillAlive();
if (this.* == .Locked) {
var locked = this.Locked;
locked.deinit = true;
Expand All @@ -842,7 +843,7 @@ pub const Body = struct {
}

if (locked.readable.get()) |readable| {
readable.done(global);
readable.abort(global);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think just calling abort should be enough

Copy link
Member Author

@cirospaciari cirospaciari Apr 15, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

abort/cancel actually was only canceling the stream not the source, so I fixed that now.
onCancel actually only mark as done, not actually emits a error so I changed the test that I add to match this expectations.

locked.readable.deinit();
}
// will be unprotected by body value deinit
Expand Down
26 changes: 21 additions & 5 deletions src/bun.js/webcore/streams.zig
Original file line number Diff line number Diff line change
Expand Up @@ -142,21 +142,37 @@ pub const ReadableStream = struct {
}

pub fn done(this: *const ReadableStream, globalThis: *JSGlobalObject) void {
JSC.markBinding(@src());
// done is called when we are done consuming the stream
// cancel actually mark the stream source as done
// this will resolve any pending promises to done: true
switch (this.ptr) {
.Blob => |source| {
source.parent().cancel();
},
.File => |source| {
source.parent().cancel();
},
.Bytes => |source| {
source.parent().cancel();
},
else => {},
}
this.detachIfPossible(globalThis);
}

pub fn cancel(this: *const ReadableStream, globalThis: *JSGlobalObject) void {
JSC.markBinding(@src());

// cancel the stream
ReadableStream__cancel(this.value, globalThis);
this.detachIfPossible(globalThis);
// mark the stream source as done
this.done(globalThis);
}

pub fn abort(this: *const ReadableStream, globalThis: *JSGlobalObject) void {
JSC.markBinding(@src());

ReadableStream__cancel(this.value, globalThis);
this.detachIfPossible(globalThis);
// for now we are just calling cancel should be fine
this.cancel(globalThis);
}

pub fn forceDetach(this: *const ReadableStream, globalObject: *JSGlobalObject) void {
Expand Down
12 changes: 10 additions & 2 deletions src/js/node/http.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1089,7 +1089,6 @@ Object.defineProperty(OutgoingMessage.prototype, "finished", {

function emitCloseNT(self) {
if (!self._closed) {
self.destroyed = true;
self._closed = true;
self.emit("close");
}
Expand Down Expand Up @@ -1432,6 +1431,15 @@ class ClientRequest extends OutgoingMessage {
this.#bodyChunks.push(...chunks);
callback();
}
_destroy(err, callback) {
this.destroyed = true;
// If request is destroyed we abort the current response
this[kAbortController]?.abort?.();
if (err) {
this.emit("error", err);
}
callback();
}

_final(callback) {
this.#finished = true;
Expand Down Expand Up @@ -1519,7 +1527,7 @@ class ClientRequest extends OutgoingMessage {

abort() {
if (this.aborted) return;
this[kAbortController]!.abort();
this[kAbortController]?.abort?.();
// TODO: Close stream if body streaming
}

Expand Down
38 changes: 38 additions & 0 deletions test/js/bun/http/body-leak-test-fixture.ts

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

134 changes: 134 additions & 0 deletions test/js/bun/http/serve-body-leak.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
import { join } from "path";
import { it, expect, beforeAll, afterAll } from "bun:test";
import { bunExe, bunEnv } from "harness";
import type { Subprocess } from "bun";

const ACCEPTABLE_MEMORY_LEAK = 2; //MB for acceptable memory leak variance
const payload = "1".repeat(32 * 1024); // decent size payload to test memory leak

let url: URL;
let process: Subprocess<"ignore", "pipe", "inherit"> | null = null;
beforeAll(async () => {
process = Bun.spawn([bunExe(), "--smol", join(import.meta.dirname, "body-leak-test-fixture.ts")], {
env: bunEnv,
stdout: "pipe",
stderr: "inherit",
stdin: "ignore",
});
const { value } = await process.stdout.getReader().read();
url = new URL(new TextDecoder().decode(value));

await warmup();
});
afterAll(() => {
process?.kill();
});

async function getMemoryUsage(): Promise<number> {
return (await fetch(`${url.origin}/report`).then(res => res.json())) as number;
}

async function warmup() {
const batch = new Array(100);
for (let i = 0; i < 100; i++) {
for (let j = 0; j < 100; j++) {
// warmup the server with streaming requests, because is the most memory intensive
batch[j] = fetch(`${url.origin}/streaming`, {
method: "POST",
body: payload,
});
}
await Promise.all(batch);
}
// clean up memory before first test
await getMemoryUsage();
}

async function callBuffering() {
const result = await fetch(`${url.origin}/buffering`, {
method: "POST",
body: payload,
}).then(res => res.text());
expect(result).toBe("Ok");
}
async function callStreaming() {
const result = await fetch(`${url.origin}/streaming`, {
method: "POST",
body: payload,
}).then(res => res.text());
expect(result).toBe("Ok");
}
async function callIncompleteStreaming() {
const result = await fetch(`${url.origin}/incomplete-streaming`, {
method: "POST",
body: payload,
}).then(res => res.text());
expect(result).toBe("Ok");
}
async function callStreamingEcho() {
const result = await fetch(`${url.origin}/streaming-echo`, {
method: "POST",
body: payload,
}).then(res => res.text());
expect(result).toBe(payload);
}
async function callIgnore() {
const result = await fetch(url, {
method: "POST",
body: payload,
}).then(res => res.text());
expect(result).toBe("Ok");
}

async function calculateMemoryLeak(fn: () => Promise<void>) {
const start_memory = await getMemoryUsage();
const memory_examples: Array<number> = [];
let peak_memory = start_memory;
const batch = new Array(100);
for (let i = 0; i < 100; i++) {
for (let j = 0; j < 100; j++) {
batch[j] = fn();
}
await Promise.all(batch);
// garbage collect and check memory usage every 1000 requests
if (i > 0 && i % 10 === 0) {
const report = await getMemoryUsage();
if (report > peak_memory) {
peak_memory = report;
}
memory_examples.push(report);
}
}

// wait for the last memory usage to be stable
const end_memory = await getMemoryUsage();
if (end_memory > peak_memory) {
peak_memory = end_memory;
}
// use first example as a reference if is a memory leak this should keep increasing and not be stable
const consumption = end_memory - memory_examples[0];
// memory leak in MB
const leak = Math.floor(consumption > 0 ? consumption / 1024 / 1024 : 0);
return { leak, start_memory, peak_memory, end_memory, memory_examples };
}

for (const test_info of [
["#10265 should not leak memory when ignoring the body", callIgnore, false],
["should not leak memory when buffering the body", callBuffering, false],
["should not leak memory when streaming the body", callStreaming, false],
["should not leak memory when streaming the body incompletely", callIncompleteStreaming, true],
["should not leak memory when streaming the body and echoing it back", callStreamingEcho, true],
]) {
const [testName, fn, skip] = test_info as [string, () => Promise<void>, boolean];
it.todoIf(skip)(
testName,
async () => {
const report = await calculateMemoryLeak(fn);
// peak memory is too high
expect(report.peak_memory > report.start_memory * 2).toBe(false);
// acceptable memory leak
expect(report.leak).toBeLessThanOrEqual(ACCEPTABLE_MEMORY_LEAK);
},
30_000,
);
}
29 changes: 29 additions & 0 deletions test/js/bun/http/serve.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -311,7 +311,7 @@
return new Response(
new ReadableStream({
pull(controller) {
throw new Error("TestPassed");

Check failure on line 314 in test/js/bun/http/serve.test.ts

View workflow job for this annotation

GitHub Actions / Test linux-x64 / Tests

error: TestPassed

at pull (/home/runner/work/bun/bun/test/js/bun/http/serve.test.ts:314:25) at promiseInvokeOrNoopMethodNoCatch (:1:21) at promiseInvokeOrNoopMethod (:1:21) at readableStreamDefaultControllerCallPullIfNeeded (:1:21)

Check failure on line 314 in test/js/bun/http/serve.test.ts

View workflow job for this annotation

GitHub Actions / Test darwin-x64-baseline / Tests

error: TestPassed

at pull (/Users/runner/work/bun/bun/test/js/bun/http/serve.test.ts:314:25) at promiseInvokeOrNoopMethodNoCatch (:1:21) at promiseInvokeOrNoopMethod (:1:21) at readableStreamDefaultControllerCallPullIfNeeded (:1:21)

Check failure on line 314 in test/js/bun/http/serve.test.ts

View workflow job for this annotation

GitHub Actions / Test linux-x64-baseline / Tests

error: TestPassed

at pull (/home/runner/work/bun/bun/test/js/bun/http/serve.test.ts:314:25) at promiseInvokeOrNoopMethodNoCatch (:1:21) at promiseInvokeOrNoopMethod (:1:21) at readableStreamDefaultControllerCallPullIfNeeded (:1:21)

Check failure on line 314 in test/js/bun/http/serve.test.ts

View workflow job for this annotation

GitHub Actions / Test linux-aarch64 / Tests

error: TestPassed

at pull (/home/runner/work/bun/bun/test/js/bun/http/serve.test.ts:314:25) at promiseInvokeOrNoopMethodNoCatch (:1:21) at promiseInvokeOrNoopMethod (:1:21) at readableStreamDefaultControllerCallPullIfNeeded (:1:21)
},
cancel(reason) {},
}),
Expand Down Expand Up @@ -348,7 +348,7 @@
async pull(controller) {
controller.enqueue("PASS");
controller.close();
throw new Error("FAIL");

Check failure on line 351 in test/js/bun/http/serve.test.ts

View workflow job for this annotation

GitHub Actions / Test linux-x64 / Tests

error: FAIL

at /home/runner/work/bun/bun/test/js/bun/http/serve.test.ts:351:25 at pull (/home/runner/work/bun/bun/test/js/bun/http/serve.test.ts:348:40) at promiseInvokeOrNoopMethodNoCatch (:1:21) at promiseInvokeOrNoopMethod (:1:21) at readableStreamDefaultControllerCallPullIfNeeded (:1:21)

Check failure on line 351 in test/js/bun/http/serve.test.ts

View workflow job for this annotation

GitHub Actions / Test linux-x64 / Tests

error: FAIL

at /home/runner/work/bun/bun/test/js/bun/http/serve.test.ts:351:25 at pull (/home/runner/work/bun/bun/test/js/bun/http/serve.test.ts:348:40) at promiseInvokeOrNoopMethodNoCatch (:1:21) at promiseInvokeOrNoopMethod (:1:21) at readableStreamDefaultControllerCallPullIfNeeded (:1:21)

Check failure on line 351 in test/js/bun/http/serve.test.ts

View workflow job for this annotation

GitHub Actions / Test linux-x64 / Tests

error: FAIL

at /home/runner/work/bun/bun/test/js/bun/http/serve.test.ts:351:25 at pull (/home/runner/work/bun/bun/test/js/bun/http/serve.test.ts:348:40) at promiseInvokeOrNoopMethodNoCatch (:1:21) at promiseInvokeOrNoopMethod (:1:21) at readableStreamDefaultControllerCallPullIfNeeded (:1:21)

Check failure on line 351 in test/js/bun/http/serve.test.ts

View workflow job for this annotation

GitHub Actions / Test linux-x64 / Tests

error: FAIL

at /home/runner/work/bun/bun/test/js/bun/http/serve.test.ts:351:25 at pull (/home/runner/work/bun/bun/test/js/bun/http/serve.test.ts:348:40) at promiseInvokeOrNoopMethodNoCatch (:1:21) at promiseInvokeOrNoopMethod (:1:21) at readableStreamDefaultControllerCallPullIfNeeded (:1:21)

Check failure on line 351 in test/js/bun/http/serve.test.ts

View workflow job for this annotation

GitHub Actions / Test darwin-x64 / Tests

error: FAIL

at /Users/runner/work/bun/bun/test/js/bun/http/serve.test.ts:351:25 at pull (/Users/runner/work/bun/bun/test/js/bun/http/serve.test.ts:348:40) at promiseInvokeOrNoopMethodNoCatch (:1:21) at promiseInvokeOrNoopMethod (:1:21) at readableStreamDefaultControllerCallPullIfNeeded (:1:21)

Check failure on line 351 in test/js/bun/http/serve.test.ts

View workflow job for this annotation

GitHub Actions / Test darwin-x64 / Tests

error: FAIL

at /Users/runner/work/bun/bun/test/js/bun/http/serve.test.ts:351:25 at pull (/Users/runner/work/bun/bun/test/js/bun/http/serve.test.ts:348:40) at promiseInvokeOrNoopMethodNoCatch (:1:21) at promiseInvokeOrNoopMethod (:1:21) at readableStreamDefaultControllerCallPullIfNeeded (:1:21)

Check failure on line 351 in test/js/bun/http/serve.test.ts

View workflow job for this annotation

GitHub Actions / Test darwin-x64 / Tests

error: FAIL

at /Users/runner/work/bun/bun/test/js/bun/http/serve.test.ts:351:25 at pull (/Users/runner/work/bun/bun/test/js/bun/http/serve.test.ts:348:40) at promiseInvokeOrNoopMethodNoCatch (:1:21) at promiseInvokeOrNoopMethod (:1:21) at readableStreamDefaultControllerCallPullIfNeeded (:1:21)

Check failure on line 351 in test/js/bun/http/serve.test.ts

View workflow job for this annotation

GitHub Actions / Test darwin-x64 / Tests

error: FAIL

at /Users/runner/work/bun/bun/test/js/bun/http/serve.test.ts:351:25 at pull (/Users/runner/work/bun/bun/test/js/bun/http/serve.test.ts:348:40) at promiseInvokeOrNoopMethodNoCatch (:1:21) at promiseInvokeOrNoopMethod (:1:21) at readableStreamDefaultControllerCallPullIfNeeded (:1:21)

Check failure on line 351 in test/js/bun/http/serve.test.ts

View workflow job for this annotation

GitHub Actions / Test darwin-aarch64 / Tests

error: FAIL

at /opt/namespace/githubrunner/work/bun/bun/test/js/bun/http/serve.test.ts:351:25 at pull (/opt/namespace/githubrunner/work/bun/bun/test/js/bun/http/serve.test.ts:348:40) at promiseInvokeOrNoopMethodNoCatch (:1:21) at promiseInvokeOrNoopMethod (:1:21) at readableStreamDefaultControllerCallPullIfNeeded (:1:21)

Check failure on line 351 in test/js/bun/http/serve.test.ts

View workflow job for this annotation

GitHub Actions / Test darwin-aarch64 / Tests

error: FAIL

at /opt/namespace/githubrunner/work/bun/bun/test/js/bun/http/serve.test.ts:351:25 at pull (/opt/namespace/githubrunner/work/bun/bun/test/js/bun/http/serve.test.ts:348:40) at promiseInvokeOrNoopMethodNoCatch (:1:21) at promiseInvokeOrNoopMethod (:1:21) at readableStreamDefaultControllerCallPullIfNeeded (:1:21)

Check failure on line 351 in test/js/bun/http/serve.test.ts

View workflow job for this annotation

GitHub Actions / Test darwin-aarch64 / Tests

error: FAIL

at /opt/namespace/githubrunner/work/bun/bun/test/js/bun/http/serve.test.ts:351:25 at pull (/opt/namespace/githubrunner/work/bun/bun/test/js/bun/http/serve.test.ts:348:40) at promiseInvokeOrNoopMethodNoCatch (:1:21) at promiseInvokeOrNoopMethod (:1:21) at readableStreamDefaultControllerCallPullIfNeeded (:1:21)

Check failure on line 351 in test/js/bun/http/serve.test.ts

View workflow job for this annotation

GitHub Actions / Test darwin-aarch64 / Tests

error: FAIL

at /opt/namespace/githubrunner/work/bun/bun/test/js/bun/http/serve.test.ts:351:25 at pull (/opt/namespace/githubrunner/work/bun/bun/test/js/bun/http/serve.test.ts:348:40) at promiseInvokeOrNoopMethodNoCatch (:1:21) at promiseInvokeOrNoopMethod (:1:21) at readableStreamDefaultControllerCallPullIfNeeded (:1:21)

Check failure on line 351 in test/js/bun/http/serve.test.ts

View workflow job for this annotation

GitHub Actions / Test darwin-aarch64 / Tests

error: FAIL

at /opt/namespace/githubrunner/work/bun/bun/test/js/bun/http/serve.test.ts:351:25 at pull (/opt/namespace/githubrunner/work/bun/bun/test/js/bun/http/serve.test.ts:348:40) at promiseInvokeOrNoopMethodNoCatch (:1:21) at promiseInvokeOrNoopMethod (:1:21) at readableStreamDefaultControllerCallPullIfNeeded (:1:21)

Check failure on line 351 in test/js/bun/http/serve.test.ts

View workflow job for this annotation

GitHub Actions / Test darwin-aarch64 / Tests

error: FAIL

at /opt/namespace/githubrunner/work/bun/bun/test/js/bun/http/serve.test.ts:351:25 at pull (/opt/namespace/githubrunner/work/bun/bun/test/js/bun/http/serve.test.ts:348:40) at promiseInvokeOrNoopMethodNoCatch (:1:21) at promiseInvokeOrNoopMethod (:1:21) at readableStreamDefaultControllerCallPullIfNeeded (:1:21)

Check failure on line 351 in test/js/bun/http/serve.test.ts

View workflow job for this annotation

GitHub Actions / Test darwin-aarch64 / Tests

error: FAIL

at /opt/namespace/githubrunner/work/bun/bun/test/js/bun/http/serve.test.ts:351:25 at pull (/opt/namespace/githubrunner/work/bun/bun/test/js/bun/http/serve.test.ts:348:40) at promiseInvokeOrNoopMethodNoCatch (:1:21) at promiseInvokeOrNoopMethod (:1:21) at readableStreamDefaultControllerCallPullIfNeeded (:1:21)

Check failure on line 351 in test/js/bun/http/serve.test.ts

View workflow job for this annotation

GitHub Actions / Test darwin-aarch64 / Tests

error: FAIL

at /opt/namespace/githubrunner/work/bun/bun/test/js/bun/http/serve.test.ts:351:25 at pull (/opt/namespace/githubrunner/work/bun/bun/test/js/bun/http/serve.test.ts:348:40) at promiseInvokeOrNoopMethodNoCatch (:1:21) at promiseInvokeOrNoopMethod (:1:21) at readableStreamDefaultControllerCallPullIfNeeded (:1:21)

Check failure on line 351 in test/js/bun/http/serve.test.ts

View workflow job for this annotation

GitHub Actions / Test darwin-x64-baseline / Tests

error: FAIL

at /Users/runner/work/bun/bun/test/js/bun/http/serve.test.ts:351:25 at pull (/Users/runner/work/bun/bun/test/js/bun/http/serve.test.ts:348:40) at promiseInvokeOrNoopMethodNoCatch (:1:21) at promiseInvokeOrNoopMethod (:1:21) at readableStreamDefaultControllerCallPullIfNeeded (:1:21)

Check failure on line 351 in test/js/bun/http/serve.test.ts

View workflow job for this annotation

GitHub Actions / Test darwin-x64-baseline / Tests

error: FAIL

at /Users/runner/work/bun/bun/test/js/bun/http/serve.test.ts:351:25 at pull (/Users/runner/work/bun/bun/test/js/bun/http/serve.test.ts:348:40) at promiseInvokeOrNoopMethodNoCatch (:1:21) at promiseInvokeOrNoopMethod (:1:21) at readableStreamDefaultControllerCallPullIfNeeded (:1:21)

Check failure on line 351 in test/js/bun/http/serve.test.ts

View workflow job for this annotation

GitHub Actions / Test darwin-x64-baseline / Tests

error: FAIL

at /Users/runner/work/bun/bun/test/js/bun/http/serve.test.ts:351:25 at pull (/Users/runner/work/bun/bun/test/js/bun/http/serve.test.ts:348:40) at promiseInvokeOrNoopMethodNoCatch (:1:21) at promiseInvokeOrNoopMethod (:1:21) at readableStreamDefaultControllerCallPullIfNeeded (:1:21)

Check failure on line 351 in test/js/bun/http/serve.test.ts

View workflow job for this annotation

GitHub Actions / Test darwin-x64-baseline / Tests

error: FAIL

at /Users/runner/work/bun/bun/test/js/bun/http/serve.test.ts:351:25 at pull (/Users/runner/work/bun/bun/test/js/bun/http/serve.test.ts:348:40) at promiseInvokeOrNoopMethodNoCatch (:1:21) at promiseInvokeOrNoopMethod (:1:21) at readableStreamDefaultControllerCallPullIfNeeded (:1:21)

Check failure on line 351 in test/js/bun/http/serve.test.ts

View workflow job for this annotation

GitHub Actions / Test linux-x64-baseline / Tests

error: FAIL

at /home/runner/work/bun/bun/test/js/bun/http/serve.test.ts:351:25 at pull (/home/runner/work/bun/bun/test/js/bun/http/serve.test.ts:348:40) at promiseInvokeOrNoopMethodNoCatch (:1:21) at promiseInvokeOrNoopMethod (:1:21) at readableStreamDefaultControllerCallPullIfNeeded (:1:21)

Check failure on line 351 in test/js/bun/http/serve.test.ts

View workflow job for this annotation

GitHub Actions / Test linux-x64-baseline / Tests

error: FAIL

at /home/runner/work/bun/bun/test/js/bun/http/serve.test.ts:351:25 at pull (/home/runner/work/bun/bun/test/js/bun/http/serve.test.ts:348:40) at promiseInvokeOrNoopMethodNoCatch (:1:21) at promiseInvokeOrNoopMethod (:1:21) at readableStreamDefaultControllerCallPullIfNeeded (:1:21)

Check failure on line 351 in test/js/bun/http/serve.test.ts

View workflow job for this annotation

GitHub Actions / Test linux-x64-baseline / Tests

error: FAIL

at /home/runner/work/bun/bun/test/js/bun/http/serve.test.ts:351:25 at pull (/home/runner/work/bun/bun/test/js/bun/http/serve.test.ts:348:40) at promiseInvokeOrNoopMethodNoCatch (:1:21) at promiseInvokeOrNoopMethod (:1:21) at readableStreamDefaultControllerCallPullIfNeeded (:1:21)

Check failure on line 351 in test/js/bun/http/serve.test.ts

View workflow job for this annotation

GitHub Actions / Test linux-x64-baseline / Tests

error: FAIL

at /home/runner/work/bun/bun/test/js/bun/http/serve.test.ts:351:25 at pull (/home/runner/work/bun/bun/test/js/bun/http/serve.test.ts:348:40) at promiseInvokeOrNoopMethodNoCatch (:1:21) at promiseInvokeOrNoopMethod (:1:21) at readableStreamDefaultControllerCallPullIfNeeded (:1:21)

Check failure on line 351 in test/js/bun/http/serve.test.ts

View workflow job for this annotation

GitHub Actions / Test linux-aarch64 / Tests

error: FAIL

at /home/runner/work/bun/bun/test/js/bun/http/serve.test.ts:351:25 at pull (/home/runner/work/bun/bun/test/js/bun/http/serve.test.ts:348:40) at promiseInvokeOrNoopMethodNoCatch (:1:21) at promiseInvokeOrNoopMethod (:1:21) at readableStreamDefaultControllerCallPullIfNeeded (:1:21)

Check failure on line 351 in test/js/bun/http/serve.test.ts

View workflow job for this annotation

GitHub Actions / Test linux-aarch64 / Tests

error: FAIL

at /home/runner/work/bun/bun/test/js/bun/http/serve.test.ts:351:25 at pull (/home/runner/work/bun/bun/test/js/bun/http/serve.test.ts:348:40) at promiseInvokeOrNoopMethodNoCatch (:1:21) at promiseInvokeOrNoopMethod (:1:21) at readableStreamDefaultControllerCallPullIfNeeded (:1:21)

Check failure on line 351 in test/js/bun/http/serve.test.ts

View workflow job for this annotation

GitHub Actions / Test linux-aarch64 / Tests

error: FAIL

at /home/runner/work/bun/bun/test/js/bun/http/serve.test.ts:351:25 at pull (/home/runner/work/bun/bun/test/js/bun/http/serve.test.ts:348:40) at promiseInvokeOrNoopMethodNoCatch (:1:21) at promiseInvokeOrNoopMethod (:1:21) at readableStreamDefaultControllerCallPullIfNeeded (:1:21)

Check failure on line 351 in test/js/bun/http/serve.test.ts

View workflow job for this annotation

GitHub Actions / Test linux-aarch64 / Tests

error: FAIL

at /home/runner/work/bun/bun/test/js/bun/http/serve.test.ts:351:25 at pull (/home/runner/work/bun/bun/test/js/bun/http/serve.test.ts:348:40) at promiseInvokeOrNoopMethodNoCatch (:1:21) at promiseInvokeOrNoopMethod (:1:21) at readableStreamDefaultControllerCallPullIfNeeded (:1:21)
},
});
const r = new Response(stream, options);
Expand Down Expand Up @@ -1430,12 +1430,12 @@
}

if (e.message.endsWith("/async-rejected")) {
throw new Error("");

Check failure on line 1433 in test/js/bun/http/serve.test.ts

View workflow job for this annotation

GitHub Actions / Test linux-x64 / Tests

error

at /home/runner/work/bun/bun/test/js/bun/http/serve.test.ts:1433:15 at error (/home/runner/work/bun/bun/test/js/bun/http/serve.test.ts:1427:20)

Check failure on line 1433 in test/js/bun/http/serve.test.ts

View workflow job for this annotation

GitHub Actions / Test darwin-x64-baseline / Tests

error

at /Users/runner/work/bun/bun/test/js/bun/http/serve.test.ts:1433:15 at error (/Users/runner/work/bun/bun/test/js/bun/http/serve.test.ts:1427:20)

Check failure on line 1433 in test/js/bun/http/serve.test.ts

View workflow job for this annotation

GitHub Actions / Test linux-x64-baseline / Tests

error

at /home/runner/work/bun/bun/test/js/bun/http/serve.test.ts:1433:15 at error (/home/runner/work/bun/bun/test/js/bun/http/serve.test.ts:1427:20)

Check failure on line 1433 in test/js/bun/http/serve.test.ts

View workflow job for this annotation

GitHub Actions / Test linux-aarch64 / Tests

error

at /home/runner/work/bun/bun/test/js/bun/http/serve.test.ts:1433:15 at error (/home/runner/work/bun/bun/test/js/bun/http/serve.test.ts:1427:20)
}

if (e.message.endsWith("/async-rejected-pending")) {
await Bun.sleep(100);
throw new Error("");

Check failure on line 1438 in test/js/bun/http/serve.test.ts

View workflow job for this annotation

GitHub Actions / Test linux-x64 / Tests

error

at /home/runner/work/bun/bun/test/js/bun/http/serve.test.ts:1438:15

Check failure on line 1438 in test/js/bun/http/serve.test.ts

View workflow job for this annotation

GitHub Actions / Test darwin-x64-baseline / Tests

error

at /Users/runner/work/bun/bun/test/js/bun/http/serve.test.ts:1438:15

Check failure on line 1438 in test/js/bun/http/serve.test.ts

View workflow job for this annotation

GitHub Actions / Test linux-x64-baseline / Tests

error

at /home/runner/work/bun/bun/test/js/bun/http/serve.test.ts:1438:15

Check failure on line 1438 in test/js/bun/http/serve.test.ts

View workflow job for this annotation

GitHub Actions / Test linux-aarch64 / Tests

error

at /home/runner/work/bun/bun/test/js/bun/http/serve.test.ts:1438:15
}

if (e.message.endsWith("/async-pending")) {
Expand Down Expand Up @@ -1524,3 +1524,32 @@
}).toThrow("Expected lowMemoryMode to be a boolean");
});
});
it("should resolve pending promise if requested ended with pending read", async () => {
let error: Error;
function shouldError(e: Error) {
error = e;
}
let is_done = false;
function shouldMarkDone(result: { done: boolean; value: any }) {
is_done = result.done;
}
await runTest(
{
fetch(req) {
// @ts-ignore
req.body?.getReader().read().catch(shouldError).then(shouldMarkDone);
return new Response("OK");
},
},
async server => {
const response = await fetch(server.url.origin, {
method: "POST",
body: "1".repeat(64 * 1024),
});
const text = await response.text();
expect(text).toContain("OK");
expect(is_done).toBe(true);
expect(error).toBeUndefined();
},
);
});
2 changes: 1 addition & 1 deletion test/js/node/http/fixtures/log-events.mjs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import * as http from "node:http";

const options = {
hostname: "www.google.com",
hostname: "www.example.com",
port: 80,
path: "/",
method: "GET",
Expand Down
40 changes: 40 additions & 0 deletions test/js/node/http/node-http.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1904,3 +1904,43 @@ it.skipIf(!process.env.TEST_INFO_STRIPE)("should be able to connect to stripe",
err = await new Response(stderr).text();
expect(err).toContain(`error: No such charge: '${charge_id}'\n`);
});

it("destroy should end download", async () => {
// just simulate some file that will take forever to download
const payload = Buffer.from("X".repeat(16 * 1024));

const server = Bun.serve({
port: 0,
async fetch(req) {
let running = true;
req.signal.onabort = () => (running = false);
return new Response(async function* () {
while (running) {
yield payload;
await Bun.sleep(10);
}
});
},
});

try {
let chunks = 0;

const { promise, resolve } = Promise.withResolvers();
const req = request(server.url, res => {
res.on("data", () => {
process.nextTick(resolve);
chunks++;
});
});
req.end();
// wait for the first chunk
await promise;
// should stop the download
req.destroy();
await Bun.sleep(200);
expect(chunks).toBeLessThanOrEqual(3);
} finally {
server.stop(true);
}
});
Loading