From d2ccce6052a6cff7ddee67f4e8d2823a04366058 Mon Sep 17 00:00:00 2001 From: Jonathan Schwender <55576758+jschwe@users.noreply.github.com> Date: Thu, 14 Aug 2025 09:05:11 +0200 Subject: [PATCH] opts: Use OnceLock for Options (#38638) Currently, the options are only initialized once and then never changed. Making this explicit allows optimizing accesses, e.g. caching opt values. For some options like `multiprocess` it is obvious that we would never want to support changing the value at runtime, however there are other options where it could be conceivable that we want to change them at runtime. However, imho such options might be better suited to put into a different datastructure, so that it is explicit which options can be changed at runtime, and which are fixed. Testing: Covered by existing tests (and manually running servo in multiprocess mode). Signed-off-by: Jonathan Schwender --- components/config/opts.rs | 26 ++++++++++++++++++++------ components/servo/lib.rs | 4 ++-- 2 files changed, 22 insertions(+), 8 deletions(-) diff --git a/components/config/opts.rs b/components/config/opts.rs index 8432833eeb2..b14c3bd5ebc 100644 --- a/components/config/opts.rs +++ b/components/config/opts.rs @@ -7,7 +7,7 @@ use std::default::Default; use std::path::PathBuf; -use std::sync::{LazyLock, RwLock, RwLockReadGuard}; +use std::sync::OnceLock; use serde::{Deserialize, Serialize}; use servo_url::ServoUrl; @@ -209,13 +209,27 @@ impl Default for Opts { // Make Opts available globally. This saves having to clone and pass // opts everywhere it is used, which gets particularly cumbersome // when passing through the DOM structures. -static OPTIONS: LazyLock> = LazyLock::new(|| RwLock::new(Opts::default())); +static OPTIONS: OnceLock = OnceLock::new(); -pub fn set_options(opts: Opts) { - *OPTIONS.write().unwrap() = opts; +/// Initialize options. +/// +/// Should only be called once at process startup. +/// Must be called before the first call to [get]. +pub fn initialize_options(opts: Opts) { + OPTIONS.set(opts).expect("Already initialized"); } +/// Get the servo options +/// +/// If the servo options have not been initialized by calling [initialize_options], then the +/// options will be initialized to default values. Outside of tests the options should +/// be explicitly initialized. #[inline] -pub fn get() -> RwLockReadGuard<'static, Opts> { - OPTIONS.read().unwrap() +pub fn get() -> &'static Opts { + // In unit-tests using default options reduces boilerplate. + // We can't use `cfg(test)` since that only is enabled when this crate + // is compiled in test mode. + // We rely on the `expect` in `initialize_options` to inform us if refactoring + // causes a `get` call to move before `initialize_options`. + OPTIONS.get_or_init(Default::default) } diff --git a/components/servo/lib.rs b/components/servo/lib.rs index 7eb66c6deb5..37ce2019c7a 100644 --- a/components/servo/lib.rs +++ b/components/servo/lib.rs @@ -254,7 +254,7 @@ impl Servo { fn new(builder: ServoBuilder) -> Self { // Global configuration options, parsed from the command line. let opts = builder.opts.map(|opts| *opts); - opts::set_options(opts.unwrap_or_default()); + opts::initialize_options(opts.unwrap_or_default()); let opts = opts::get(); // Set the preferences globally. @@ -1244,7 +1244,7 @@ pub fn run_content_process(token: String) { .unwrap(); let unprivileged_content = unprivileged_content_receiver.recv().unwrap(); - opts::set_options(unprivileged_content.opts()); + opts::initialize_options(unprivileged_content.opts()); prefs::set(unprivileged_content.prefs().clone()); // Enter the sandbox if necessary.