Skip to content
Closed
Show file tree
Hide file tree
Changes from all 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
4 changes: 4 additions & 0 deletions src/runtime/server/AnyRequestContext.rs
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,10 @@ impl AnyRequestContext {
})
}

pub(crate) fn set_pathname(self, url: &bun_core::String) {
dispatch!(self, (), |_T, ctx| ctx.set_pathname(url))
}

/// Wont actually set anything if `self` is `.none`
pub(crate) fn set_request(self, req: *mut uws::Request) {
dispatch!(self, (), |T, ctx| {
Expand Down
26 changes: 9 additions & 17 deletions src/runtime/server/RequestContext.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2350,7 +2350,7 @@ where

// For HTTP/3, prepareJsRequestContextFor() already eagerly
// populated url+headers (the lazy getRequest() path is H1-only),
// so the guards below short-circuit and `req` is never read.
// so the copy below short-circuits and `req` is never read.
if !MUX {
// `Req<SSL,H3>` is erased to `c_void`; for !MUX the concrete
// type is `uws::Request`, so the cast is nominal.
Expand All @@ -2359,29 +2359,21 @@ where
.set_request(req.cast::<uws::Request>());
}

if request_object.ensure_url().is_err() {
request_object.url.set(BunString::EMPTY);
}
// `req` dies with this stack frame, so what is still read lazily from it is copied out now.
request_object.detach_uws_request_head();
}

// we have to clone the request headers here since they will soon belong to a different request
if !request_object.has_fetch_headers() {
if !MUX {
// `HeadersRef::create_from_uws` adopts the freshly-allocated +1 ref.
request_object.set_fetch_headers(Some(response::HeadersRef::create_from_uws(req)));
}
/// The path the development-mode error page prints once the uWS request is detached.
pub(crate) fn set_pathname(&self, url: &BunString) {
if DEBUG_MODE {
self.pathname.set(url.clone());
}

// This object dies after the stack frame is popped
// so we have to clear it in here too
request_object.request_context.detach_request();
}

pub(crate) fn to_async(&self, req: *mut Req<SSL_ENABLED, MUX>, request_object: &mut Request) {
ctx_log!("toAsync");
self.to_async_without_abort_handler(req, request_object);
if DEBUG_MODE {
self.pathname.set(request_object.url.get().clone());
}
self.set_pathname(request_object.url.get());
self.set_abort_handler();
}

Expand Down
30 changes: 30 additions & 0 deletions src/runtime/server/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -419,6 +419,21 @@ impl Drop for DetachRequestOnDrop {
}
}

/// The url and headers that `request_object` still reads lazily from the uWS receive buffer when its handler runs.
#[inline]
fn borrow_request_head(
request_object: *mut crate::webcore::Request,
) -> bun_uws_sys::loop_::RecvBufferBorrow {
unsafe fn release(owner: *mut c_void) {
// SAFETY: whoever registered the borrow keeps `owner`, the heap `webcore::Request`, live until it is unregistered.
let request = unsafe { &*owner.cast::<crate::webcore::Request>() };
request.detach_uws_request_head();
request.request_context.set_pathname(request.url.get());
}

bun_uws_sys::loop_::RecvBufferBorrow::new(release, request_object.cast::<c_void>())
}

impl<const SSL: bool, const DEBUG: bool> NewServer<SSL, DEBUG> {
pub(crate) const HAS_H3: bool = SSL;

Expand Down Expand Up @@ -973,6 +988,11 @@ impl<const SSL: bool, const DEBUG: bool> NewServer<SSL, DEBUG> {
args.push(prepared.js_request);
args.extend_from_slice(&extra_args);

let mut head_borrow = borrow_request_head(prepared.request_object);
// SAFETY: `request_object` stays allocated for this whole frame (its JS wrapper, `prepared.js_request`, is on this
// stack), and the guard is a local of the frame.
let _head_borrow = unsafe { head_borrow.register() };

// SAFETY: `this` is the live server backref for this request.
let server = unsafe { &*this };
let _entered = server.vm().enter_event_loop_scope_without_checkpoint();
Expand Down Expand Up @@ -1120,6 +1140,11 @@ impl<const SSL: bool, const DEBUG: bool> NewServer<SSL, DEBUG> {
return;
};

let mut head_borrow = borrow_request_head(prepared.request_object);
// SAFETY: `request_object` stays allocated for this whole frame (its JS wrapper, `prepared.js_request`, is on this
// stack), and the guard is a local of the frame.
let _head_borrow = unsafe { head_borrow.register() };

// SAFETY: `this` is the live server backref for this request.
let server = unsafe { &*this };
let _entered = server.vm().enter_event_loop_scope_without_checkpoint();
Expand Down Expand Up @@ -1172,6 +1197,11 @@ impl<const SSL: bool, const DEBUG: bool> NewServer<SSL, DEBUG> {
return;
};

let mut head_borrow = borrow_request_head(prepared.request_object);
// SAFETY: `request_object` stays allocated for this whole frame (its JS wrapper, `prepared.js_request`, is on this
// stack), and the guard is a local of the frame.
let _head_borrow = unsafe { head_borrow.register() };

// SAFETY: `server` is the live backref stored in `user_route`.
let server_ref = unsafe { &*server };
let _entered = server_ref.vm().enter_event_loop_scope_without_checkpoint();
Expand Down
9 changes: 9 additions & 0 deletions src/runtime/server/server_body.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3183,6 +3183,10 @@ where
.upgrade_context
.set(UpgradeState::Pending(NonNull::from(upgrade_ctx)))
};
let mut head_borrow = super::borrow_request_head(prepared.request_object);
// SAFETY: `request_object` stays allocated for this whole frame (its JS wrapper, `prepared.js_request`, is on this
// stack), and the guard is a local of the frame.
let _head_borrow = unsafe { head_borrow.register() };
let _entered = server_ref.vm().enter_event_loop_scope_without_checkpoint();
let server_request_list = Self::js_route_list_get_cached(server_js).unwrap();
// S008: `JSGlobalObject` is an `opaque_ffi!` ZST — safe deref.
Expand Down Expand Up @@ -3323,6 +3327,11 @@ where
let args = [unsafe { (*request_object_ptr).to_js(&global) }, server_js];
args[0].ensure_still_alive();

let mut head_borrow = super::borrow_request_head(request_object_ptr);
// SAFETY: `request_object_ptr` stays allocated for this whole frame (its JS wrapper, `args[0]`, is on this stack),
// and the guard is a local of the frame.
let _head_borrow = unsafe { head_borrow.register() };

let response_value = match this.config.on_request.call(&global, server_js, &args) {
Ok(v) => v,
Err(err) => global.take_exception(err),
Expand Down
19 changes: 19 additions & 0 deletions src/runtime/webcore/Request.rs
Original file line number Diff line number Diff line change
Expand Up @@ -236,6 +236,25 @@ impl Request {
self.headers.set(headers);
}

/// Copies the url and headers still read lazily from the `uWS::HttpRequest` into `self`, then forgets that request.
pub(crate) fn detach_uws_request_head(&self) {
let Some(req) = self.request_context.get_request() else {
return;
};

if self.ensure_url().is_err() {
self.url.set(BunString::EMPTY);
}

if !self.has_fetch_headers() {
self.set_fetch_headers(Some(HeadersRef::create_from_uws(
req.cast::<core::ffi::c_void>(),
)));
}

self.request_context.detach_request();
}

/// Returns the headers of the request. If the headers are not already cached, it will create a new FetchHeaders object.
/// If the headers are empty, it will look at request_context to get the headers.
/// If the headers are empty and request_context is null, it will create an empty FetchHeaders object.
Expand Down
74 changes: 74 additions & 0 deletions src/uws_sys/Loop.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use core::cell::Cell;
use core::ffi::{c_int, c_uint, c_void};
use core::ptr::NonNull;

Expand All @@ -13,6 +14,73 @@ bun_core::declare_scope!(Loop, visible);
/// it reaches the idle sweep, so passing this costs nothing on the paths that never park.
pub const NOW_NS_UNKNOWN: u64 = 0;

// ─────────────────── borrows of the receive buffer ───────────────────

/// Receive-buffer bytes that a callback still reads after it called into JS. `release` copies them out.
pub struct RecvBufferBorrow {
next: Cell<*const RecvBufferBorrow>,
release: unsafe fn(*mut c_void),
owner: *mut c_void,
}

thread_local! {
/// The borrows registered on this thread, innermost first.
static RECV_BUFFER_BORROWS: Cell<*const RecvBufferBorrow> =
const { Cell::new(core::ptr::null()) };
}

impl RecvBufferBorrow {
pub fn new(release: unsafe fn(*mut c_void), owner: *mut c_void) -> Self {
Self {
next: Cell::new(core::ptr::null()),
release,
owner,
}
}

/// Safety: `release(owner)` stays sound to call, repeatedly, until the guard drops. Guards drop in reverse order.
pub unsafe fn register(&mut self) -> RecvBufferBorrowGuard<'_> {
self.next.set(RECV_BUFFER_BORROWS.get());
RECV_BUFFER_BORROWS.set(core::ptr::from_ref(self));
RecvBufferBorrowGuard(self)
}
}

/// Unregisters its `RecvBufferBorrow` on drop.
pub struct RecvBufferBorrowGuard<'a>(&'a RecvBufferBorrow);

impl Drop for RecvBufferBorrowGuard<'_> {
fn drop(&mut self) {
debug_assert!(
core::ptr::eq(RECV_BUFFER_BORROWS.get(), core::ptr::from_ref(self.0)),
"RecvBufferBorrowGuard dropped out of order"
);
RECV_BUFFER_BORROWS.set(self.0.next.get());
}
}

/// Runs before every run of this thread's loop, which is what reads a socket into the buffer again.
#[inline]
fn release_recv_buffer_borrows() {
if RECV_BUFFER_BORROWS.get().is_null() {
return;
}
release_registered_borrows();
}

#[cold]
fn release_registered_borrows() {
let mut node = RECV_BUFFER_BORROWS.get();
while !node.is_null() {
// SAFETY: `register`'s caller keeps each listed node live and its `release(owner)` sound to call.
unsafe {
let borrow = &*node;
node = borrow.next.get();
(borrow.release)(borrow.owner);
}
}
}

// ───────────────────────────── PosixLoop ─────────────────────────────

// Mirrors C `struct us_loop_t` (packages/bun-usockets/src/internal/eventing/
Expand Down Expand Up @@ -245,11 +313,13 @@ impl PosixLoop {
}

pub fn tick(&mut self) {
release_recv_buffer_borrows();
// SAFETY: self is a valid loop pointer
unsafe { c::us_loop_run_bun_tick(self, core::ptr::null(), NOW_NS_UNKNOWN) };
}

pub fn tick_without_idle(&mut self) {
release_recv_buffer_borrows();
let timespec = Timespec { sec: 0, nsec: 0 };
// SAFETY: self is a valid loop pointer; &timespec lives for the call
unsafe { c::us_loop_run_bun_tick(self, &raw const timespec, NOW_NS_UNKNOWN) };
Expand All @@ -259,6 +329,7 @@ impl PosixLoop {
/// `timer::All::get_timeout`), reused by the JS park hook's idle-sweep rate limit rather
/// than read again. `NOW_NS_UNKNOWN` if the caller has none to share.
pub fn tick_with_timeout(&mut self, timespec: Option<&Timespec>, now_ns: u64) {
release_recv_buffer_borrows();
// SAFETY: self is a valid loop pointer
unsafe {
c::us_loop_run_bun_tick(
Expand Down Expand Up @@ -386,11 +457,13 @@ impl WindowsLoop {
/// Windows the park hook is driven from `us_loop_run` (libuv.c), which reads libuv's
/// already-refreshed clock via `uv_now` rather than taking one of its own.
pub fn tick_with_timeout(&mut self, _: Option<&Timespec>, _now_ns: u64) {
release_recv_buffer_borrows();
// SAFETY: self is a valid loop pointer
unsafe { c::us_loop_run(self) };
}

pub fn tick_without_idle(&mut self) {
release_recv_buffer_borrows();
// SAFETY: self is a valid loop pointer
unsafe { c::us_loop_pump(self) };
}
Expand Down Expand Up @@ -418,6 +491,7 @@ impl WindowsLoop {
}

pub fn run(&mut self) {
release_recv_buffer_borrows();
// SAFETY: self is a valid loop pointer
unsafe { c::us_loop_run(self) };
}
Expand Down
Loading
Loading