tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

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 });