Skip to content
This repository was archived by the owner on Apr 2, 2026. It is now read-only.
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
25 changes: 22 additions & 3 deletions core/runtime/jsrealm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -113,9 +113,9 @@ pub struct ContextState {
/// `UvLoopInner` and `ContextState` are `!Send` -- all access is on the
/// event loop thread.
pub(crate) uv_loop_inner: Cell<Option<*const UvLoopInner>>,
/// Raw pointer to the `uv_loop_t` handle, used to set `loop_.data`
/// to the current `v8::Context` at the start of each event loop tick
/// so that libuv-style C callbacks can retrieve the context.
/// Raw pointer to the `uv_loop_t` handle. At registration time,
/// `loop_.data` is set to a `Global::into_raw()` pointer so that
/// libuv-style C callbacks can retrieve the context.
///
/// # Safety
/// Same lifetime requirements as `uv_loop_inner` above.
Expand Down Expand Up @@ -242,6 +242,25 @@ impl JsRealmInner {
let raw_ptr = self.state().isolate.unwrap();
// SAFETY: We know the isolate outlives the realm
let mut isolate = unsafe { v8::Isolate::from_raw_isolate_ptr(raw_ptr) };

if let Some(loop_ptr) = state.uv_loop_ptr.get() {
// SAFETY: `loop_ptr` is valid for the lifetime of the runtime
// (guaranteed by `register_uv_loop`). `data` was set to a
// `Global::into_raw()` pointer during registration. We
// reconstruct the Global via `from_raw` so it is properly
// dropped. `NonNull::new_unchecked` is safe because we checked
// `!is_null()`. `isolate` is valid because we just obtained it
// from the raw isolate pointer above.
unsafe {
let data = (*loop_ptr).data;
if !data.is_null() {
let raw = std::ptr::NonNull::new_unchecked(data as *mut v8::Context);
let _global = v8::Global::from_raw(&mut isolate, raw);
(*loop_ptr).data = std::ptr::null_mut();
}
}
}

v8::scope!(let scope, &mut isolate);
// These globals will prevent snapshots from completing, take them
state.exception_state.prepare_to_destroy();
Expand Down
36 changes: 15 additions & 21 deletions core/runtime/jsruntime.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1545,8 +1545,9 @@ impl JsRuntime {
/// (timers, I/O, idle, prepare, check, close) are driven by
/// `poll_event_loop`.
///
/// The v8::Context pointer is stored in `loop_.data` at the start of each
/// event loop tick so that libuv-style callbacks can retrieve it.
/// A `v8::Global<v8::Context>` is created via `Global::into_raw()` and
/// stored in `loop_.data` so that libuv-style callbacks can retrieve
/// the context. The raw Global is dropped during realm cleanup.
///
/// # Safety
/// `loop_ptr` must be a valid, initialized `uv_loop_t` pointer that
Expand All @@ -1555,12 +1556,23 @@ impl JsRuntime {
&mut self,
loop_ptr: *mut crate::uv_compat::uv_loop_t,
) {
let context_state = &self.inner.main_realm.0.context_state;
let realm = &self.inner.main_realm;
let context_state = &realm.0.context_state;
let inner_ptr =
unsafe { crate::uv_compat::uv_loop_get_inner_ptr(loop_ptr) };
let uv_inner = inner_ptr as *const crate::uv_compat::UvLoopInner;
context_state.uv_loop_inner.set(Some(uv_inner));
context_state.uv_loop_ptr.set(Some(loop_ptr));

let global_ctx = realm.0.context().clone();
let raw = global_ctx.into_raw();
// SAFETY: `loop_ptr` is a valid, initialized `uv_loop_t` guaranteed
// by the caller. `raw` is a persistent-handle slot pointer from
// `Global::into_raw()` — V8 keeps it updated across GC cycles.
// The raw Global is reconstructed and dropped in `JsRealmInner::destroy`.
unsafe {
(*loop_ptr).data = raw.as_ptr() as *mut std::ffi::c_void;
}
}

/// Returns the runtime's op names, ordered by OpId.
Expand Down Expand Up @@ -2010,24 +2022,6 @@ impl JsRuntime {
let modules = &realm.0.module_map;
let context_state = &realm.0.context_state;

// Set the v8::Context pointer in the uv_loop so libuv-style callbacks
// can retrieve it via context_from_loop().
if let Some(loop_ptr) = context_state.uv_loop_ptr.get() {
let context = scope.get_current_context();
// SAFETY: `v8::Local<v8::Context>` is a thin pointer (one pointer
// wide). We store it as `*mut c_void` in `loop_.data` for the
// duration of this event loop tick. Callbacks reconstruct it via
// `std::mem::transmute` in `context_from_loop()`. The context is
// alive for the entire tick because `scope` holds it.
const _: () = assert!(
std::mem::size_of::<v8::Local<v8::Context>>()
== std::mem::size_of::<*mut std::ffi::c_void>()
);
unsafe {
let ctx_ptr: *mut std::ffi::c_void = std::mem::transmute(context);
(*loop_ptr).data = ctx_ptr;
}
}
let exception_state = &context_state.exception_state;

// Tight I/O loop: when run_io does work, re-run I/O phases immediately
Expand Down