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(unix)]
15use std::os::unix::thread::{JoinHandleExt, RawPthread};
16#[cfg(windows)]
17use std::os::windows::{io::AsRawHandle, prelude::RawHandle};
18use std::{io, thread};
19use thin_vec::ThinVec;
20
21/// Platform-specific handle to a thread.
22#[cfg(unix)]
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
68lazy_static! {
69    /// JoinHandles for spawned style threads. These will be joined during
70    /// StyleThreadPool::shutdown() after exiting the thread pool.
71    ///
72    /// This would be quite inefficient if rayon destroyed and re-created
73    /// threads regularly during threadpool operation in response to demand,
74    /// however rayon actually never destroys its threads until the entire
75    /// thread pool is shut-down, so the size of this list is bounded.
76    static ref STYLE_THREAD_JOIN_HANDLES: Mutex<Vec<thread::JoinHandle<()>>> =
77        Mutex::new(Vec::new());
78}
79
80fn thread_spawn(options: rayon::ThreadBuilder) -> io::Result<()> {
81    let mut b = thread::Builder::new();
82    if let Some(name) = options.name() {
83        b = b.name(name.to_owned());
84    }
85    if let Some(stack_size) = options.stack_size() {
86        b = b.stack_size(stack_size);
87    }
88    let join_handle = b.spawn(|| options.run())?;
89    STYLE_THREAD_JOIN_HANDLES.lock().push(join_handle);
90    Ok(())
91}
92
93fn thread_startup(_index: usize) {
94    thread_state::initialize_layout_worker_thread();
95    #[cfg(feature = "gecko")]
96    unsafe {
97        bindings::Gecko_SetJemallocThreadLocalArena(true);
98        let name = thread_name(_index);
99        gecko_profiler::register_thread(&name);
100    }
101}
102
103fn thread_shutdown(_: usize) {
104    #[cfg(feature = "gecko")]
105    unsafe {
106        gecko_profiler::unregister_thread();
107        bindings::Gecko_SetJemallocThreadLocalArena(false);
108    }
109}
110
111impl StyleThreadPool {
112    /// Shuts down the thread pool, waiting for all work to complete.
113    pub fn shutdown() {
114        if STYLE_THREAD_JOIN_HANDLES.lock().is_empty() {
115            return;
116        }
117        {
118            // Drop the pool.
119            let _ = STYLE_THREAD_POOL.style_thread_pool.write().take();
120        }
121
122        // Join spawned threads until all of the threads have been joined. This
123        // will usually be pretty fast, as on shutdown there should be basically
124        // no threads left running.
125        while let Some(join_handle) = STYLE_THREAD_JOIN_HANDLES.lock().pop() {
126            let _ = join_handle.join();
127        }
128    }
129
130    /// Returns a reference to the thread pool.
131    ///
132    /// We only really want to give read-only access to the pool, except
133    /// for shutdown().
134    pub fn pool(&self) -> RwLockReadGuard<Option<rayon::ThreadPool>> {
135        self.style_thread_pool.read()
136    }
137
138    /// Returns a list of the pool's platform-specific thread handles.
139    pub fn get_thread_handles(handles: &mut ThinVec<PlatformThreadHandle>) {
140        // Force the lazy initialization of STYLE_THREAD_POOL so that the threads get spawned and
141        // their join handles are added to STYLE_THREAD_JOIN_HANDLES.
142        lazy_static::initialize(&STYLE_THREAD_POOL);
143
144        for join_handle in STYLE_THREAD_JOIN_HANDLES.lock().iter() {
145            #[cfg(unix)]
146            let handle = join_handle.as_pthread_t();
147            #[cfg(windows)]
148            let handle = join_handle.as_raw_handle();
149            #[cfg(all(target_arch = "wasm32", not(feature = "gecko")))]
150            let handle = {
151                let _ = join_handle;
152                DummyThreadHandle
153            };
154
155            handles.push(handle);
156        }
157    }
158}
159
160#[cfg(feature = "servo")]
161fn stylo_threads_pref() -> i32 {
162    style_config::get_i32("layout.threads")
163}
164
165#[cfg(feature = "gecko")]
166fn stylo_threads_pref() -> i32 {
167    static_prefs::pref!("layout.css.stylo-threads")
168}
169
170/// The performance benefit of additional threads seems to level off at around six, so we cap it
171/// there on many-core machines (see bug 1431285 comment 14).
172pub(crate) const STYLO_MAX_THREADS: usize = 6;
173
174lazy_static! {
175    /// Global thread pool
176    pub static ref STYLE_THREAD_POOL: StyleThreadPool = {
177        use std::cmp;
178        // We always set this pref on startup, before layout or script have had a chance of
179        // accessing (and thus creating) the thread-pool.
180        let threads_pref: i32 = stylo_threads_pref();
181        let num_threads = if threads_pref >= 0 {
182            threads_pref as usize
183        } else {
184            // Gecko may wish to override the default number of threads, for example on
185            // systems with heterogeneous CPUs.
186            #[cfg(feature = "gecko")]
187            let num_threads = unsafe { bindings::Gecko_GetNumStyleThreads() };
188            #[cfg(not(feature = "gecko"))]
189            let num_threads = -1;
190
191            if num_threads >= 0 {
192                num_threads as usize
193            } else {
194                use num_cpus;
195                // The default heuristic is num_virtual_cores * .75. This gives us three threads on a
196                // hyper-threaded dual core, and six threads on a hyper-threaded quad core.
197                cmp::max(num_cpus::get() * 3 / 4, 1)
198            }
199        };
200
201        let num_threads = cmp::min(num_threads, STYLO_MAX_THREADS);
202        // Since the main-thread is also part of the pool, having one thread or less doesn't make
203        // sense.
204        let (pool, num_threads) = if num_threads <= 1 {
205            (None, None)
206        } else {
207            let workers = rayon::ThreadPoolBuilder::new()
208                .spawn_handler(thread_spawn)
209                .use_current_thread()
210                .num_threads(num_threads)
211                .thread_name(thread_name)
212                .start_handler(thread_startup)
213                .exit_handler(thread_shutdown)
214                .stack_size(STYLE_THREAD_STACK_SIZE_KB * 1024)
215                .build();
216            (workers.ok(), Some(num_threads))
217        };
218
219        StyleThreadPool {
220            num_threads,
221            style_thread_pool: RwLock::new(pool),
222        }
223    };
224
225    /// Global style data
226    pub static ref GLOBAL_STYLE_DATA: GlobalStyleData = GlobalStyleData {
227        shared_lock: SharedRwLock::new_leaked(),
228        options: StyleSystemOptions::default(),
229    };
230}