storage: Add support for temporary storage (#44433)

Add support for temporary storage via a new config option
`temporary_storage`
and a corresponding command-line argument `--temporary-storage`.

When enabled, client storage uses a storage directory
(e.g. `clientstorage/temporary/<uuid>`) instead of the shared default
location.
This can be used to provide isolation between concurrent servo
instances.

This is especially useful for WPT runs, where multiple Servo instances
may
execute in parallel and would otherwise share the same storage, leading
to
cross-test interference.

Based on that, this PR also updates the WPT runner to enable temporary
storage
by default.

Testing: Manual testing and a full try run.

Signed-off-by: Jan Varga <jvarga@igalia.com>
This commit is contained in:
Jan Varga
2026-04-25 12:18:21 +02:00
committed by GitHub
parent 1a9808d421
commit 902d5d10d9
9 changed files with 53 additions and 20 deletions

View File

@@ -60,6 +60,9 @@ pub struct Opts {
/// Directory for a default config directory
pub config_dir: Option<PathBuf>,
/// Use temporary storage (data on disk will not persist across restarts).
pub temporary_storage: bool,
/// Path to PEM encoded SSL CA certificate store.
pub certificate_path: Option<String>,
@@ -222,6 +225,7 @@ impl Default for Opts {
sandbox: false,
debug: Default::default(),
config_dir: None,
temporary_storage: false,
shaders_path: None,
certificate_path: None,
ignore_certificate_errors: false,

View File

@@ -930,8 +930,11 @@ impl Servo {
protocols.clone(),
);
let (private_storage_threads, public_storage_threads) =
new_storage_threads(mem_profiler_chan.clone(), opts.config_dir.clone());
let (private_storage_threads, public_storage_threads) = new_storage_threads(
mem_profiler_chan.clone(),
opts.config_dir.clone(),
opts.temporary_storage,
);
create_constellation(
embedder_to_constellation_receiver,

View File

@@ -642,19 +642,25 @@ impl RegistryEngine for SqliteEngine {
}
pub trait ClientStorageThreadFactory {
fn new(config_dir: Option<PathBuf>) -> Self;
fn new(config_dir: Option<PathBuf>, temporary_storage: bool) -> Self;
}
impl ClientStorageThreadFactory for ClientStorageThreadHandle {
fn new(config_dir: Option<PathBuf>) -> ClientStorageThreadHandle {
fn new(config_dir: Option<PathBuf>, temporary_storage: bool) -> ClientStorageThreadHandle {
let (generic_sender, generic_receiver) = generic_channel::channel().unwrap();
let storage_dir = config_dir
let base_dir = config_dir
.unwrap_or_else(|| {
let tmp_dir = tempfile::tempdir().unwrap();
tmp_dir.path().to_path_buf()
})
.join("clientstorage");
let storage_dir = if temporary_storage {
let unique_id = uuid::Uuid::new_v4().to_string();
base_dir.join("temporary").join(unique_id)
} else {
base_dir.join("default_v1")
};
std::fs::create_dir_all(&storage_dir)
.expect("Failed to create ClientStorage storage directory");
let sender_clone = generic_sender.clone();

View File

@@ -16,10 +16,11 @@ use crate::{ClientStorageThreadFactory, IndexedDBThreadFactory, WebStorageThread
fn new_storage_thread_group(
mem_profiler_chan: MemProfilerChan,
config_dir: Option<PathBuf>,
temporary_storage: bool,
label: &str,
) -> StorageThreads {
let client_storage: ClientStorageThreadHandle =
ClientStorageThreadFactory::new(config_dir.clone());
ClientStorageThreadFactory::new(config_dir.clone(), temporary_storage);
let idb: GenericSender<IndexedDBThreadMsg> = IndexedDBThreadFactory::new(
config_dir.clone(),
mem_profiler_chan.clone(),
@@ -37,10 +38,16 @@ fn new_storage_thread_group(
pub fn new_storage_threads(
mem_profiler_chan: MemProfilerChan,
config_dir: Option<PathBuf>,
temporary_storage: bool,
) -> (StorageThreads, StorageThreads) {
let private_storage_threads =
new_storage_thread_group(mem_profiler_chan.clone(), config_dir.clone(), "private");
let public_storage_threads = new_storage_thread_group(mem_profiler_chan, config_dir, "public");
let private_storage_threads = new_storage_thread_group(
mem_profiler_chan.clone(),
config_dir.clone(),
temporary_storage,
"private",
);
let public_storage_threads =
new_storage_thread_group(mem_profiler_chan, config_dir, temporary_storage, "public");
(private_storage_threads, public_storage_threads)
}

View File

@@ -19,7 +19,11 @@ fn install_test_namespace() {
}
fn registry_db_path(tmp_dir: &tempfile::TempDir) -> PathBuf {
tmp_dir.path().join("clientstorage").join("reg.sqlite")
tmp_dir
.path()
.join("clientstorage")
.join("default_v1")
.join("reg.sqlite")
}
fn open_registry(tmp_dir: &tempfile::TempDir) -> Connection {
@@ -42,7 +46,7 @@ fn obtain_bottle_map(
#[test]
fn test_exit() {
let handle: ClientStorageThreadHandle = ClientStorageThreadFactory::new(None);
let handle: ClientStorageThreadHandle = ClientStorageThreadFactory::new(None, false);
let (sender, receiver) = generic_channel::channel().unwrap();
handle
@@ -60,7 +64,7 @@ fn test_workflow() {
install_test_namespace();
let tmp_dir = tempfile::tempdir().unwrap();
let handle: ClientStorageThreadHandle =
ClientStorageThreadFactory::new(Some(tmp_dir.path().to_path_buf()));
ClientStorageThreadFactory::new(Some(tmp_dir.path().to_path_buf()), false);
let url = ServoUrl::parse("https://example.com").unwrap();
@@ -124,7 +128,7 @@ fn test_repeated_local_obtain_reuses_same_logical_rows() {
install_test_namespace();
let tmp_dir = tempfile::tempdir().unwrap();
let handle: ClientStorageThreadHandle =
ClientStorageThreadFactory::new(Some(tmp_dir.path().to_path_buf()));
ClientStorageThreadFactory::new(Some(tmp_dir.path().to_path_buf()), false);
let origin = ServoUrl::parse("https://example.com").unwrap().origin();
let webview = WebViewId::new(servo_base::id::TEST_PAINTER_ID);
@@ -179,7 +183,7 @@ fn test_repeated_session_obtain_reuses_same_logical_rows() {
install_test_namespace();
let tmp_dir = tempfile::tempdir().unwrap();
let handle: ClientStorageThreadHandle =
ClientStorageThreadFactory::new(Some(tmp_dir.path().to_path_buf()));
ClientStorageThreadFactory::new(Some(tmp_dir.path().to_path_buf()), false);
let origin = ServoUrl::parse("https://example.com").unwrap().origin();
let webview = WebViewId::new(servo_base::id::TEST_PAINTER_ID);
@@ -235,7 +239,7 @@ fn test_local_persistence_and_estimate() {
install_test_namespace();
let tmp_dir = tempfile::tempdir().unwrap();
let handle: ClientStorageThreadHandle =
ClientStorageThreadFactory::new(Some(tmp_dir.path().to_path_buf()));
ClientStorageThreadFactory::new(Some(tmp_dir.path().to_path_buf()), false);
let origin = ServoUrl::parse("https://example.com").unwrap().origin();
let webview = WebViewId::new(servo_base::id::TEST_PAINTER_ID);
@@ -287,7 +291,7 @@ fn test_storage_manager_operations_fail_for_opaque_origins() {
install_test_namespace();
let tmp_dir = tempfile::tempdir().unwrap();
let handle: ClientStorageThreadHandle =
ClientStorageThreadFactory::new(Some(tmp_dir.path().to_path_buf()));
ClientStorageThreadFactory::new(Some(tmp_dir.path().to_path_buf()), false);
let origin = ServoUrl::parse("data:text/plain,hello").unwrap().origin();

View File

@@ -39,7 +39,7 @@ fn shutdown_storage_group(threads: &StorageThreads) {
fn test_new_storage_threads_create_independent_groups() {
let mem_profiler_chan = profile_mem::Profiler::create();
let (private_storage_threads, public_storage_threads) =
storage::new_storage_threads(mem_profiler_chan, None);
storage::new_storage_threads(mem_profiler_chan, None, false);
shutdown_storage_group(&private_storage_threads);
shutdown_storage_group(&public_storage_threads);

View File

@@ -22,7 +22,7 @@ impl WebStorageTest {
let tmp_dir = tempfile::tempdir().unwrap();
let config_dir = tmp_dir.path().to_path_buf();
let mem_profiler_chan = profile_mem::Profiler::create();
let threads = storage::new_storage_threads(mem_profiler_chan, Some(config_dir));
let threads = storage::new_storage_threads(mem_profiler_chan, Some(config_dir), false);
Self {
tmp_dir: Some(tmp_dir),
@@ -32,7 +32,7 @@ impl WebStorageTest {
pub(crate) fn new_in_memory() -> Self {
let mem_profiler_chan = profile_mem::Profiler::create();
let threads = storage::new_storage_threads(mem_profiler_chan, None);
let threads = storage::new_storage_threads(mem_profiler_chan, None, false);
Self {
tmp_dir: None,
@@ -44,7 +44,7 @@ impl WebStorageTest {
let tmp_dir = self.tmp_dir.take();
let config_dir = tmp_dir.as_ref().map(|d| d.path().to_path_buf());
let mem_profiler_chan = profile_mem::Profiler::create();
let threads = storage::new_storage_threads(mem_profiler_chan, config_dir);
let threads = storage::new_storage_threads(mem_profiler_chan, config_dir, false);
Self {
tmp_dir: tmp_dir,

View File

@@ -378,6 +378,10 @@ struct CmdArgs {
#[bpaf(argument("~/.config/servo"))]
config_dir: Option<PathBuf>,
/// Use temporary storage (data on disk will not persist across restarts).
#[bpaf(long)]
temporary_storage: bool,
///
/// Run as a content process and connect to the given pipe.
#[bpaf(argument("servo-ipc-channel.abcdefg"))]
@@ -634,6 +638,7 @@ fn parse_arguments_helper(args_without_binary: Args) -> ArgumentParsingResult {
fs::create_dir_all(config_dir).expect("Could not create config_dir");
}
});
let temporary_storage = cmd_args.temporary_storage;
if let Some(ref time_profiler_trace_path) = cmd_args.profiler_trace_path {
let mut path = PathBuf::from(time_profiler_trace_path);
path.pop();
@@ -706,6 +711,7 @@ fn parse_arguments_helper(args_without_binary: Args) -> ArgumentParsingResult {
random_pipeline_closure_probability: cmd_args.random_pipeline_closure_probability,
random_pipeline_closure_seed: cmd_args.random_pipeline_closure_seed,
config_dir,
temporary_storage,
shaders_path: cmd_args.shaders,
certificate_path: cmd_args
.certificate_path

View File

@@ -136,6 +136,9 @@ def run_tests(default_binary_path: str, multiprocess: bool, **kwargs: Any) -> in
with tempfile.TemporaryDirectory(prefix="servo-") as config_dir:
kwargs["binary_args"] += ["--config-dir", config_dir]
# Temporary workaround to avoid shared storage across parallel processes.
# Can be removed once per-process config dirs are supported.
kwargs["binary_args"] += ["--temporary-storage"]
wptrunner.run_tests(**kwargs)