Skip to content

Commit 7d810ee

Browse files
bartlomiejuclaude
andauthored
feat(core): update v8 to 146.8.0 with foreground task ownership (#32771)
## Summary - Updates the `v8` crate from 146.5.0 to 146.8.0 (includes [rusty_v8#1934](denoland/rusty_v8#1934)) - Updates `DenoPlatformImpl` to receive `v8::Task` / `v8::IdleTask` ownership from the C++ custom platform, instead of tasks being queued in `DefaultPlatform` - Tasks are queued per-isolate and drained/run directly on the event loop, removing the need for `PumpMessageLoop` - Follows up on #32450 by adapting deno_core to the new rusty_v8 task ownership API --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 2b62955 commit 7d810ee

12 files changed

Lines changed: 178 additions & 241 deletions

File tree

Cargo.lock

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@ deno_task_shell = "=0.29.0"
9999
deno_terminal = "=0.2.3"
100100
deno_unsync = { version = "0.4.4", default-features = false }
101101
deno_whoami = "0.1.0"
102-
v8 = { version = "146.5.0", default-features = false }
102+
v8 = { version = "146.8.0", default-features = false }
103103

104104
denokv_proto = "0.13.0"
105105
denokv_remote = "0.13.0"

cli/lsp/tsc.rs

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@ use deno_config::deno_json::CompilerOptions;
2222
use deno_core::JsRuntime;
2323
use deno_core::ModuleSpecifier;
2424
use deno_core::OpState;
25-
use deno_core::PollEventLoopOptions;
2625
use deno_core::RuntimeOptions;
2726
use deno_core::anyhow::anyhow;
2827
use deno_core::convert::Smi;
@@ -5351,10 +5350,7 @@ fn run_tsc_thread(
53515350
.lock()
53525351
.await
53535352
.js_runtime
5354-
.run_event_loop(PollEventLoopOptions {
5355-
wait_for_inspector: false,
5356-
pump_v8_message_loop: true,
5357-
})
5353+
.run_event_loop(Default::default())
53585354
.await
53595355
{
53605356
log::error!("Error in TSC event loop: {e}");

cli/tools/repl/session.rs

Lines changed: 1 addition & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@ use deno_ast::swc::ecma_visit::Visit;
2222
use deno_ast::swc::ecma_visit::VisitWith;
2323
use deno_ast::swc::ecma_visit::noop_visit_type;
2424
use deno_core::LocalInspectorSession;
25-
use deno_core::PollEventLoopOptions;
2625
use deno_core::anyhow::anyhow;
2726
use deno_core::error::AnyError;
2827
use deno_core::error::CoreError;
@@ -426,16 +425,7 @@ impl ReplSession {
426425
self
427426
.worker
428427
.js_runtime
429-
.with_event_loop_future(
430-
fut,
431-
PollEventLoopOptions {
432-
// NOTE(bartlomieju): this is an important bit; we don't want to pump V8
433-
// message loop here, so that GC won't run. Otherwise, the resulting
434-
// object might be GC'ed before we have a chance to inspect it.
435-
pump_v8_message_loop: false,
436-
..Default::default()
437-
},
438-
)
428+
.with_event_loop_future(fut, Default::default())
439429
.await
440430
.unwrap()
441431
}

libs/core/runtime/jsruntime.rs

Lines changed: 42 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -182,7 +182,7 @@ impl InnerIsolateState {
182182

183183
// Unregister isolate waker before dropping the isolate
184184
let isolate_ptr = unsafe { self.v8_isolate.as_raw_isolate_ptr() };
185-
setup::unregister_isolate_waker(setup::isolate_ptr_to_key(isolate_ptr));
185+
setup::unregister_isolate(setup::isolate_ptr_to_key(isolate_ptr));
186186

187187
let state_ptr = self.v8_isolate.get_data(STATE_DATA_OFFSET);
188188
// SAFETY: We are sure that it's a valid pointer for whole lifetime of
@@ -457,7 +457,10 @@ pub struct JsRuntimeState {
457457
pub(crate) function_templates: Rc<RefCell<FunctionTemplateData>>,
458458
pub(crate) callsite_prototype: RefCell<Option<v8::Global<v8::Object>>>,
459459
waker: Arc<AtomicWaker>,
460-
safety_net_active: Arc<std::sync::atomic::AtomicBool>,
460+
/// Foreground V8 tasks queued by the custom platform. Shared with the
461+
/// global isolate registry so background threads can push tasks, while
462+
/// the event loop drains them without touching the global map.
463+
foreground_tasks: setup::ForegroundTaskQueue,
461464
/// Accessed through [`JsRuntimeState::with_inspector`].
462465
inspector: RefCell<Option<Rc<JsRuntimeInspector>>>,
463466
has_inspector: Cell<bool>,
@@ -586,19 +589,9 @@ impl RuntimeOptions {
586589
}
587590
}
588591

589-
#[derive(Copy, Clone, Debug)]
592+
#[derive(Copy, Clone, Debug, Default)]
590593
pub struct PollEventLoopOptions {
591594
pub wait_for_inspector: bool,
592-
pub pump_v8_message_loop: bool,
593-
}
594-
595-
impl Default for PollEventLoopOptions {
596-
fn default() -> Self {
597-
Self {
598-
wait_for_inspector: false,
599-
pump_v8_message_loop: true,
600-
}
601-
}
602595
}
603596

604597
#[derive(Default)]
@@ -785,7 +778,7 @@ impl JsRuntime {
785778
eval_context_set_code_cache_cb,
786779
),
787780
waker: waker.clone(),
788-
safety_net_active: Arc::new(std::sync::atomic::AtomicBool::new(false)),
781+
foreground_tasks: Default::default(),
789782
// Some fields are initialized later after isolate is created
790783
inspector: None.into(),
791784
has_inspector: false.into(),
@@ -862,12 +855,19 @@ impl JsRuntime {
862855

863856
let isolate_ptr = unsafe { isolate.as_raw_isolate_ptr() };
864857

865-
// Register this isolate's waker so the custom platform can wake
866-
// the event loop when V8 posts foreground tasks from background threads.
867-
setup::register_isolate_waker(
868-
setup::isolate_ptr_to_key(isolate_ptr),
869-
waker.clone(),
870-
);
858+
// Register this isolate in the global platform registry so V8 background
859+
// threads can queue foreground tasks. The task queue Arc is already shared
860+
// with JsRuntimeState (created above) so the event loop can drain it
861+
// without touching the global map.
862+
// Not all contexts have a tokio runtime (e.g. snapshot creation, unit tests).
863+
if let Ok(handle) = tokio::runtime::Handle::try_current() {
864+
setup::register_isolate(
865+
setup::isolate_ptr_to_key(isolate_ptr),
866+
waker.clone(),
867+
handle,
868+
state_rc.foreground_tasks.clone(),
869+
);
870+
}
871871

872872
// ...isolate is fully set up, we can forward its pointer to the ops to finish
873873
// their' setup...
@@ -1902,32 +1902,6 @@ impl JsRuntime {
19021902
}
19031903
}
19041904

1905-
fn pump_v8_message_loop(
1906-
&self,
1907-
scope: &mut v8::PinScope,
1908-
) -> Result<(), Box<JsError>> {
1909-
while v8::Platform::pump_message_loop(
1910-
&v8::V8::get_current_platform(),
1911-
scope,
1912-
false, // don't block if there are no tasks
1913-
) {
1914-
// do nothing
1915-
}
1916-
1917-
v8::tc_scope!(let tc_scope, scope);
1918-
1919-
let context_state = JsRealm::state_from_scope(tc_scope);
1920-
if !context_state.has_tick_scheduled() {
1921-
tc_scope.perform_microtask_checkpoint();
1922-
}
1923-
match tc_scope.exception() {
1924-
None => Ok(()),
1925-
Some(exception) => {
1926-
exception_to_err_result(tc_scope, exception, false, true)
1927-
}
1928-
}
1929-
}
1930-
19311905
pub fn maybe_init_inspector(&mut self) {
19321906
let inspector = &mut self.inner.state.inspector.borrow_mut();
19331907
if inspector.is_some() {
@@ -2130,12 +2104,31 @@ impl JsRuntime {
21302104
let has_inspector = self.inner.state.has_inspector.get();
21312105
self.inner.state.waker.register(cx.waker());
21322106

2133-
// Pre-phase: Inspector + V8 message loop pump
2107+
// Pre-phase: Inspector + drain foreground tasks + microtask checkpoint
21342108
if has_inspector {
21352109
self.inspector().poll_sessions_from_event_loop(cx);
21362110
}
2137-
if poll_options.pump_v8_message_loop {
2138-
self.pump_v8_message_loop(scope)?;
2111+
{
2112+
// Drain and run foreground tasks queued by the custom V8 platform.
2113+
// Uses the local Arc shared with the registry — no global map lookup.
2114+
let tasks =
2115+
std::mem::take(&mut *self.inner.state.foreground_tasks.lock().unwrap());
2116+
for task in tasks {
2117+
task.run();
2118+
}
2119+
2120+
v8::tc_scope!(let tc_scope, scope);
2121+
let context_state = JsRealm::state_from_scope(tc_scope);
2122+
if !context_state.has_tick_scheduled() {
2123+
tc_scope.perform_microtask_checkpoint();
2124+
}
2125+
if let Some(exception) = tc_scope.exception() {
2126+
return Poll::Ready(Err(
2127+
exception_to_err_result::<()>(tc_scope, exception, false, true)
2128+
.unwrap_err()
2129+
.into(),
2130+
));
2131+
}
21392132
}
21402133

21412134
let realm = &self.inner.main_realm;
@@ -2291,24 +2284,6 @@ impl JsRuntime {
22912284
scope.perform_microtask_checkpoint();
22922285
}
22932286

2294-
// Safety net: if V8 has pending background tasks (e.g. module compilation),
2295-
// schedule a delayed wake to pump the message loop in case the platform
2296-
// callback was missed due to a race condition.
2297-
if pending_state.has_pending_background_tasks
2298-
&& !self
2299-
.inner
2300-
.state
2301-
.safety_net_active
2302-
.swap(true, std::sync::atomic::Ordering::SeqCst)
2303-
{
2304-
cx.waker().wake_by_ref();
2305-
self
2306-
.inner
2307-
.state
2308-
.safety_net_active
2309-
.store(false, std::sync::atomic::Ordering::SeqCst);
2310-
}
2311-
23122287
// Re-wake logic for next iteration
23132288
#[allow(
23142289
clippy::suspicious_else_formatting,

0 commit comments

Comments
 (0)