global_style_data.rs (7839B)
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 7 use crate::context::StyleSystemOptions; 8 #[cfg(feature = "gecko")] 9 use crate::gecko_bindings::bindings; 10 use crate::parallel::STYLE_THREAD_STACK_SIZE_KB; 11 use crate::shared_lock::SharedRwLock; 12 use crate::thread_state; 13 use parking_lot::{Mutex, RwLock, RwLockReadGuard}; 14 #[cfg(unix)] 15 use std::os::unix::thread::{JoinHandleExt, RawPthread}; 16 #[cfg(windows)] 17 use std::os::windows::{io::AsRawHandle, prelude::RawHandle}; 18 use std::{io, sync::LazyLock, thread}; 19 use thin_vec::ThinVec; 20 21 /// Platform-specific handle to a thread. 22 #[cfg(unix)] 23 pub type PlatformThreadHandle = RawPthread; 24 /// Platform-specific handle to a thread. 25 #[cfg(windows)] 26 pub 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")))] 31 pub struct DummyThreadHandle; 32 #[cfg(all(target_arch = "wasm32", not(feature = "gecko")))] 33 impl 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. 41 pub type PlatformThreadHandle = DummyThreadHandle; 42 43 /// Global style data 44 pub 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. 53 pub 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 64 fn 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. 75 static STYLE_THREAD_JOIN_HANDLES: Mutex<Vec<thread::JoinHandle<()>>> = Mutex::new(Vec::new()); 76 77 fn 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 90 fn 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 100 fn thread_shutdown(_: usize) { 101 #[cfg(feature = "gecko")] 102 unsafe { 103 gecko_profiler::unregister_thread(); 104 bindings::Gecko_SetJemallocThreadLocalArena(false); 105 } 106 } 107 108 impl 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(unix)] 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")] 158 fn stylo_threads_pref() -> i32 { 159 style_config::get_i32("layout.threads") 160 } 161 162 #[cfg(feature = "gecko")] 163 fn 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). 169 pub(crate) const STYLO_MAX_THREADS: usize = 6; 170 171 /// Global thread pool 172 pub 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 221 pub static GLOBAL_STYLE_DATA: LazyLock<GlobalStyleData> = LazyLock::new(|| GlobalStyleData { 222 shared_lock: SharedRwLock::new_leaked(), 223 options: StyleSystemOptions::default(), 224 });