Skip to content
268 changes: 78 additions & 190 deletions src/http/lib.rs

Large diffs are not rendered by default.

17 changes: 3 additions & 14 deletions src/http_jsc/websocket_client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1615,20 +1615,9 @@ impl<const SSL: bool> WebSocket<SSL> {
let ws = Self::new_raw(outgoing, global_this, deflate_params, secure, None);

// `adopt_group` takes a closure to write the new socket.
let group = {
// reshaped for borrowck — `rare_data()` borrows `vm`
// mutably and `ws_client_group` also wants a `vm` reference.
let vm_ptr: *mut _ = global_this.bun_vm().as_mut();
// SAFETY: `rare_data()` returns `&mut RareData` reached through
// `vm.rare_data: Option<Box<RareData>>`, i.e. a SEPARATE heap
// allocation behind a `Box` — the returned `&mut` does not cover
// any byte of `*vm_ptr` itself, so forming `&*vm_ptr` alongside
// it is non-overlapping under Stacked Borrows. `lazy_group` only
// reads `vm.uws_loop()` / `vm.event_loop_handle` and never touches
// `vm.rare_data`, so the shared `&VirtualMachine` argument cannot
// observe or invalidate the `&mut RareData` receiver.
unsafe { (*vm_ptr).rare_data().ws_client_group::<SSL>(&*vm_ptr) }
};
let vm = global_this.bun_vm().as_mut();
let loop_ = vm.uws_loop();
let group = vm.rare_data().ws_client_group::<SSL>(loop_);
if !Socket::<SSL>::adopt_group(
tcp,
group,
Expand Down
9 changes: 2 additions & 7 deletions src/http_jsc/websocket_client/WebSocketUpgradeClient.rs
Original file line number Diff line number Diff line change
Expand Up @@ -403,13 +403,8 @@ impl<const SSL: bool> HTTPClient<SSL> {
using_proxy
);

// Reshaped for borrowck — `rare_data()` borrows `vm` mutably and
// `ws_upgrade_group` also wants a `vm` reference. See websocket_client.rs.
let group = {
// SAFETY: `rare_data()` returns `&mut RareData` reached through a
// separate Box; the `&*vm_ptr` argument does not overlap.
unsafe { (*vm_ptr).rare_data().ws_upgrade_group::<SSL>(&*vm_ptr) }
};
let loop_ = vm.uws_loop();
let group = vm.rare_data().ws_upgrade_group::<SSL>(loop_);
let kind: SocketKind = if SSL {
SocketKind::WsClientUpgradeTls
} else {
Expand Down
15 changes: 2 additions & 13 deletions src/jsc/VirtualMachine.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6374,21 +6374,10 @@ impl VirtualMachine {

self.event_loop_mut().ensure_waker();

// Note: reshaped for borrowck — `rare_data()` borrows `self` and
// `spawn_ipc_group` then needs `&mut VirtualMachine`. Split via raw
// pointers (disjoint fields) per the existing `Bun__RareData__*`
// accessors in virtual_machine_exports.rs.
#[cfg(not(windows))]
let this: *mut VirtualMachine = self;

#[cfg(not(windows))]
let instance: *mut IPCInstance = {
// SAFETY: disjoint borrow — `spawn_ipc_group` only touches the
// embedded `SocketGroup` field + `vm.uws_loop()`.
let group: *mut uws::SocketGroup = unsafe {
let rare = std::ptr::from_mut::<RareData>((*this).rare_data());
(*rare).spawn_ipc_group(&*this)
};
let loop_ = self.uws_loop();
let group: *mut uws::SocketGroup = self.rare_data().spawn_ipc_group(loop_);

// Box the instance first so `data.owner` can name its final
// address.
Expand Down
46 changes: 28 additions & 18 deletions src/jsc/rare_data.rs
Original file line number Diff line number Diff line change
Expand Up @@ -743,81 +743,91 @@ impl RareData {
}

// ── socket groups: lazy init ──────────────────────────────────────────
//
// These take the `uws::Loop` pointer directly (rather than
// `&VirtualMachine`) because every caller reaches `&mut RareData` through
// `vm.rare_data()`, which already holds `&mut VirtualMachine`; requiring a
// second `&VirtualMachine` just to read `vm.uws_loop()` forced a raw-pointer
// split-borrow at every call site. The loop pointer is `Copy` and read
// before `rare_data()` is borrowed, so no aliasing.
#[inline]
fn lazy_group<'a>(g: &'a mut SocketGroup, vm: &VirtualMachine) -> &'a mut SocketGroup {
fn lazy_group(g: &mut SocketGroup, loop_: *mut uws::Loop) -> &mut SocketGroup {
if g.loop_.is_null() {
g.init(vm.uws_loop(), None, core::ptr::null_mut());
g.init(loop_, None, core::ptr::null_mut());
}
g
}

pub fn spawn_ipc_group(&mut self, vm: &VirtualMachine) -> &mut SocketGroup {
Self::lazy_group(&mut self.spawn_ipc_group, vm)
pub fn spawn_ipc_group(&mut self, loop_: *mut uws::Loop) -> &mut SocketGroup {
Self::lazy_group(&mut self.spawn_ipc_group, loop_)
}
pub fn test_parallel_ipc_group(&mut self, vm: &VirtualMachine) -> &mut SocketGroup {
Self::lazy_group(&mut self.test_parallel_ipc_group, vm)
pub fn test_parallel_ipc_group(&mut self, loop_: *mut uws::Loop) -> &mut SocketGroup {
Self::lazy_group(&mut self.test_parallel_ipc_group, loop_)
}
/// One shared group per (VM, ssl) for every `Bun.connect` / `tls.connect`
/// client socket. Replaces the old per-connection `us_socket_context_t`
/// allocation that was the root of the SSL_CTX-per-connect leak.
pub fn bun_connect_group<const SSL: bool>(&mut self, vm: &VirtualMachine) -> &mut SocketGroup {
pub fn bun_connect_group<const SSL: bool>(
&mut self,
loop_: *mut uws::Loop,
) -> &mut SocketGroup {
Self::lazy_group(
if SSL {
&mut self.bun_connect_group_tls
} else {
&mut self.bun_connect_group_tcp
},
vm,
loop_,
)
}
pub fn postgres_group<const SSL: bool>(&mut self, vm: &VirtualMachine) -> &mut SocketGroup {
pub fn postgres_group<const SSL: bool>(&mut self, loop_: *mut uws::Loop) -> &mut SocketGroup {
Self::lazy_group(
if SSL {
&mut self.postgres_tls_group
} else {
&mut self.postgres_group
},
vm,
loop_,
)
}
pub fn mysql_group<const SSL: bool>(&mut self, vm: &VirtualMachine) -> &mut SocketGroup {
pub fn mysql_group<const SSL: bool>(&mut self, loop_: *mut uws::Loop) -> &mut SocketGroup {
Self::lazy_group(
if SSL {
&mut self.mysql_tls_group
} else {
&mut self.mysql_group_
},
vm,
loop_,
)
}
pub fn valkey_group<const SSL: bool>(&mut self, vm: &VirtualMachine) -> &mut SocketGroup {
pub fn valkey_group<const SSL: bool>(&mut self, loop_: *mut uws::Loop) -> &mut SocketGroup {
Self::lazy_group(
if SSL {
&mut self.valkey_tls_group
} else {
&mut self.valkey_group_
},
vm,
loop_,
)
}
pub fn ws_upgrade_group<const SSL: bool>(&mut self, vm: &VirtualMachine) -> &mut SocketGroup {
pub fn ws_upgrade_group<const SSL: bool>(&mut self, loop_: *mut uws::Loop) -> &mut SocketGroup {
Self::lazy_group(
if SSL {
&mut self.ws_upgrade_tls_group
} else {
&mut self.ws_upgrade_group_
},
vm,
loop_,
)
}
pub fn ws_client_group<const SSL: bool>(&mut self, vm: &VirtualMachine) -> &mut SocketGroup {
pub fn ws_client_group<const SSL: bool>(&mut self, loop_: *mut uws::Loop) -> &mut SocketGroup {
Self::lazy_group(
if SSL {
&mut self.ws_client_tls_group
} else {
&mut self.ws_client_group_
},
vm,
loop_,
)
}

Expand Down
9 changes: 5 additions & 4 deletions src/runtime/api/bun/js_bun_spawn_bindings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1520,11 +1520,12 @@ pub(crate) fn spawn_maybe_sync<const IS_SYNC: bool>(
#[cfg(unix)]
if !IS_SYNC {
if let Some(mode) = maybe_ipc_mode {
// SAFETY: re-borrow `jsc_vm` through the raw pointer for the nested
// `vm` arg while `rare_data()` holds the outer &mut.
let raw_socket = unsafe { &mut *jsc_vm_ptr }
// SAFETY: `jsc_vm_ptr` is the live per-thread VM; JS thread.
let vm = unsafe { &mut *jsc_vm_ptr };
let loop_ = vm.uws_loop();
let raw_socket = vm
.rare_data()
.spawn_ipc_group(unsafe { &mut *jsc_vm_ptr })
.spawn_ipc_group(loop_)
.from_fd(
bun_uws::SocketKind::SpawnIpc,
None,
Expand Down
10 changes: 2 additions & 8 deletions src/runtime/cli/test/parallel/Channel.rs
Original file line number Diff line number Diff line change
Expand Up @@ -114,14 +114,8 @@ impl<Owner: ChannelOwner> Channel<Owner> {
/// `SocketKind` value of its own. The per-file isolation swap skips
/// `rare.test_parallel_ipc_group` so the coordinator link survives.
fn ensure_posix_group(vm: &mut VirtualMachine) -> &mut uws::SocketGroup {
// borrowck split — `rare_data()` mutably borrows `vm`, but
// the group accessor needs `vm` again for `uws_loop()`. The two touch
// disjoint storage (the `Box<RareData>` payload vs the loop pointer
// field), so a raw-pointer reborrow is sound here.
let rd: *mut bun_jsc::rare_data::RareData = vm.rare_data();
// SAFETY: `rd` points into `vm`'s boxed RareData, which outlives this
// call; the accessor only reads `vm.uws_loop()` (a separate field).
let g = unsafe { (*rd).test_parallel_ipc_group(vm) };
let loop_ = vm.uws_loop();
let g = vm.rare_data().test_parallel_ipc_group(loop_);
// First Owner to call wins the vtable; coordinator and worker run in
// separate processes so there's never more than one Owner type sharing
// this group.
Expand Down
6 changes: 2 additions & 4 deletions src/runtime/socket/socket_body.rs
Original file line number Diff line number Diff line change
Expand Up @@ -551,12 +551,10 @@ impl<const SSL: bool> NewSocket<SSL> {
// SAFETY: per-thread VM singleton; `VirtualMachine::get()` yields the
// canonical `*mut` (write provenance) — never derive `&mut` from the
// `&'static` borrow stored on Handlers (that's `invalid_reference_casting`).
// No aliasing `&mut` held across the `rare_data()` borrow — `vm`
// reborrowed immutably for the 2nd arg.
let group = VirtualMachine::get()
.as_mut()
.rare_data()
.bun_connect_group::<SSL>(vm);
.bun_connect_group::<SSL>(vm.uws_loop());
let kind: uws::SocketKind = if SSL {
uws::SocketKind::BunSocketTls
} else {
Expand Down Expand Up @@ -3497,7 +3495,7 @@ impl<const SSL: bool> NewSocket<SSL> {
let group = VirtualMachine::get()
.as_mut()
.rare_data()
.bun_connect_group::<true>(vm);
.bun_connect_group::<true>(vm.uws_loop());
// SAFETY: `raw_socket` is the live `*mut us_socket_t` extracted from
// `InternalSocket::Connected` above; `owned_ssl_ctx` is the +1 ref
// taken from SecureContext/ssl_ctx_cache and never null here.
Expand Down
25 changes: 8 additions & 17 deletions src/runtime/valkey_jsc/js_valkey.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1514,21 +1514,12 @@ impl JSValkeyClient {
let socket_ref = self.ref_scope();

let is_tls = self.client.get().tls != valkey::TLS::None;
// `vm.rare_data()` needs `&mut VirtualMachine`; `client.vm`
// is `&'static`. Cast through raw — the per-thread VM is single-owner
// on the JS thread, and `valkey_group` only touches the embedded
// `SocketGroup` field + `vm.uws_loop()` (disjoint from anything we
// hold). Same pattern as `Bun__RareData__postgresGroup`.
let vm_ptr = std::ptr::from_ref::<VirtualMachine>(self.client.get().vm).cast_mut();
// SAFETY: per-thread VM, accessed from the JS thread; `rare_data()`
// lazy-inits the box.
let group: *mut uws::SocketGroup = unsafe {
let rare = std::ptr::from_mut::<jsc::rare_data::RareData>((*vm_ptr).rare_data());
if is_tls {
(*rare).valkey_group::<true>(&*vm_ptr)
} else {
(*rare).valkey_group::<false>(&*vm_ptr)
}
let vm = self.client.get().vm.as_mut();
let loop_ = vm.uws_loop();
let group: *mut uws::SocketGroup = if is_tls {
vm.rare_data().valkey_group::<true>(loop_)
} else {
vm.rare_data().valkey_group::<false>(loop_)
};

// Populate `_secure` first, then handle the failure branch outside the
Expand Down Expand Up @@ -1568,8 +1559,8 @@ impl JSValkeyClient {
let ssl_ctx: Option<*mut uws::SslCtx> = match &self.client.get().tls {
valkey::TLS::None => None,
valkey::TLS::Enabled => {
// SAFETY: `vm_ptr` is the live per-thread VM (see above).
Some(unsafe { crate::jsc_hooks::default_client_ssl_ctx(vm_ptr) })
// SAFETY: `vm` is the live per-thread VM (see above).
Some(unsafe { crate::jsc_hooks::default_client_ssl_ctx(vm) })
}
valkey::TLS::Custom(_) => Some(self._secure.get().unwrap()),
};
Expand Down
15 changes: 4 additions & 11 deletions src/sql_jsc/jsc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -324,20 +324,13 @@
}
#[inline]
fn postgres_socket_group<const SSL: bool>(&mut self) -> &mut bun_uws::SocketGroup {
// `rare_data()` returns the boxed `&mut RareData` (disjoint allocation);
// `*_group` only reads `vm.uws_loop()`. Route the read-only `vm`
// argument through the JS-thread singleton accessor instead of a
// raw-pointer split-borrow — `VirtualMachine::get()` is `&'static`
// and doesn't borrow `self`, so borrowck is satisfied without a
// per-site raw-pointer deref.
self.rare_data()
.postgres_group::<SSL>(VirtualMachine::get())
let loop_ = self.uws_loop();
self.rare_data().postgres_group::<SSL>(loop_)

Check warning on line 328 in src/sql_jsc/jsc.rs

View check run for this annotation

Claude / Claude Code Review

Stale trait doc comment on VirtualMachineSqlExt socket-group accessors

The trait doc comment on `VirtualMachineSqlExt::postgres_socket_group` still says it "Encapsulates the `rare_data(&mut self)` / `*_group(.., &VirtualMachine)` borrowck conflict … so the four call sites need no per-site raw-pointer dance," but this PR changed the `*_group` accessors to take `*mut uws::Loop` and rewrote the impl bodies to a plain two-liner with no borrowck conflict and no raw-pointer dance. The PR deleted the stale impl-body comments but left the trait-decl doc describing a signat
Comment thread
robobun marked this conversation as resolved.
Comment thread
robobun marked this conversation as resolved.
}
#[inline]
fn mysql_socket_group<const SSL: bool>(&mut self) -> &mut bun_uws::SocketGroup {
// See `postgres_socket_group` — singleton `&'static` for the read-only
// `vm` argument avoids the raw-pointer split-borrow.
self.rare_data().mysql_group::<SSL>(VirtualMachine::get())
let loop_ = self.uws_loop();
self.rare_data().mysql_group::<SSL>(loop_)
}
}

Expand Down
Loading