/
githubmirror
/
deno
Обзор
Документация
Войти
/
githubmirror
/
deno
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
libs/core/runtime/setup.rs
307 строк
10 KB
Haruto Tanaka
fix(lsp): fix CPU busy loop by creating tokio runtime before JsRuntime in TSC thread (#35595)
06 июл 2026, 16:58
Не верифицирован
06 июл 2026, 16:58
ac8677d
Код
Авторство
О чём код?
// Copyright 2018-2026 the Deno authors. MIT license. use std::borrow::Cow; use std::collections::HashMap; use std::ffi::c_void; use std::sync::Mutex; use std::sync::Once; use std::sync::atomic::AtomicBool; use std::sync::atomic::Ordering; use std::time::Duration; use futures::task::AtomicWaker; use super::bindings; use super::snapshot; use super::snapshot::V8Snapshot; /// Extract the raw isolate address from an `UnsafeRawIsolatePtr`. /// /// `UnsafeRawIsolatePtr` is `#[repr(transparent)]` over `*mut RealIsolate`, /// so its bit-pattern is a single pointer-sized value. We use transmute /// because the inner field is private. /// /// The compile-time assert below guarantees the layout assumption holds. const _: () = assert!( std::mem::size_of::<v8::UnsafeRawIsolatePtr>() == std::mem::size_of::<usize>() ); pub(crate) fn isolate_ptr_to_key(ptr: v8::UnsafeRawIsolatePtr) -> usize { // SAFETY: UnsafeRawIsolatePtr is #[repr(transparent)] over *mut RealIsolate, // which is pointer-sized. The compile-time assert above guarantees this. unsafe { std::mem::transmute::<v8::UnsafeRawIsolatePtr, usize>(ptr) } } /// Thread-safe queue of V8 foreground tasks, shared between the global /// isolate registry (written by V8 background threads) and the event /// loop (drained on the main thread). Cloning is cheap (Arc). pub type ForegroundTaskQueue = std::sync::Arc<Mutex<Vec<v8::Task>>>; /// Per-isolate state stored in the global registry. Kept minimal: just /// enough for platform callbacks (which only have an isolate pointer) to /// push tasks and wake the event loop. struct IsolateEntry { waker: std::sync::Arc<AtomicWaker>, handle: Option<tokio::runtime::Handle>, tasks: ForegroundTaskQueue, } static ISOLATE_ENTRIES: std::sync::LazyLock< Mutex<HashMap<usize, IsolateEntry>>, > = std::sync::LazyLock::new(|| Mutex::new(HashMap::new())); /// Register an isolate in the global platform registry. The `tasks` /// queue is shared with `JsRuntimeState` so the event loop drains it /// directly without touching the global map. pub fn register_isolate( isolate_ptr: usize, waker: std::sync::Arc<AtomicWaker>, handle: Option<tokio::runtime::Handle>, tasks: ForegroundTaskQueue, ) { let mut map = ISOLATE_ENTRIES.lock().unwrap(); map.insert( isolate_ptr, IsolateEntry { waker, handle, tasks, }, ); } pub fn unregister_isolate(isolate_ptr: usize) { let mut map = ISOLATE_ENTRIES.lock().unwrap(); map.remove(&isolate_ptr); } /// Queue an immediate foreground task and wake the event loop. fn queue_task(key: usize, task: v8::Task) { let map = ISOLATE_ENTRIES.lock().unwrap(); if let Some(entry) = map.get(&key) { entry.tasks.lock().unwrap().push(task); entry.waker.wake(); } } /// Spawn a delayed V8 foreground task on the isolate's tokio runtime. /// After the delay, the task is queued for synchronous draining (not /// run directly on the tokio worker thread). /// /// Aborts the process when the isolate was registered without a tokio /// handle: the delay cannot be honored, queuing the task immediately /// would busy-loop the event loop on any self-rescheduling V8 task /// (e.g. GC memory reducer tasks), and dropping it would lose foreground /// work. Note that a regular `panic!` cannot be used here — this is /// called by V8 from C++ frames that Rust cannot unwind through, which /// would abort anyway but with the panic message swallowed. fn spawn_delayed_task(key: usize, task: v8::Task, delay_in_seconds: f64) { let map = ISOLATE_ENTRIES.lock().unwrap(); let Some(entry) = map.get(&key) else { return }; let Some(handle) = entry.handle.as_ref() else { #[allow(clippy::print_stderr, reason = "printed before aborting")] { eprintln!( "V8 posted a delayed task, but this isolate was created outside of a tokio runtime context and the delay cannot be honored. Enter a tokio runtime (e.g. via `tokio::runtime::Runtime::enter()`) before creating the `JsRuntime`." ); } std::process::abort(); }; let tasks = entry.tasks.clone(); let waker = entry.waker.clone(); handle.spawn(async move { tokio::time::sleep(Duration::from_secs_f64(delay_in_seconds)).await; tasks.lock().unwrap().push(task); waker.wake(); }); } /// Custom V8 platform implementation that queues immediate foreground /// tasks for synchronous draining, and spawns delayed tasks on tokio. struct DenoPlatformImpl; impl v8::PlatformImpl for DenoPlatformImpl { fn post_task(&self, isolate_ptr: *mut c_void, task: v8::Task) { queue_task(isolate_ptr as usize, task); } fn post_non_nestable_task(&self, isolate_ptr: *mut c_void, task: v8::Task) { queue_task(isolate_ptr as usize, task); } fn post_delayed_task( &self, isolate_ptr: *mut c_void, task: v8::Task, delay_in_seconds: f64, ) { spawn_delayed_task(isolate_ptr as usize, task, delay_in_seconds); } fn post_non_nestable_delayed_task( &self, isolate_ptr: *mut c_void, task: v8::Task, delay_in_seconds: f64, ) { spawn_delayed_task(isolate_ptr as usize, task, delay_in_seconds); } fn post_idle_task(&self, _isolate_ptr: *mut c_void, _task: v8::IdleTask) { unreachable!(); } } fn v8_init( v8_platform: Option<v8::SharedRef<v8::Platform>>, snapshot: bool, expose_natives: bool, ) { #[cfg(feature = "include_icu_data")] { v8::icu::set_common_data_77(deno_core_icudata::ICU_DATA).unwrap(); } let base_flags = concat!( " --no-validate-asm", " --turbo_fast_api_calls", " --harmony-temporal", " --js-float16array", " --js-explicit-resource-management", " --js-source-phase-imports", " --js-defer-import-eval", // Installs a native `queueMicrotask` on the global object, letting us // enqueue microtasks from JS without crossing the op boundary. See // `queueMicrotask` in `01_core.js`. " --enable-queue-microtask" ); let snapshot_flags = "--predictable --random-seed=42"; let expose_natives_flags = "--expose_gc --allow_natives_syntax"; let lazy_flags = if cfg!(feature = "snapshot_flags_eager_parse") { "--no-lazy --no-lazy-eval --no-lazy-streaming" } else { "" }; let flags = match (snapshot, expose_natives) { (false, false) => base_flags.to_string(), (true, false) => { format!("{base_flags} {snapshot_flags} {lazy_flags}") } (false, true) => format!("{base_flags} {expose_natives_flags}"), (true, true) => { format!( "{base_flags} {snapshot_flags} {lazy_flags} {expose_natives_flags}" ) } }; v8::V8::set_flags_from_string(&flags); let v8_platform = v8_platform.unwrap_or_else(|| { let unprotected = cfg!(any(test, feature = "unsafe_use_unprotected_platform")); // Cap V8 platform thread pool to 4 threads (like Node.js). // Using all available cores (the default when 0 is passed) wastes // memory for workloads that rarely use background V8 tasks. let thread_pool_size = std::cmp::min( std::thread::available_parallelism() .map(|n| n.get() as u32) .unwrap_or(4), 4, ); v8::new_custom_platform( thread_pool_size, false, unprotected, DenoPlatformImpl, ) .make_shared() }); v8::V8::initialize_platform(v8_platform.clone()); v8::V8::initialize(); } pub fn init_v8( v8_platform: Option<v8::SharedRef<v8::Platform>>, snapshot: bool, expose_natives: bool, ) { static DENO_INIT: Once = Once::new(); static DENO_SNAPSHOT: AtomicBool = AtomicBool::new(false); static DENO_SNAPSHOT_SET: AtomicBool = AtomicBool::new(false); if DENO_SNAPSHOT_SET.load(Ordering::SeqCst) { let current = DENO_SNAPSHOT.load(Ordering::SeqCst); assert_eq!( current, snapshot, "V8 may only be initialized once in either snapshotting or non-snapshotting mode. Either snapshotting or non-snapshotting mode may be used in a single process, not both." ); DENO_SNAPSHOT_SET.store(true, Ordering::SeqCst); DENO_SNAPSHOT.store(snapshot, Ordering::SeqCst); } DENO_INIT.call_once(move || v8_init(v8_platform, snapshot, expose_natives)); } pub fn create_isolate( will_snapshot: bool, maybe_create_params: Option<v8::CreateParams>, maybe_startup_snapshot: Option<V8Snapshot>, external_refs: Cow<'static, [v8::ExternalReference]>, ) -> v8::OwnedIsolate { let mut params = maybe_create_params.unwrap_or_default(); let mut isolate = if will_snapshot { snapshot::create_snapshot_creator( external_refs, maybe_startup_snapshot, params, ) } else { params = params.external_references(external_refs); let has_snapshot = maybe_startup_snapshot.is_some(); if let Some(snapshot) = maybe_startup_snapshot { params = params.snapshot_blob(v8::StartupData::from(snapshot.0)); } static FIRST_SNAPSHOT_INIT: AtomicBool = AtomicBool::new(false); static SNAPSHOW_INIT_MUT: Mutex<()> = Mutex::new(()); // On Windows, the snapshot deserialization code appears to be crashing and we are not // certain of the reason. We take a mutex the first time an isolate with a snapshot to // prevent this. https://github.com/denoland/deno/issues/15590 if cfg!(windows) && has_snapshot && FIRST_SNAPSHOT_INIT.load(Ordering::SeqCst) { let _g = SNAPSHOW_INIT_MUT.lock().unwrap(); let res = v8::Isolate::new(params); FIRST_SNAPSHOT_INIT.store(true, Ordering::SeqCst); res } else { v8::Isolate::new(params) } }; isolate.set_microtasks_policy(v8::MicrotasksPolicy::Explicit); isolate.set_capture_stack_trace_for_uncaught_exceptions(true, 10); isolate.set_promise_reject_callback(bindings::promise_reject_callback); isolate.set_prepare_stack_trace_callback( crate::error::prepare_stack_trace_callback, ); isolate.set_host_initialize_import_meta_object_callback( bindings::host_initialize_import_meta_object_callback, ); isolate.set_host_import_module_dynamically_callback( bindings::host_import_module_dynamically_callback, ); isolate.set_host_import_module_with_phase_dynamically_callback( bindings::host_import_module_with_phase_dynamically_callback, ); isolate.set_wasm_async_resolve_promise_callback( bindings::wasm_async_resolve_promise_callback, ); isolate.set_wasm_streaming_callback( crate::ops_builtin_v8::wasm_streaming_callback, ); isolate }