diff --git a/libs/core/runtime/jsrealm.rs b/libs/core/runtime/jsrealm.rs index 221ce9bb1b77c3..d2ca7afde20172 100644 --- a/libs/core/runtime/jsrealm.rs +++ b/libs/core/runtime/jsrealm.rs @@ -115,9 +115,9 @@ pub struct ContextState { /// `UvLoopInner` and `ContextState` are `!Send` -- all access is on the /// event loop thread. pub(crate) uv_loop_inner: Cell>, - /// 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. @@ -244,6 +244,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(); diff --git a/libs/core/runtime/jsruntime.rs b/libs/core/runtime/jsruntime.rs index 7bc56f9cfd6250..deae4119412c9e 100644 --- a/libs/core/runtime/jsruntime.rs +++ b/libs/core/runtime/jsruntime.rs @@ -1546,8 +1546,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` 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 @@ -1556,12 +1557,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. @@ -2011,24 +2023,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` 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::>() - == 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