Skip to content
Open
Show file tree
Hide file tree
Changes from 3 commits
Commits
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
141 changes: 34 additions & 107 deletions src/runtime/webcore/fetch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -190,10 +190,7 @@ fn data_url_response(data_url_: DataURL, global_this: &JSGlobalObject) -> JSValu
Err(_) => {
let err =
global_this.create_error_instance(format_args!("failed to fetch the data URL"));
return JSPromise::dangerously_create_rejected_promise_value_without_notifying_vm(
global_this,
err,
);
return JSPromise::rejected_promise(global_this, err).to_js();
}
};
let blob = Blob::init(data, global_this);
Expand Down Expand Up @@ -365,7 +362,7 @@ fn reject_on_exception(
}
},
};
Ok(JSPromise::dangerously_create_rejected_promise_value_without_notifying_vm(global_this, err))
Ok(JSPromise::rejected_promise(global_this, err).to_js())
}

// ──────────────────────────────────────────────────────────────────────────
Expand Down Expand Up @@ -403,12 +400,7 @@ fn fetch_impl<const ALLOW_GET_BODY: bool>(
jsc::ErrorCode::MISSING_ARGS,
format_args!("{FETCH_ERROR_NO_ARGS}"),
);
return Ok(
JSPromise::dangerously_create_rejected_promise_value_without_notifying_vm(
global_this,
err,
),
);
return Ok(JSPromise::rejected_promise(global_this, err).to_js());
}

let mut headers: Option<Headers> = None;
Expand Down Expand Up @@ -559,12 +551,7 @@ fn fetch_impl<const ALLOW_GET_BODY: bool>(
jsc::ErrorCode::INVALID_URL,
format_args!("{FETCH_ERROR_BLANK_URL}"),
);
return Ok(
JSPromise::dangerously_create_rejected_promise_value_without_notifying_vm(
global_this,
err,
),
);
return Ok(JSPromise::rejected_promise(global_this, err).to_js());
}

if url_str.has_prefix_comptime(b"data:") {
Expand All @@ -575,12 +562,7 @@ fn fetch_impl<const ALLOW_GET_BODY: bool>(
Ok(d) => d,
Err(_) => {
let err = ctx.create_error_instance(format_args!("failed to fetch the data URL"));
return Ok(
JSPromise::dangerously_create_rejected_promise_value_without_notifying_vm(
global_this,
err,
),
);
return Ok(JSPromise::rejected_promise(global_this, err).to_js());
}
};
let mut data_url = data_url;
Expand All @@ -600,12 +582,7 @@ fn fetch_impl<const ALLOW_GET_BODY: bool>(
jsc::ErrorCode::INVALID_URL,
format_args!("fetch() URL is invalid"),
);
return Ok(
JSPromise::dangerously_create_rejected_promise_value_without_notifying_vm(
global_this,
err,
),
);
return Ok(JSPromise::rejected_promise(global_this, err).to_js());
}
};
let mut url_proxy_buffer = owned_url.into_href().into_vec();
Expand Down Expand Up @@ -1019,11 +996,7 @@ fn fetch_impl<const ALLOW_GET_BODY: bool>(
jsc::ErrorCode::INVALID_ARG_VALUE,
format_args!("fetch() proxy URL is invalid"),
);
return Ok(
JSPromise::dangerously_create_rejected_promise_value_without_notifying_vm(
global_this, err,
),
);
return Ok(JSPromise::rejected_promise(global_this, err).to_js());
}
let mut buffer: Vec<u8> = Vec::with_capacity(url_proxy_buffer.len());
buffer.extend_from_slice(&url_proxy_buffer);
Expand Down Expand Up @@ -1059,9 +1032,7 @@ fn fetch_impl<const ALLOW_GET_BODY: bool>(
format_args!("fetch() proxy URL is invalid"),
);
return Ok(
JSPromise::dangerously_create_rejected_promise_value_without_notifying_vm(
global_this, err,
),
JSPromise::rejected_promise(global_this, err).to_js()
);
}
let mut buffer: Vec<u8> =
Expand Down Expand Up @@ -1143,12 +1114,7 @@ fn fetch_impl<const ALLOW_GET_BODY: bool>(
jsc::ErrorCode::INVALID_ARG_TYPE,
format_args!("signal is not of type AbortSignal."),
);
return Ok(
JSPromise::dangerously_create_rejected_promise_value_without_notifying_vm(
global_this,
err,
),
);
return Ok(JSPromise::rejected_promise(global_this, err).to_js());
}

if global_this.has_exception() {
Expand Down Expand Up @@ -1177,12 +1143,7 @@ fn fetch_impl<const ALLOW_GET_BODY: bool>(
jsc::ErrorCode::INVALID_ARG_TYPE,
format_args!("signal is not of type AbortSignal."),
);
return Ok(
JSPromise::dangerously_create_rejected_promise_value_without_notifying_vm(
global_this,
err,
),
);
return Ok(JSPromise::rejected_promise(global_this, err).to_js());
}
}

Expand Down Expand Up @@ -1414,12 +1375,7 @@ fn fetch_impl<const ALLOW_GET_BODY: bool>(
jsc::ErrorCode::INVALID_ARG_VALUE,
format_args!("{FETCH_ERROR_PROXY_UNIX}"),
);
return Ok(
JSPromise::dangerously_create_rejected_promise_value_without_notifying_vm(
global_this,
err,
),
);
return Ok(JSPromise::rejected_promise(global_this, err).to_js());
}

if global_this.has_exception() {
Expand Down Expand Up @@ -1477,12 +1433,7 @@ fn fetch_impl<const ALLOW_GET_BODY: bool>(
bstr::BStr::new(url_path_decoded)
),
);
return Ok(
JSPromise::dangerously_create_rejected_promise_value_without_notifying_vm(
global_this,
err,
),
);
return Ok(JSPromise::rejected_promise(global_this, err).to_js());
}
}

Expand Down Expand Up @@ -1593,12 +1544,7 @@ fn fetch_impl<const ALLOW_GET_BODY: bool>(
jsc::ErrorCode::INVALID_ARG_VALUE,
format_args!("protocol must be http:, https: or s3:"),
);
return Ok(
JSPromise::dangerously_create_rejected_promise_value_without_notifying_vm(
global_this,
err,
),
);
return Ok(JSPromise::rejected_promise(global_this, err).to_js());
}
}

Expand All @@ -1610,12 +1556,7 @@ fn fetch_impl<const ALLOW_GET_BODY: bool>(
jsc::ErrorCode::INVALID_ARG_VALUE,
format_args!("{FETCH_ERROR_UNEXPECTED_BODY}"),
);
return Ok(
JSPromise::dangerously_create_rejected_promise_value_without_notifying_vm(
global_this,
err,
),
);
return Ok(JSPromise::rejected_promise(global_this, err).to_js());
}

// Fetch spec step 11: reject synchronously for a pre-aborted signal. Runs
Expand All @@ -1631,12 +1572,7 @@ fn fetch_impl<const ALLOW_GET_BODY: bool>(
}
}
body.detach();
return Ok(
JSPromise::dangerously_create_rejected_promise_value_without_notifying_vm(
global_this,
reason,
),
);
return Ok(JSPromise::rejected_promise(global_this, reason).to_js());
}
}

Expand Down Expand Up @@ -1674,11 +1610,11 @@ fn fetch_impl<const ALLOW_GET_BODY: bool>(
old.detach();
break 'prepare_body;
}
let rejected_value =
JSPromise::dangerously_create_rejected_promise_value_without_notifying_vm(
global_this,
global_this.create_error_instance(format_args!("Failed to start s3 stream")),
);
let rejected_value = JSPromise::rejected_promise(
global_this,
global_this.create_error_instance(format_args!("Failed to start s3 stream")),
)
.to_js();
// HTTPRequestBody has no Drop impl, so a bare `drop(body)` would
// leak the S3 Blob.Store ref.
body.detach();
Expand Down Expand Up @@ -1710,11 +1646,7 @@ fn fetch_impl<const ALLOW_GET_BODY: bool>(
let opened_fd = match opened_fd_res {
Err(err) => {
let err_js = err.to_js(global_this);
let rejected_value =
JSPromise::dangerously_create_rejected_promise_value_without_notifying_vm(
global_this,
err_js,
);
let rejected_value = JSPromise::rejected_promise(global_this, err_js).to_js();
return Ok(rejected_value);
}
Ok(fd) => fd,
Expand Down Expand Up @@ -1806,10 +1738,7 @@ fn fetch_impl<const ALLOW_GET_BODY: bool>(
match res {
Err(err) => {
let rejected_value =
JSPromise::dangerously_create_rejected_promise_value_without_notifying_vm(
global_this,
err.to_js(global_this),
);
JSPromise::rejected_promise(global_this, err.to_js(global_this)).to_js();
body.detach();
return Ok(rejected_value);
}
Expand Down Expand Up @@ -1898,14 +1827,13 @@ fn fetch_impl<const ALLOW_GET_BODY: bool>(
// `defer body.ReadableStream.deinit()` → Drop on `body` scope exit.

if method != Method::PUT && method != Method::POST {
return Ok(
JSPromise::dangerously_create_rejected_promise_value_without_notifying_vm(
global_this,
global_this.create_error_instance(format_args!(
"Only POST and PUT do support body when using S3"
)),
),
);
return Ok(JSPromise::rejected_promise(
global_this,
global_this.create_error_instance(format_args!(
"Only POST and PUT do support body when using S3"
)),
)
.to_js());
}
let promise = jsc::JSPromiseStrong::init(global_this);
let promise_value = promise.value();
Expand Down Expand Up @@ -1979,12 +1907,11 @@ fn fetch_impl<const ALLOW_GET_BODY: bool>(
) {
Ok(r) => r,
Err(sign_err) => {
return Ok(
JSPromise::dangerously_create_rejected_promise_value_without_notifying_vm(
global_this,
s3::get_js_sign_error(sign_err.into(), global_this),
),
);
return Ok(JSPromise::rejected_promise(
global_this,
s3::get_js_sign_error(sign_err.into(), global_this),
)
.to_js());
}
};
// `defer result.deinit()` → Drop.
Expand Down
2 changes: 1 addition & 1 deletion test/js/bun/http/fetch-file-upload.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -234,7 +234,7 @@
proxy: "http://localhost:3000",
});
expect(Bun.peek.status(resp)).toBe("rejected");
expect(async () => await resp).toThrow("no such file or directory");
expect(resp).rejects.toThrow("no such file or directory");

Check warning on line 237 in test/js/bun/http/fetch-file-upload.test.ts

View check run for this annotation

Claude / Claude Code Review

GC regression test no longer collects the promises it was written to exercise

The trailing `Bun.gc(true)` no longer collects the 1000 rejected promises this loop was written to stress: `rejected_promise` now appends each promise to `m_aboutToBeNotifiedRejectedPromises` (a GC root), and `.rejects.toThrow()` only calls `markAsHandled()` without draining that list, so all 1000 are still pinned when the forced GC runs. Insert an `await 1;` before this `Bun.gc(true)` so `handleRejectedPromises()` clears the tracker first and the GC safety net still bites.
Comment thread
claude[bot] marked this conversation as resolved.
}
});
Bun.gc(true);
Expand Down
Loading