Repository: denoland/deno_core
Pull request #378 (2023-12-07 18:23:40)

refactor(core): remove ContextState outer lock
Removes the outer `RefCell` without changing functionality. This unlocks additional optimizations. Additionally, promotes the `Weak<JsRuntimeState>` to `Rc<JsRuntimeState>` because we know it'll get cleaned up properly.

 - [x] Tested `deno`


Patch:
diff --git a/core/modules/map.rs b/core/modules/map.rs
index 134fffe..45f1cf7 100644
--- a/core/modules/map.rs
+++ b/core/modules/map.rs
@@ -86,8 +86,7 @@ pub(crate) struct ModuleMap {
   pub(crate) loader: RefCell<Rc<dyn ModuleLoader>>,
   pub(crate) import_meta_resolve_cb: ImportMetaResolveCallback,
 
-  // TODO(mmastrac): temporarily crate-public to avoid RefCell on ContextState
-  pub(crate) exception_state: Rc<ExceptionState>,
+  exception_state: Rc<ExceptionState>,
   dynamic_import_map:
     RefCell<HashMap<ModuleLoadId, v8::Global<v8::PromiseResolver>>>,
   preparing_dynamic_imports:
diff --git a/core/ops.rs b/core/ops.rs
index 8f373d4..eb31761 100644
--- a/core/ops.rs
+++ b/core/ops.rs
@@ -18,7 +18,6 @@ use std::ops::Deref;
 use std::ops::DerefMut;
 use std::ptr::NonNull;
 use std::rc::Rc;
-use std::rc::Weak;
 use std::sync::Arc;
 use v8::fast_api::CFunctionInfo;
 use v8::fast_api::CTypeInfo;
@@ -125,11 +124,12 @@ pub struct OpCtx {
 
   pub(crate) decl: Rc<OpDecl>,
   pub(crate) fast_fn_c_info: Option<NonNull<v8::fast_api::CFunctionInfo>>,
-  pub(crate) runtime_state: Weak<JsRuntimeState>,
   pub(crate) metrics_fn: Option<OpMetricsFn>,
-  pub(crate) context_state: Rc<RefCell<ContextState>>,
   /// If the last fast op failed, stores the error to be picked up by the slow op.
   pub(crate) last_fast_error: UnsafeCell<Option<AnyError>>,
+
+  context_state: Rc<ContextState>,
+  runtime_state: Rc<JsRuntimeState>,
 }
 
 impl OpCtx {
@@ -137,10 +137,10 @@ impl OpCtx {
   pub(crate) fn new(
     id: OpId,
     isolate: *mut Isolate,
-    context_state: Rc<RefCell<ContextState>>,
+    context_state: Rc<ContextState>,
     decl: Rc<OpDecl>,
     state: Rc<RefCell<OpState>>,
-    runtime_state: Weak<JsRuntimeState>,
+    runtime_state: Rc<JsRuntimeState>,
     get_error_class_fn: GetErrorClassFn,
     metrics_fn: Option<OpMetricsFn>,
   ) -> Self {
@@ -221,6 +221,16 @@ impl OpCtx {
     let opt_mut = &mut *self.last_fast_error.get();
     *opt_mut = Some(error);
   }
+
+  /// Get the [`ContextState`] for this op.
+  pub(crate) fn context_state(&self) -> &ContextState {
+    &self.context_state
+  }
+
+  /// Get the [`JsRuntimeState`] for this op.
+  pub(crate) fn runtime_state(&self) -> &JsRuntimeState {
+    &self.runtime_state
+  }
 }
 
 /// Maintains the resources and ops inside a JS runtime.
diff --git a/core/ops_builtin_v8.rs b/core/ops_builtin_v8.rs
index c4091c9..7bc4408 100644
--- a/core/ops_builtin_v8.rs
+++ b/core/ops_builtin_v8.rs
@@ -26,13 +26,13 @@ use v8::ValueSerializerHelper;
 #[op2]
 pub fn op_ref_op(scope: &mut v8::HandleScope, promise_id: i32) {
   let context_state = JsRealm::state_from_scope(scope);
-  context_state.borrow_mut().unrefed_ops.remove(&promise_id);
+  context_state.unrefed_ops.borrow_mut().remove(&promise_id);
 }
 
 #[op2]
 pub fn op_unref_op(scope: &mut v8::HandleScope, promise_id: i32) {
   let context_state = JsRealm::state_from_scope(scope);
-  context_state.borrow_mut().unrefed_ops.insert(promise_id);
+  context_state.unrefed_ops.borrow_mut().insert(promise_id);
 }
 
 #[op2(reentrant)]
@@ -87,15 +87,15 @@ pub fn op_run_microtasks(isolate: *mut v8::Isolate) {
 #[op2]
 pub fn op_has_tick_scheduled(scope: &mut v8::HandleScope) -> bool {
   JsRealm::state_from_scope(scope)
-    .borrow()
     .has_next_tick_scheduled
+    .get()
 }
 
 #[op2]
 pub fn op_set_has_tick_scheduled(scope: &mut v8::HandleScope, v: bool) {
   JsRealm::state_from_scope(scope)
-    .borrow_mut()
-    .has_next_tick_scheduled = v;
+    .has_next_tick_scheduled
+    .set(v);
 }
 
 #[derive(Serialize)]
@@ -719,21 +719,20 @@ pub fn op_set_wasm_streaming_callback(
   #[global] cb: v8::Global<v8::Function>,
 ) -> Result<(), Error> {
   let context_state_rc = JsRealm::state_from_scope(scope);
-  let mut context_state = context_state_rc.borrow_mut();
   // The callback to pass to the v8 API has to be a unit type, so it can't
   // borrow or move any local variables. Therefore, we're storing the JS
   // callback in a JsRuntimeState slot.
-  if context_state.js_wasm_streaming_cb.is_some() {
+  if context_state_rc.js_wasm_streaming_cb.borrow().is_some() {
     return Err(type_error("op_set_wasm_streaming_callback already called"));
   }
-  context_state.js_wasm_streaming_cb = Some(Rc::new(cb));
+  *context_state_rc.js_wasm_streaming_cb.borrow_mut() = Some(Rc::new(cb));
 
   scope.set_wasm_streaming_callback(|scope, arg, wasm_streaming| {
     let (cb_handle, streaming_rid) = {
       let context_state_rc = JsRealm::state_from_scope(scope);
       let cb_handle = context_state_rc
-        .borrow()
         .js_wasm_streaming_cb
+        .borrow()
         .as_ref()
         .unwrap()
         .clone();
@@ -817,13 +816,9 @@ pub fn op_dispatch_exception(
 #[op2]
 #[serde]
 pub fn op_op_names(scope: &mut v8::HandleScope) -> Vec<String> {
-  let state_rc = JsRealm::state_from_scope(scope);
-  let state = state_rc.borrow();
-  state
-    .op_ctxs
-    .iter()
-    .map(|o| o.decl.name.to_string())
-    .collect()
+  let state = JsRealm::state_from_scope(scope);
+  let ctx = state.op_ctxs.borrow();
+  ctx.iter().map(|o| o.decl.name.to_string()).collect()
 }
 
 #[derive(Deserialize, Serialize)]
@@ -988,7 +983,6 @@ pub fn op_set_format_exception_callback<'a>(
 ) -> Option<v8::Local<'a, v8::Value>> {
   let context_state_rc = JsRealm::state_from_scope(scope);
   let old = context_state_rc
-    .borrow_mut()
     .exception_state
     .js_format_exception_cb
     .borrow_mut()
diff --git a/core/runtime/jsrealm.rs b/core/runtime/jsrealm.rs
index 43b28ed..b74e6de 100644
--- a/core/runtime/jsrealm.rs
+++ b/core/runtime/jsrealm.rs
@@ -13,6 +13,7 @@ use crate::tasks::V8TaskSpawnerFactory;
 use anyhow::Error;
 use deno_unsync::JoinSet;
 use futures::stream::StreamExt;
+use std::cell::Cell;
 use std::cell::RefCell;
 use std::collections::HashSet;
 use std::hash::BuildHasherDefault;
@@ -46,16 +47,19 @@ impl Hasher for IdentityHasher {
 #[derive(Default)]
 pub(crate) struct ContextState {
   pub(crate) task_spawner_factory: Arc<V8TaskSpawnerFactory>,
-  pub(crate) js_event_loop_tick_cb: Option<Rc<v8::Global<v8::Function>>>,
-  pub(crate) js_wasm_streaming_cb: Option<Rc<v8::Global<v8::Function>>>,
-  pub(crate) unrefed_ops: HashSet<i32, BuildHasherDefault<IdentityHasher>>,
-  pub(crate) pending_ops: JoinSet<PendingOp>,
+  pub(crate) js_event_loop_tick_cb:
+    RefCell<Option<Rc<v8::Global<v8::Function>>>>,
+  pub(crate) js_wasm_streaming_cb:
+    RefCell<Option<Rc<v8::Global<v8::Function>>>>,
+  pub(crate) unrefed_ops:
+    RefCell<HashSet<i32, BuildHasherDefault<IdentityHasher>>>,
+  pub(crate) pending_ops: RefCell<JoinSet<PendingOp>>,
   // We don't explicitly re-read this prop but need the slice to live alongside
   // the context
-  pub(crate) op_ctxs: Box<[OpCtx]>,
+  pub(crate) op_ctxs: RefCell<Box<[OpCtx]>>,
   pub(crate) isolate: Option<*mut v8::OwnedIsolate>,
   pub(crate) exception_state: Rc<ExceptionState>,
-  pub(crate) has_next_tick_scheduled: bool,
+  pub(crate) has_next_tick_scheduled: Cell<bool>,
 }
 
 /// A representation of a JavaScript realm tied to a [`JsRuntime`], that allows
@@ -92,14 +96,14 @@ pub(crate) struct JsRealm(pub(crate) JsRealmInner);
 
 #[derive(Clone)]
 pub(crate) struct JsRealmInner {
-  pub(crate) context_state: Rc<RefCell<ContextState>>,
+  pub(crate) context_state: Rc<ContextState>,
   context: Rc<v8::Global<v8::Context>>,
   pub(crate) module_map: Rc<ModuleMap>,
 }
 
 impl JsRealmInner {
   pub(crate) fn new(
-    context_state: Rc<RefCell<ContextState>>,
+    context_state: Rc<ContextState>,
     context: v8::Global<v8::Context>,
     module_map: Rc<ModuleMap>,
   ) -> Self {
@@ -116,7 +120,7 @@ impl JsRealmInner {
   }
 
   #[inline(always)]
-  pub(crate) fn state(&self) -> Rc<RefCell<ContextState>> {
+  pub(crate) fn state(&self) -> Rc<ContextState> {
     self.context_state.clone()
   }
 
@@ -136,16 +140,15 @@ impl JsRealmInner {
 
   pub fn destroy(self) {
     let state = self.state();
-    let raw_ptr = self.state().borrow().isolate.unwrap();
+    let raw_ptr = self.state().isolate.unwrap();
     // SAFETY: We know the isolate outlives the realm
     let isolate = unsafe { raw_ptr.as_mut().unwrap() };
-    let mut realm_state = state.borrow_mut();
     // These globals will prevent snapshots from completing, take them
-    realm_state.exception_state.prepare_to_destroy();
-    std::mem::take(&mut realm_state.js_event_loop_tick_cb);
-    std::mem::take(&mut realm_state.js_wasm_streaming_cb);
+    state.exception_state.prepare_to_destroy();
+    std::mem::take(&mut *state.js_event_loop_tick_cb.borrow_mut());
+    std::mem::take(&mut *state.js_wasm_streaming_cb.borrow_mut());
     // The OpCtx slice may contain a circular reference
-    std::mem::take(&mut realm_state.op_ctxs);
+    std::mem::take(&mut *state.op_ctxs.borrow_mut());
 
     self.context().open(isolate).clear_all_slots(isolate);
 
@@ -163,12 +166,9 @@ impl JsRealm {
   #[inline(always)]
   pub(crate) fn state_from_scope(
     scope: &mut v8::HandleScope,
-  ) -> Rc<RefCell<ContextState>> {
+  ) -> Rc<ContextState> {
     let context = scope.get_current_context();
-    context
-      .get_slot::<Rc<RefCell<ContextState>>>(scope)
-      .unwrap()
-      .clone()
+    context.get_slot::<Rc<ContextState>>(scope).unwrap().clone()
   }
 
   #[inline(always)]
@@ -183,7 +183,7 @@ impl JsRealm {
   ) -> Rc<ExceptionState> {
     let context = scope.get_current_context();
     context
-      .get_slot::<Rc<ModuleMap>>(scope)
+      .get_slot::<Rc<ContextState>>(scope)
       .unwrap()
       .exception_state
       .clone()
@@ -192,13 +192,13 @@ impl JsRealm {
   #[cfg(test)]
   #[inline(always)]
   pub fn num_pending_ops(&self) -> usize {
-    self.0.context_state.borrow().pending_ops.len()
+    self.0.context_state.pending_ops.borrow().len()
   }
 
   #[cfg(test)]
   #[inline(always)]
   pub fn num_unrefed_ops(&self) -> usize {
-    self.0.context_state.borrow().unrefed_ops.len()
+    self.0.context_state.unrefed_ops.borrow().len()
   }
 
   /// For info on the [`v8::Isolate`] parameter, check [`JsRealm#panics`].
diff --git a/core/runtime/jsruntime.rs b/core/runtime/jsruntime.rs
index 94d6325..d87eb50 100644
--- a/core/runtime/jsruntime.rs
+++ b/core/runtime/jsruntime.rs
@@ -631,18 +631,18 @@ impl JsRuntime {
       waker,
     });
 
-    let weak = Rc::downgrade(&state_rc);
-    let context_state = Rc::new(RefCell::new(ContextState::default()));
+    let context_state = Rc::new(ContextState {
+      isolate: Some(isolate_ptr),
+      ..Default::default()
+    });
 
     // Add the task spawners to the OpState
     let spawner = context_state
-      .borrow()
       .task_spawner_factory
       .clone()
       .new_same_thread_spawner();
     op_state.borrow_mut().put(spawner);
     let spawner = context_state
-      .borrow()
       .task_spawner_factory
       .clone()
       .new_cross_thread_spawner();
@@ -663,14 +663,13 @@ impl JsRuntime {
           context_state.clone(),
           Rc::new(decl),
           op_state.clone(),
-          weak.clone(),
+          state_rc.clone(),
           options.get_error_class_fn.unwrap_or(&|_| "Error"),
           metrics_fn,
         )
       })
       .collect::<Vec<_>>()
       .into_boxed_slice();
-    context_state.borrow_mut().isolate = Some(isolate_ptr);
 
     let refs = bindings::external_references(&op_ctxs, &additional_references);
     // V8 takes ownership of external_references.
@@ -704,7 +703,7 @@ impl JsRuntime {
     for op_ctx in op_ctxs.iter_mut() {
       op_ctx.isolate = isolate.as_mut() as *mut Isolate;
     }
-    context_state.borrow_mut().op_ctxs = op_ctxs;
+    *context_state.op_ctxs.borrow_mut() = op_ctxs;
 
     isolate.set_capture_stack_trace_for_uncaught_exceptions(true, 10);
     isolate.set_promise_reject_callback(bindings::promise_reject_callback);
@@ -752,7 +751,7 @@ impl JsRuntime {
     bindings::initialize_context(
       scope,
       context,
-      &context_state.borrow().op_ctxs,
+      &context_state.op_ctxs.borrow(),
       init_mode,
     );
 
@@ -772,7 +771,7 @@ impl JsRuntime {
       .import_meta_resolve_callback
       .unwrap_or_else(|| Box::new(default_import_meta_resolve_cb));
 
-    let exception_state = context_state.borrow().exception_state.clone();
+    let exception_state = context_state.exception_state.clone();
     let module_map = if let Some(snapshotted_data) = snapshotted_data {
       Rc::new(ModuleMap::new_from_snapshotted_data(
         loader,
@@ -1163,11 +1162,11 @@ impl JsRuntime {
 
     // Put global handles in the realm's ContextState
     let state_rc = realm.0.state();
-    let mut state = state_rc.borrow_mut();
-    state
+    state_rc
       .js_event_loop_tick_cb
+      .borrow_mut()
       .replace(Rc::new(event_loop_tick_cb));
-    state
+    state_rc
       .exception_state
       .js_build_custom_error_cb
       .borrow_mut()
@@ -1184,8 +1183,8 @@ impl JsRuntime {
   pub fn op_names(&self) -> Vec<&'static str> {
     let main_realm = self.inner.main_realm.clone();
     let state_rc = main_realm.0.state();
-    let state = state_rc.borrow();
-    state.op_ctxs.iter().map(|o| o.decl.name).collect()
+    let ctx = state_rc.op_ctxs.borrow();
+    ctx.iter().map(|o| o.decl.name).collect()
   }
 
   /// Executes traditional JavaScript code (traditional = not ES modules).
@@ -1619,7 +1618,8 @@ impl JsRuntime {
 
     let realm = &self.inner.main_realm;
     let modules = &realm.0.module_map;
-    let exception_state = &modules.exception_state;
+    let context_state = &realm.0.context_state;
+    let exception_state = &context_state.exception_state;
 
     modules.poll_progress(cx, scope)?;
 
@@ -1630,7 +1630,7 @@ impl JsRuntime {
     let dispatched_ops = Self::do_js_event_loop_tick_realm(
       cx,
       scope,
-      &realm.0.context_state,
+      context_state,
       exception_state,
     )?;
     exception_state.check_exception_condition(scope)?;
@@ -1647,13 +1647,8 @@ impl JsRuntime {
     }
 
     // Get the pending state from the main realm, or all realms
-    let pending_state = {
-      EventLoopPendingState::new(
-        scope,
-        &realm.0.context_state.borrow(),
-        modules,
-      )
-    };
+    let pending_state =
+      EventLoopPendingState::new(scope, context_state, modules);
 
     if !pending_state.is_pending() && !maybe_scheduling {
       if has_inspector {
@@ -1862,14 +1857,14 @@ impl EventLoopPendingState {
     state: &ContextState,
     modules: &ModuleMap,
   ) -> Self {
-    let num_unrefed_ops = state.unrefed_ops.len();
-    let num_pending_ops = state.pending_ops.len();
+    let num_unrefed_ops = state.unrefed_ops.borrow().len();
+    let num_pending_ops = state.pending_ops.borrow().len();
     let has_pending_tasks = state.task_spawner_factory.has_pending_tasks();
     let has_pending_dyn_imports = modules.has_pending_dynamic_imports();
     let has_pending_dyn_module_evaluation =
       modules.has_pending_dyn_module_evaluation();
     let has_pending_module_evaluation = modules.has_pending_module_evaluation();
-    let has_pending_promise_rejections = !modules
+    let has_pending_promise_rejections = !state
       .exception_state
       .pending_promise_rejections
       .borrow()
@@ -1881,7 +1876,7 @@ impl EventLoopPendingState {
       has_pending_dyn_module_evaluation,
       has_pending_module_evaluation,
       has_pending_background_tasks: scope.has_pending_background_tasks(),
-      has_tick_scheduled: state.has_next_tick_scheduled,
+      has_tick_scheduled: state.has_next_tick_scheduled.get(),
       has_pending_promise_rejections,
     }
   }
@@ -1890,7 +1885,6 @@ impl EventLoopPendingState {
   pub fn new_from_scope(scope: &mut v8::HandleScope) -> Self {
     let module_map = JsRealm::module_map_from(scope);
     let context_state = JsRealm::state_from_scope(scope);
-    let context_state = context_state.borrow();
     Self::new(scope, &context_state, &module_map)
   }
 
@@ -2031,14 +2025,14 @@ impl JsRuntime {
   fn do_js_event_loop_tick_realm(
     cx: &mut Context,
     scope: &mut v8::HandleScope,
-    context_state: &RefCell<ContextState>,
+    context_state: &ContextState,
     exception_state: &ExceptionState,
   ) -> Result<bool, Error> {
     let mut dispatched_ops = false;
 
     // Poll any pending task spawner tasks. Note that we need to poll separately because otherwise
     // Rust will extend the lifetime of the borrow longer than we expect.
-    let tasks = context_state.borrow().task_spawner_factory.poll_inner(cx);
+    let tasks = context_state.task_spawner_factory.poll_inner(cx);
     if let Poll::Ready(tasks) = tasks {
       // TODO(mmastrac): we are using this flag
       dispatched_ops = true;
@@ -2047,8 +2041,6 @@ impl JsRuntime {
       }
     }
 
-    let mut context_state = context_state.borrow_mut();
-
     // We return async responses to JS in unbounded batches (may change),
     // each batch is a flat vector of tuples:
     // `[promise_id1, op_result1, promise_id2, op_result2, ...]`
@@ -2063,13 +2055,14 @@ impl JsRuntime {
       SmallVec::with_capacity(32);
 
     loop {
-      let Poll::Ready(item) = context_state.pending_ops.poll_join_next(cx)
+      let Poll::Ready(item) =
+        context_state.pending_ops.borrow_mut().poll_join_next(cx)
       else {
         break;
       };
       // TODO(mmastrac): If this task is really errored, things could be pretty bad
       let PendingOp(promise_id, op_id, resp, metrics_event) = item.unwrap();
-      context_state.unrefed_ops.remove(&promise_id);
+      context_state.unrefed_ops.borrow_mut().remove(&promise_id);
       dispatched_ops |= true;
       args.push(v8::Integer::new(scope, promise_id).into());
       let was_error = matches!(resp, OpResult::Err(_));
@@ -2077,12 +2070,12 @@ impl JsRuntime {
       if metrics_event {
         if res.is_ok() && !was_error {
           dispatch_metrics_async(
-            &context_state.op_ctxs[op_id as usize],
+            &context_state.op_ctxs.borrow()[op_id as usize],
             OpMetricsEvent::CompletedAsync,
           );
         } else {
           dispatch_metrics_async(
-            &context_state.op_ctxs[op_id as usize],
+            &context_state.op_ctxs.borrow()[op_id as usize],
             OpMetricsEvent::ErrorAsync,
           );
         }
@@ -2096,7 +2089,7 @@ impl JsRuntime {
     }
 
     let undefined: v8::Local<v8::Value> = v8::undefined(scope).into();
-    let has_tick_scheduled = context_state.has_next_tick_scheduled;
+    let has_tick_scheduled = context_state.has_next_tick_scheduled.get();
     dispatched_ops |= has_tick_scheduled;
 
     let rejections = if !exception_state
@@ -2127,12 +2120,10 @@ impl JsRuntime {
     let has_tick_scheduled = v8::Boolean::new(scope, has_tick_scheduled);
     args.push(has_tick_scheduled.into());
 
-    let js_event_loop_tick_cb_handle =
-      context_state.js_event_loop_tick_cb.clone().unwrap();
     let tc_scope = &mut v8::TryCatch::new(scope);
-    let js_event_loop_tick_cb = js_event_loop_tick_cb_handle.open(tc_scope);
-
-    drop(context_state);
+    let js_event_loop_tick_cb = context_state.js_event_loop_tick_cb.borrow();
+    let js_event_loop_tick_cb =
+      js_event_loop_tick_cb.as_ref().unwrap().open(tc_scope);
 
     js_event_loop_tick_cb.call(tc_scope, undefined, args.as_slice());
 
diff --git a/core/runtime/ops.rs b/core/runtime/ops.rs
index 03102e2..17bc000 100644
--- a/core/runtime/ops.rs
+++ b/core/runtime/ops.rs
@@ -47,9 +47,9 @@ pub fn map_async_op_infallible<R: 'static>(
       )
     };
     ctx
-      .context_state
-      .borrow_mut()
+      .context_state()
       .pending_ops
+      .borrow_mut()
       .spawn(op.map(mapper));
     return None;
   }
@@ -72,9 +72,9 @@ pub fn map_async_op_infallible<R: 'static>(
     };
 
   ctx
-    .context_state
-    .borrow_mut()
+    .context_state()
     .pending_ops
+    .borrow_mut()
     .spawn(pinned.map(move |r| {
       PendingOp(
         r.0,
@@ -103,7 +103,7 @@ pub fn map_async_op_fallible<R: 'static, E: Into<Error> + 'static>(
 
   if lazy {
     let get_class = ctx.get_error_class_fn;
-    ctx.context_state.borrow_mut().pending_ops.spawn(op.map(
+    ctx.context_state().pending_ops.borrow_mut().spawn(op.map(
       move |r| match r {
         Ok(v) => PendingOp(
           promise_id,
@@ -140,8 +140,11 @@ pub fn map_async_op_fallible<R: 'static, E: Into<Error> + 'static>(
     };
 
   let get_class = ctx.get_error_class_fn;
-  ctx.context_state.borrow_mut().pending_ops.spawn(pinned.map(
-    move |r| match r.2 {
+  ctx
+    .context_state()
+    .pending_ops
+    .borrow_mut()
+    .spawn(pinned.map(move |r| match r.2 {
       Ok(v) => PendingOp(
         r.0,
         r.1,
@@ -154,8 +157,7 @@ pub fn map_async_op_fallible<R: 'static, E: Into<Error> + 'static>(
         OpResult::Err(OpError::new(get_class, err.into())),
         metrics,
       ),
-    },
-  ));
+    }));
   None
 }
 
diff --git a/ops/op2/README.md b/ops/op2/README.md
index 03b3f62..42d5f30 100644
--- a/ops/op2/README.md
+++ b/ops/op2/README.md
@@ -1086,20 +1086,6 @@ Only usable in `deno_core`.
 <tr>
 <td>
 
-```text
-Rc<JsRuntimeState>
-```
-
-</td><td>
-✅
-</td><td>
-
-</td><td>
-Only usable in `deno_core`.
-</td></tr>
-<tr>
-<td>
-
 ```text
 *mut v8::Isolate
 ```
diff --git a/ops/op2/dispatch_fast.rs b/ops/op2/dispatch_fast.rs
index 2b7f28b..ed4697a 100644
--- a/ops/op2/dispatch_fast.rs
+++ b/ops/op2/dispatch_fast.rs
@@ -431,7 +431,7 @@ pub(crate) fn generate_dispatch_fast(
   let with_js_runtime_state = if generator_state.needs_fast_js_runtime_state {
     generator_state.needs_fast_opctx = true;
     gs_quote!(generator_state(js_runtime_state, opctx) => {
-      let #js_runtime_state = std::rc::Weak::upgrade(&#opctx.runtime_state).unwrap();
+      let #js_runtime_state = &#opctx.runtime_state();
     })
   } else {
     quote!()
@@ -622,10 +622,6 @@ fn map_v8_fastcall_arg_to_arg(
       *needs_js_runtime_state = true;
       quote!(let #arg_ident = &#js_runtime_state;)
     }
-    Arg::Rc(Special::JsRuntimeState) => {
-      *needs_js_runtime_state = true;
-      quote!(let #arg_ident = #js_runtime_state.clone();)
-    }
     Arg::State(RefType::Ref, state) => {
       *needs_opctx = true;
       let state =
diff --git a/ops/op2/dispatch_slow.rs b/ops/op2/dispatch_slow.rs
index fa4ad12..9af65b2 100644
--- a/ops/op2/dispatch_slow.rs
+++ b/ops/op2/dispatch_slow.rs
@@ -232,7 +232,7 @@ pub(crate) fn with_js_runtime_state(
 ) -> TokenStream {
   generator_state.needs_opctx = true;
   gs_quote!(generator_state(opctx, js_runtime_state) =>
-    (let #js_runtime_state = std::rc::Weak::upgrade(&#opctx.runtime_state).unwrap();)
+    (let #js_runtime_state = &#opctx.runtime_state();)
   )
 }
 
@@ -430,10 +430,6 @@ pub fn from_arg(
       *needs_js_runtime_state = true;
       quote!(let #arg_ident = &#js_runtime_state;)
     }
-    Arg::Rc(Special::JsRuntimeState) => {
-      *needs_js_runtime_state = true;
-      quote!(let #arg_ident = #js_runtime_state.clone();)
-    }
     Arg::State(RefType::Ref, state) => {
       *needs_opstate = true;
       let state =
diff --git a/ops/op2/valid_args.md b/ops/op2/valid_args.md
index 0553ad5..3f35974 100644
--- a/ops/op2/valid_args.md
+++ b/ops/op2/valid_args.md
@@ -67,5 +67,4 @@
 | X         | #[state] &StateObject               | X        |                                                     | Extracts an object from `OpState`.                                                                                                                                                                             |
 | X         | #[state] &mut StateObject           | X        |                                                     | Extracts an object from `OpState`.                                                                                                                                                                             |
 | X         | &JsRuntimeState                     | X        |                                                     | Only usable in `deno_core`.                                                                                                                                                                                    |
-| X         | Rc<JsRuntimeState>                  | X        |                                                     | Only usable in `deno_core`.                                                                                                                                                                                    |
 | X         | *mut v8::Isolate                    | X        |                                                     | ⚠️ Extremely dangerous, may crash if you don't use `nofast` depending on what you do.                                                                                                                           |


Test patch:
diff --git a/core/runtime/tests/misc.rs b/core/runtime/tests/misc.rs
index d721334..fc0d91b 100644
--- a/core/runtime/tests/misc.rs
+++ b/core/runtime/tests/misc.rs
@@ -780,8 +780,8 @@ fn test_has_tick_scheduled() {
     .main_realm()
     .0
     .state()
-    .borrow_mut()
-    .has_next_tick_scheduled = false;
+    .has_next_tick_scheduled
+    .take();
   assert!(matches!(
     runtime.poll_event_loop(cx, false),
     Poll::Ready(Ok(()))