Skip to main content

v8/
platform.rs

1use crate::Isolate;
2use crate::isolate::RealIsolate;
3use crate::support::int;
4
5use crate::support::Opaque;
6use crate::support::Shared;
7use crate::support::SharedPtrBase;
8use crate::support::SharedRef;
9use crate::support::UniquePtr;
10use crate::support::UniqueRef;
11use crate::support::long;
12
13unsafe extern "C" {
14  fn v8__Platform__NewDefaultPlatform(
15    thread_pool_size: int,
16    idle_task_support: bool,
17  ) -> *mut Platform;
18  fn v8__Platform__NewUnprotectedDefaultPlatform(
19    thread_pool_size: int,
20    idle_task_support: bool,
21  ) -> *mut Platform;
22  fn v8__Platform__NewSingleThreadedDefaultPlatform(
23    idle_task_support: bool,
24  ) -> *mut Platform;
25  fn v8__Platform__NewCustomPlatform(
26    thread_pool_size: int,
27    idle_task_support: bool,
28    unprotected: bool,
29    context: *mut std::ffi::c_void,
30  ) -> *mut Platform;
31  fn v8__Platform__DELETE(this: *mut Platform);
32
33  fn v8__Platform__PumpMessageLoop(
34    platform: *mut Platform,
35    isolate: *mut RealIsolate,
36    wait_for_work: bool,
37  ) -> bool;
38
39  fn v8__Platform__RunIdleTasks(
40    platform: *mut Platform,
41    isolate: *mut RealIsolate,
42    idle_time_in_seconds: f64,
43  );
44
45  fn v8__Platform__NotifyIsolateShutdown(
46    platform: *mut Platform,
47    isolate: *mut RealIsolate,
48  );
49
50  fn v8__Task__Run(task: *mut std::ffi::c_void);
51  fn v8__Task__DELETE(task: *mut std::ffi::c_void);
52  fn v8__IdleTask__Run(task: *mut std::ffi::c_void, deadline_in_seconds: f64);
53  fn v8__IdleTask__DELETE(task: *mut std::ffi::c_void);
54
55  fn std__shared_ptr__v8__Platform__CONVERT__std__unique_ptr(
56    unique_ptr: UniquePtr<Platform>,
57  ) -> SharedPtrBase<Platform>;
58  fn std__shared_ptr__v8__Platform__get(
59    ptr: *const SharedPtrBase<Platform>,
60  ) -> *mut Platform;
61  fn std__shared_ptr__v8__Platform__COPY(
62    ptr: *const SharedPtrBase<Platform>,
63  ) -> SharedPtrBase<Platform>;
64  fn std__shared_ptr__v8__Platform__reset(ptr: *mut SharedPtrBase<Platform>);
65  fn std__shared_ptr__v8__Platform__use_count(
66    ptr: *const SharedPtrBase<Platform>,
67  ) -> long;
68}
69
70#[repr(C)]
71#[derive(Debug)]
72pub struct Platform(Opaque);
73
74/// A V8 foreground task. Ownership is transferred from C++ to Rust when
75/// V8 posts a task via [`PlatformImpl`] trait methods.
76///
77/// The embedder is responsible for scheduling the task and calling
78/// [`run()`](Task::run). For example, in an async runtime like tokio:
79///
80/// ```ignore
81/// tokio::spawn(async move { task.run() });
82/// ```
83///
84/// If dropped without calling `run()`, the task is destroyed without
85/// executing.
86pub struct Task(*mut std::ffi::c_void);
87
88// SAFETY: V8 tasks are designed to be posted from background threads and
89// run on the isolate's foreground thread. The unique_ptr transfer is safe
90// across thread boundaries.
91unsafe impl Send for Task {}
92
93impl Task {
94  /// Run the task. Consumes self to prevent double execution.
95  pub fn run(self) {
96    let ptr = self.0;
97    // Prevent Drop from deleting — we'll delete after Run.
98    std::mem::forget(self);
99    unsafe {
100      v8__Task__Run(ptr);
101      v8__Task__DELETE(ptr);
102    }
103  }
104}
105
106impl Drop for Task {
107  fn drop(&mut self) {
108    unsafe { v8__Task__DELETE(self.0) };
109  }
110}
111
112/// A V8 idle task. Similar to [`Task`] but accepts a deadline parameter
113/// when run.
114///
115/// If dropped without calling `run()`, the task is destroyed without
116/// executing.
117pub struct IdleTask(*mut std::ffi::c_void);
118
119// SAFETY: Same as Task — safe to transfer across threads.
120unsafe impl Send for IdleTask {}
121
122impl IdleTask {
123  /// Run the idle task with the given deadline. Consumes self.
124  ///
125  /// `deadline_in_seconds` is the absolute time (in seconds since some
126  /// epoch) by which the idle task should complete.
127  pub fn run(self, deadline_in_seconds: f64) {
128    let ptr = self.0;
129    std::mem::forget(self);
130    unsafe {
131      v8__IdleTask__Run(ptr, deadline_in_seconds);
132      v8__IdleTask__DELETE(ptr);
133    }
134  }
135}
136
137impl Drop for IdleTask {
138  fn drop(&mut self) {
139    unsafe { v8__IdleTask__DELETE(self.0) };
140  }
141}
142
143/// Trait for customizing platform behavior, following the same pattern as
144/// [`V8InspectorClientImpl`](crate::inspector::V8InspectorClientImpl).
145///
146/// Implement this trait to receive V8 foreground tasks and schedule them
147/// on your event loop. The C++ `CustomPlatform` wraps each isolate's
148/// `TaskRunner` so that every `PostTask` / `PostDelayedTask` / etc. call
149/// transfers task ownership to Rust through the corresponding trait method.
150///
151/// **The embedder is responsible for calling [`Task::run()`] on the
152/// isolate's thread.** For example, using tokio:
153///
154/// ```ignore
155/// fn post_task(&self, isolate_ptr: *mut c_void, task: Task) {
156///     tokio::spawn(async move { task.run() });
157/// }
158///
159/// fn post_delayed_task(&self, isolate_ptr: *mut c_void, task: Task, delay: f64) {
160///     tokio::spawn(async move {
161///         tokio::time::sleep(Duration::from_secs_f64(delay)).await;
162///         task.run();
163///     });
164/// }
165/// ```
166///
167/// All methods have default implementations that run the task immediately
168/// (synchronously). Override to integrate with your event loop.
169///
170/// Implementations must be `Send + Sync` as callbacks may fire from any
171/// thread.
172#[allow(unused_variables)]
173pub trait PlatformImpl: Send + Sync {
174  /// Called when `TaskRunner::PostTask` is invoked for the given isolate.
175  ///
176  /// The [`Task`] must be run on the isolate's foreground thread by calling
177  /// [`Task::run()`].
178  ///
179  /// May be called from ANY thread (V8 background threads, etc.).
180  fn post_task(&self, isolate_ptr: *mut std::ffi::c_void, task: Task) {
181    task.run();
182  }
183
184  /// Called when `TaskRunner::PostNonNestableTask` is invoked.
185  ///
186  /// Same semantics as [`post_task`](Self::post_task), but the task must
187  /// not be run within a nested `PumpMessageLoop`.
188  fn post_non_nestable_task(
189    &self,
190    isolate_ptr: *mut std::ffi::c_void,
191    task: Task,
192  ) {
193    task.run();
194  }
195
196  /// Called when `TaskRunner::PostDelayedTask` is invoked.
197  ///
198  /// The task should be run after `delay_in_seconds` has elapsed.
199  /// For example, using `tokio::time::sleep` or a timer wheel.
200  ///
201  /// May be called from ANY thread.
202  fn post_delayed_task(
203    &self,
204    isolate_ptr: *mut std::ffi::c_void,
205    task: Task,
206    delay_in_seconds: f64,
207  ) {
208    task.run();
209  }
210
211  /// Called when `TaskRunner::PostNonNestableDelayedTask` is invoked.
212  ///
213  /// Same semantics as [`post_delayed_task`](Self::post_delayed_task).
214  fn post_non_nestable_delayed_task(
215    &self,
216    isolate_ptr: *mut std::ffi::c_void,
217    task: Task,
218    delay_in_seconds: f64,
219  ) {
220    task.run();
221  }
222
223  /// Called when `TaskRunner::PostIdleTask` is invoked.
224  ///
225  /// The [`IdleTask`] should be run when the embedder has idle time,
226  /// passing the deadline via [`IdleTask::run(deadline)`](IdleTask::run).
227  fn post_idle_task(&self, isolate_ptr: *mut std::ffi::c_void, task: IdleTask) {
228    task.run(0.0);
229  }
230}
231
232// FFI callbacks called from C++ CustomPlatform/CustomTaskRunner.
233// `context` is a raw pointer to a `Box<dyn PlatformImpl>`.
234// Task pointers are owned — Rust is responsible for running and deleting them.
235
236#[unsafe(no_mangle)]
237unsafe extern "C" fn v8__Platform__CustomPlatform__BASE__PostTask(
238  context: *mut std::ffi::c_void,
239  isolate: *mut std::ffi::c_void,
240  task: *mut std::ffi::c_void,
241) {
242  let imp = unsafe { &*(context as *const Box<dyn PlatformImpl>) };
243  imp.post_task(isolate, Task(task));
244}
245
246#[unsafe(no_mangle)]
247unsafe extern "C" fn v8__Platform__CustomPlatform__BASE__PostNonNestableTask(
248  context: *mut std::ffi::c_void,
249  isolate: *mut std::ffi::c_void,
250  task: *mut std::ffi::c_void,
251) {
252  let imp = unsafe { &*(context as *const Box<dyn PlatformImpl>) };
253  imp.post_non_nestable_task(isolate, Task(task));
254}
255
256#[unsafe(no_mangle)]
257unsafe extern "C" fn v8__Platform__CustomPlatform__BASE__PostDelayedTask(
258  context: *mut std::ffi::c_void,
259  isolate: *mut std::ffi::c_void,
260  task: *mut std::ffi::c_void,
261  delay_in_seconds: f64,
262) {
263  let imp = unsafe { &*(context as *const Box<dyn PlatformImpl>) };
264  imp.post_delayed_task(isolate, Task(task), delay_in_seconds);
265}
266
267#[unsafe(no_mangle)]
268unsafe extern "C" fn v8__Platform__CustomPlatform__BASE__PostNonNestableDelayedTask(
269  context: *mut std::ffi::c_void,
270  isolate: *mut std::ffi::c_void,
271  task: *mut std::ffi::c_void,
272  delay_in_seconds: f64,
273) {
274  let imp = unsafe { &*(context as *const Box<dyn PlatformImpl>) };
275  imp.post_non_nestable_delayed_task(isolate, Task(task), delay_in_seconds);
276}
277
278#[unsafe(no_mangle)]
279unsafe extern "C" fn v8__Platform__CustomPlatform__BASE__PostIdleTask(
280  context: *mut std::ffi::c_void,
281  isolate: *mut std::ffi::c_void,
282  task: *mut std::ffi::c_void,
283) {
284  let imp = unsafe { &*(context as *const Box<dyn PlatformImpl>) };
285  imp.post_idle_task(isolate, IdleTask(task));
286}
287
288#[unsafe(no_mangle)]
289unsafe extern "C" fn v8__Platform__CustomPlatform__BASE__DROP(
290  context: *mut std::ffi::c_void,
291) {
292  unsafe {
293    let _ = Box::from_raw(context as *mut Box<dyn PlatformImpl>);
294  }
295}
296
297/// Returns a new instance of the default v8::Platform implementation.
298///
299/// |thread_pool_size| is the number of worker threads to allocate for
300/// background jobs. If a value of zero is passed, a suitable default
301/// based on the current number of processors online will be chosen.
302/// If |idle_task_support| is enabled then the platform will accept idle
303/// tasks (IdleTasksEnabled will return true) and will rely on the embedder
304/// calling v8::platform::RunIdleTasks to process the idle tasks.
305///
306/// The default platform for v8 may include restrictions and caveats on thread
307/// creation and initialization. This platform should only be used in cases
308/// where v8 can be reliably initialized on the application's main thread, or
309/// the parent thread to all threads in the system that will use v8.
310///
311/// One example of a restriction is the use of Memory Protection Keys (pkeys) on
312/// modern Linux systems using modern Intel/AMD processors. This particular
313/// technology requires that all threads using v8 are created as descendent
314/// threads of the thread that called `v8::Initialize`.
315#[inline(always)]
316pub fn new_default_platform(
317  thread_pool_size: u32,
318  idle_task_support: bool,
319) -> UniqueRef<Platform> {
320  Platform::new(thread_pool_size, idle_task_support)
321}
322
323/// Creates a platform that is identical to the default platform, but does not
324/// enforce thread-isolated allocations. This may reduce security in some cases,
325/// so this method should be used with caution in cases where the threading
326/// guarantees of `new_default_platform` cannot be upheld (generally for tests).
327#[inline(always)]
328pub fn new_unprotected_default_platform(
329  thread_pool_size: u32,
330  idle_task_support: bool,
331) -> UniqueRef<Platform> {
332  Platform::new_unprotected(thread_pool_size, idle_task_support)
333}
334
335/// The same as new_default_platform() but disables the worker thread pool.
336/// It must be used with the --single-threaded V8 flag.
337///
338/// If |idle_task_support| is enabled then the platform will accept idle
339/// tasks (IdleTasksEnabled will return true) and will rely on the embedder
340/// calling v8::platform::RunIdleTasks to process the idle tasks.
341#[inline(always)]
342pub fn new_single_threaded_default_platform(
343  idle_task_support: bool,
344) -> UniqueRef<Platform> {
345  Platform::new_single_threaded(idle_task_support)
346}
347
348/// Creates a custom platform backed by `DefaultPlatform` that transfers
349/// foreground task ownership to the provided [`PlatformImpl`] trait object.
350///
351/// Unlike the default platform, foreground tasks are NOT queued internally.
352/// Instead, each `PostTask` / `PostDelayedTask` / etc. call transfers the
353/// [`Task`] to Rust via the trait. The embedder is responsible for
354/// scheduling and calling [`Task::run()`] on the isolate's thread.
355///
356/// Background tasks (thread pool) are still handled by `DefaultPlatform`.
357///
358/// When `unprotected` is true, thread-isolated allocations are disabled
359/// (same as `new_unprotected_default_platform`). This is required when
360/// isolates may be created on threads other than the one that called
361/// `V8::initialize`.
362#[inline(always)]
363pub fn new_custom_platform(
364  thread_pool_size: u32,
365  idle_task_support: bool,
366  unprotected: bool,
367  platform_impl: impl PlatformImpl + 'static,
368) -> UniqueRef<Platform> {
369  Platform::new_custom(
370    thread_pool_size,
371    idle_task_support,
372    unprotected,
373    platform_impl,
374  )
375}
376
377impl Platform {
378  /// Returns a new instance of the default v8::Platform implementation.
379  ///
380  /// |thread_pool_size| is the number of worker threads to allocate for
381  /// background jobs. If a value of zero is passed, a suitable default
382  /// based on the current number of processors online will be chosen.
383  /// If |idle_task_support| is enabled then the platform will accept idle
384  /// tasks (IdleTasksEnabled will return true) and will rely on the embedder
385  /// calling v8::platform::RunIdleTasks to process the idle tasks.
386  ///
387  /// The default platform for v8 may include restrictions and caveats on thread
388  /// creation and initialization. This platform should only be used in cases
389  /// where v8 can be reliably initialized on the application's main thread, or
390  /// the parent thread to all threads in the system that will use v8.
391  ///
392  /// One example of a restriction is the use of Memory Protection Keys (pkeys)
393  /// on modern Linux systems using modern Intel/AMD processors. This particular
394  /// technology requires that all threads using v8 are created as descendent
395  /// threads of the thread that called `v8::Initialize`.
396  #[inline(always)]
397  pub fn new(
398    thread_pool_size: u32,
399    idle_task_support: bool,
400  ) -> UniqueRef<Self> {
401    unsafe {
402      UniqueRef::from_raw(v8__Platform__NewDefaultPlatform(
403        thread_pool_size.min(16) as i32,
404        idle_task_support,
405      ))
406    }
407  }
408
409  /// Creates a platform that is identical to the default platform, but does not
410  /// enforce thread-isolated allocations. This may reduce security in some
411  /// cases, so this method should be used with caution in cases where the
412  /// threading guarantees of `new_default_platform` cannot be upheld (generally
413  /// for tests).
414  #[inline(always)]
415  pub fn new_unprotected(
416    thread_pool_size: u32,
417    idle_task_support: bool,
418  ) -> UniqueRef<Self> {
419    unsafe {
420      UniqueRef::from_raw(v8__Platform__NewUnprotectedDefaultPlatform(
421        thread_pool_size.min(16) as i32,
422        idle_task_support,
423      ))
424    }
425  }
426
427  /// The same as new() but disables the worker thread pool.
428  /// It must be used with the --single-threaded V8 flag.
429  ///
430  /// If |idle_task_support| is enabled then the platform will accept idle
431  /// tasks (IdleTasksEnabled will return true) and will rely on the embedder
432  /// calling v8::platform::RunIdleTasks to process the idle tasks.
433  #[inline(always)]
434  pub fn new_single_threaded(idle_task_support: bool) -> UniqueRef<Self> {
435    unsafe {
436      UniqueRef::from_raw(v8__Platform__NewSingleThreadedDefaultPlatform(
437        idle_task_support,
438      ))
439    }
440  }
441
442  /// Creates a custom platform that transfers foreground task ownership to
443  /// the provided [`PlatformImpl`] trait object.
444  ///
445  /// See [`new_custom_platform`] for details.
446  ///
447  /// The trait object is owned by the platform and will be dropped when the
448  /// platform is destroyed.
449  #[inline(always)]
450  pub fn new_custom(
451    thread_pool_size: u32,
452    idle_task_support: bool,
453    unprotected: bool,
454    platform_impl: impl PlatformImpl + 'static,
455  ) -> UniqueRef<Self> {
456    // Double-box: inner Box<dyn> is a fat pointer, outer Box gives us a
457    // thin pointer we can pass through C++ void*.
458    let boxed: Box<dyn PlatformImpl> = Box::new(platform_impl);
459    let context = Box::into_raw(Box::new(boxed)) as *mut std::ffi::c_void;
460    // thread_pool_size clamping (0 → hardware_concurrency, max 16) is
461    // handled on the C++ side in v8__Platform__NewCustomPlatform.
462    unsafe {
463      UniqueRef::from_raw(v8__Platform__NewCustomPlatform(
464        thread_pool_size as i32,
465        idle_task_support,
466        unprotected,
467        context,
468      ))
469    }
470  }
471}
472
473impl Platform {
474  /// Pumps the message loop for the given isolate.
475  ///
476  /// The caller has to make sure that this is called from the right thread.
477  /// Returns true if a task was executed, and false otherwise. If the call to
478  /// PumpMessageLoop is nested within another call to PumpMessageLoop, only
479  /// nestable tasks may run. Otherwise, any task may run. Unless requested through
480  /// the |wait_for_work| parameter, this call does not block if no task is pending.
481  #[inline(always)]
482  pub fn pump_message_loop(
483    platform: &SharedRef<Self>,
484    isolate: &Isolate,
485    wait_for_work: bool,
486  ) -> bool {
487    unsafe {
488      v8__Platform__PumpMessageLoop(
489        &**platform as *const Self as *mut _,
490        isolate.as_real_ptr(),
491        wait_for_work,
492      )
493    }
494  }
495
496  /// Runs pending idle tasks for at most |idle_time_in_seconds| seconds.
497  ///
498  /// The caller has to make sure that this is called from the right thread.
499  /// This call does not block if no task is pending.
500  #[inline(always)]
501  pub fn run_idle_tasks(
502    platform: &SharedRef<Self>,
503    isolate: &Isolate,
504    idle_time_in_seconds: f64,
505  ) {
506    unsafe {
507      v8__Platform__RunIdleTasks(
508        &**platform as *const Self as *mut _,
509        isolate.as_real_ptr(),
510        idle_time_in_seconds,
511      );
512    }
513  }
514
515  /// Notifies the given platform about the Isolate getting deleted soon. Has to
516  /// be called for all Isolates which are deleted - unless we're shutting down
517  /// the platform.
518  ///
519  /// The |platform| has to be created using |NewDefaultPlatform|.
520  #[inline(always)]
521  pub(crate) unsafe fn notify_isolate_shutdown(
522    platform: &SharedRef<Self>,
523    isolate: &Isolate,
524  ) {
525    unsafe {
526      v8__Platform__NotifyIsolateShutdown(
527        &**platform as *const Self as *mut _,
528        isolate.as_real_ptr(),
529      );
530    }
531  }
532}
533
534impl Shared for Platform {
535  fn from_unique_ptr(unique_ptr: UniquePtr<Self>) -> SharedPtrBase<Self> {
536    unsafe {
537      std__shared_ptr__v8__Platform__CONVERT__std__unique_ptr(unique_ptr)
538    }
539  }
540  fn get(ptr: &SharedPtrBase<Self>) -> *const Self {
541    unsafe { std__shared_ptr__v8__Platform__get(ptr) }
542  }
543  fn clone(ptr: &SharedPtrBase<Self>) -> SharedPtrBase<Self> {
544    unsafe { std__shared_ptr__v8__Platform__COPY(ptr) }
545  }
546  fn reset(ptr: &mut SharedPtrBase<Self>) {
547    unsafe { std__shared_ptr__v8__Platform__reset(ptr) }
548  }
549  fn use_count(ptr: &SharedPtrBase<Self>) -> long {
550    unsafe { std__shared_ptr__v8__Platform__use_count(ptr) }
551  }
552}
553
554impl Drop for Platform {
555  fn drop(&mut self) {
556    unsafe { v8__Platform__DELETE(self) };
557  }
558}