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}