style/
global_style_data.rs

1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4
5//! Global style data
6
7use crate::context::StyleSystemOptions;
8#[cfg(feature = "gecko")]
9use crate::gecko_bindings::bindings;
10use crate::parallel::STYLE_THREAD_STACK_SIZE_KB;
11use crate::shared_lock::SharedRwLock;
12use crate::thread_state;
13use parking_lot::{Mutex, RwLock, RwLockReadGuard};
14#[cfg(all(unix, not(target_arch = "wasm32")))]
15use std::os::unix::thread::{JoinHandleExt, RawPthread};
16#[cfg(windows)]
17use std::os::windows::{io::AsRawHandle, prelude::RawHandle};
18use std::{io, sync::LazyLock, thread};
19use thin_vec::ThinVec;
20
21/// Platform-specific handle to a thread.
22#[cfg(all(unix, not(target_arch = "wasm32")))]
23pub type PlatformThreadHandle = RawPthread;
24/// Platform-specific handle to a thread.
25#[cfg(windows)]
26pub type PlatformThreadHandle = RawHandle;
27
28/// A noop thread join handle for wasm
29/// The usize field is a dummy field to make this type non-zero sized so as not to confuse FFI
30#[cfg(all(target_arch = "wasm32", not(feature = "gecko")))]
31pub struct DummyThreadHandle;
32#[cfg(all(target_arch = "wasm32", not(feature = "gecko")))]
33impl DummyThreadHandle {
34    /// A noop thread join method for wasm
35    pub fn join(&self) {
36        // Do nothing
37    }
38}
39#[cfg(all(target_arch = "wasm32", not(feature = "gecko")))]
40/// Platform-specific handle to a thread.
41pub type PlatformThreadHandle = DummyThreadHandle;
42
43/// Global style data
44pub struct GlobalStyleData {
45    /// Shared RWLock for CSSOM objects
46    pub shared_lock: SharedRwLock,
47
48    /// Global style system options determined by env vars.
49    pub options: StyleSystemOptions,
50}
51
52/// Global thread pool.
53pub struct StyleThreadPool {
54    /// How many threads parallel styling can use. If not using a thread pool, this is set to `None`.
55    pub num_threads: Option<usize>,
56
57    /// The parallel styling thread pool.
58    ///
59    /// For leak-checking purposes, we want to terminate the thread-pool, which
60    /// waits for all the async jobs to complete. Thus the RwLock.
61    style_thread_pool: RwLock<Option<rayon::ThreadPool>>,
62}
63
64fn thread_name(index: usize) -> String {
65    format!("StyleThread#{}", index)
66}
67
68/// JoinHandles for spawned style threads. These will be joined during
69/// StyleThreadPool::shutdown() after exiting the thread pool.
70///
71/// This would be quite inefficient if rayon destroyed and re-created
72/// threads regularly during threadpool operation in response to demand,
73/// however rayon actually never destroys its threads until the entire
74/// thread pool is shut-down, so the size of this list is bounded.
75static STYLE_THREAD_JOIN_HANDLES: Mutex<Vec<thread::JoinHandle<()>>> = Mutex::new(Vec::new());
76
77fn thread_spawn(options: rayon::ThreadBuilder) -> io::Result<()> {
78    let mut b = thread::Builder::new();
79    if let Some(name) = options.name() {
80        b = b.name(name.to_owned());
81    }
82    if let Some(stack_size) = options.stack_size() {
83        b = b.stack_size(stack_size);
84    }
85    let join_handle = b.spawn(|| options.run())?;
86    STYLE_THREAD_JOIN_HANDLES.lock().push(join_handle);
87    Ok(())
88}
89
90fn thread_startup(_index: usize) {
91    thread_state::initialize_layout_worker_thread();
92    #[cfg(feature = "gecko")]
93    unsafe {
94        bindings::Gecko_SetJemallocThreadLocalArena(true);
95        let name = thread_name(_index);
96        gecko_profiler::register_thread(&name);
97    }
98}
99
100fn thread_shutdown(_: usize) {
101    #[cfg(feature = "gecko")]
102    unsafe {
103        gecko_profiler::unregister_thread();
104        bindings::Gecko_SetJemallocThreadLocalArena(false);
105    }
106}
107
108impl StyleThreadPool {
109    /// Shuts down the thread pool, waiting for all work to complete.
110    pub fn shutdown() {
111        if STYLE_THREAD_JOIN_HANDLES.lock().is_empty() {
112            return;
113        }
114        {
115            // Drop the pool.
116            let _ = STYLE_THREAD_POOL.style_thread_pool.write().take();
117        }
118
119        // Join spawned threads until all of the threads have been joined. This
120        // will usually be pretty fast, as on shutdown there should be basically
121        // no threads left running.
122        while let Some(join_handle) = STYLE_THREAD_JOIN_HANDLES.lock().pop() {
123            let _ = join_handle.join();
124        }
125    }
126
127    /// Returns a reference to the thread pool.
128    ///
129    /// We only really want to give read-only access to the pool, except
130    /// for shutdown().
131    pub fn pool(&self) -> RwLockReadGuard<'_, Option<rayon::ThreadPool>> {
132        self.style_thread_pool.read()
133    }
134
135    /// Returns a list of the pool's platform-specific thread handles.
136    pub fn get_thread_handles(handles: &mut ThinVec<PlatformThreadHandle>) {
137        // Force the lazy initialization of STYLE_THREAD_POOL so that the threads get spawned and
138        // their join handles are added to STYLE_THREAD_JOIN_HANDLES.
139        LazyLock::force(&STYLE_THREAD_POOL);
140
141        for join_handle in STYLE_THREAD_JOIN_HANDLES.lock().iter() {
142            #[cfg(all(unix, not(target_arch = "wasm32")))]
143            let handle = join_handle.as_pthread_t();
144            #[cfg(windows)]
145            let handle = join_handle.as_raw_handle();
146            #[cfg(all(target_arch = "wasm32", not(feature = "gecko")))]
147            let handle = {
148                let _ = join_handle;
149                DummyThreadHandle
150            };
151
152            handles.push(handle);
153        }
154    }
155}
156
157#[cfg(feature = "servo")]
158fn stylo_threads_pref() -> i32 {
159    style_config::get_i32("layout.threads")
160}
161
162#[cfg(feature = "gecko")]
163fn stylo_threads_pref() -> i32 {
164    static_prefs::pref!("layout.css.stylo-threads")
165}
166
167/// The performance benefit of additional threads seems to level off at around six, so we cap it
168/// there on many-core machines (see bug 1431285 comment 14).
169pub(crate) const STYLO_MAX_THREADS: usize = 6;
170
171/// Global thread pool
172pub static STYLE_THREAD_POOL: LazyLock<StyleThreadPool> = LazyLock::new(|| {
173    use std::cmp;
174    // We always set this pref on startup, before layout or script have had a chance of
175    // accessing (and thus creating) the thread-pool.
176    let threads_pref: i32 = stylo_threads_pref();
177    let num_threads = if threads_pref >= 0 {
178        threads_pref as usize
179    } else {
180        // Gecko may wish to override the default number of threads, for example on
181        // systems with heterogeneous CPUs.
182        #[cfg(feature = "gecko")]
183        let num_threads = unsafe { bindings::Gecko_GetNumStyleThreads() };
184        #[cfg(not(feature = "gecko"))]
185        let num_threads = -1;
186
187        if num_threads >= 0 {
188            num_threads as usize
189        } else {
190            // The default heuristic is num_virtual_cores * .75. This gives us three threads on a
191            // hyper-threaded dual core, and six threads on a hyper-threaded quad core.
192            cmp::max(num_cpus::get() * 3 / 4, 1)
193        }
194    };
195
196    let num_threads = cmp::min(num_threads, STYLO_MAX_THREADS);
197    // Since the main-thread is also part of the pool, having one thread or less doesn't make
198    // sense.
199    let (pool, num_threads) = if num_threads <= 1 {
200        (None, None)
201    } else {
202        let workers = rayon::ThreadPoolBuilder::new()
203            .spawn_handler(thread_spawn)
204            .use_current_thread()
205            .num_threads(num_threads)
206            .thread_name(thread_name)
207            .start_handler(thread_startup)
208            .exit_handler(thread_shutdown)
209            .stack_size(STYLE_THREAD_STACK_SIZE_KB * 1024)
210            .build();
211        (workers.ok(), Some(num_threads))
212    };
213
214    StyleThreadPool {
215        num_threads,
216        style_thread_pool: RwLock::new(pool),
217    }
218});
219
220/// Global style data
221pub static GLOBAL_STYLE_DATA: LazyLock<GlobalStyleData> = LazyLock::new(|| GlobalStyleData {
222    shared_lock: SharedRwLock::new_leaked(),
223    options: StyleSystemOptions::default(),
224});