From 755a6aabd89a086c6a0418c4b5516bf92f86d92d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=9B=B7=E7=94=B5=E8=8A=BD=E8=A1=A3?= Date: Thu, 9 Jul 2026 23:05:10 -0400 Subject: [PATCH] fix(servo): lease persistent profile data --- .../src/bin/ely_servo_sidecar/live.rs | 22 +++++++++++- .../bin/ely_servo_sidecar/live_protocol.rs | 5 ++- crates/ely_servo_host/tests/sidecar.rs | 36 ++++++++++++++++++- 3 files changed, 60 insertions(+), 3 deletions(-) diff --git a/crates/ely_servo_host/src/bin/ely_servo_sidecar/live.rs b/crates/ely_servo_host/src/bin/ely_servo_sidecar/live.rs index 561bb5c..c9ac412 100644 --- a/crates/ely_servo_host/src/bin/ely_servo_sidecar/live.rs +++ b/crates/ely_servo_host/src/bin/ely_servo_sidecar/live.rs @@ -1,7 +1,8 @@ use std::{ collections::HashMap, - fs, + fs::{self, File, OpenOptions, TryLockError}, io::{self, BufRead}, + path::Path, }; use ely_domain::{ProfileId, TabId, UrlText}; @@ -24,9 +25,12 @@ use super::{ }, }; +const PROFILE_DATA_LEASE_FILE: &str = ".ely-servo-sidecar.lock"; + pub(super) fn run(args: LiveArgs) -> Result<(), LiveSidecarError> { let LiveArgs { profile_data_dir, rendering_context, iosurface_mach_service } = args; fs::create_dir_all(&profile_data_dir)?; + let _profile_data_lease = acquire_profile_data_lease(&profile_data_dir)?; let rendering_context_kind = match rendering_context { SidecarRenderingContext::Software => RenderingContextKind::Software, SidecarRenderingContext::Hardware => RenderingContextKind::Hardware, @@ -84,6 +88,22 @@ pub(super) fn run(args: LiveArgs) -> Result<(), LiveSidecarError> { Ok(()) } +fn acquire_profile_data_lease(profile_data_dir: &Path) -> Result { + let lease = OpenOptions::new() + .create(true) + .read(true) + .write(true) + .truncate(false) + .open(profile_data_dir.join(PROFILE_DATA_LEASE_FILE))?; + match lease.try_lock() { + Ok(()) => Ok(lease), + Err(TryLockError::WouldBlock) => Err(LiveSidecarError::ProfileDataDirectoryInUse { + path: profile_data_dir.to_path_buf(), + }), + Err(TryLockError::Error(error)) => Err(error.into()), + } +} + fn handle_request( host: &mut SoftwareServoHost, sessions: &mut HashMap, diff --git a/crates/ely_servo_host/src/bin/ely_servo_sidecar/live_protocol.rs b/crates/ely_servo_host/src/bin/ely_servo_sidecar/live_protocol.rs index ab51db1..e1abaa6 100644 --- a/crates/ely_servo_host/src/bin/ely_servo_sidecar/live_protocol.rs +++ b/crates/ely_servo_host/src/bin/ely_servo_sidecar/live_protocol.rs @@ -1,4 +1,4 @@ -use std::io; +use std::{io, path::PathBuf}; use ely_servo_host::{ ConsumedPermission, IOSurfaceHandle, RenderedFrame, ServoHostError, WebViewSnapshot, @@ -305,6 +305,9 @@ pub(super) enum LiveSidecarError { #[error("sidecar process is already bound to profile {expected}; received {actual}")] ProfileMismatch { expected: String, actual: String }, + #[error("servo profile data directory is already in use: {path}")] + ProfileDataDirectoryInUse { path: PathBuf }, + #[error("{input} requires both x and y coordinates")] IncompletePoint { input: &'static str }, diff --git a/crates/ely_servo_host/tests/sidecar.rs b/crates/ely_servo_host/tests/sidecar.rs index 5d57fc2..13621c9 100644 --- a/crates/ely_servo_host/tests/sidecar.rs +++ b/crates/ely_servo_host/tests/sidecar.rs @@ -2,7 +2,9 @@ use std::{ error::Error, - io, thread, + io, + process::{Command, Stdio}, + thread, time::{Duration, Instant}, }; @@ -64,6 +66,38 @@ fn live_sidecar_streams_rgba_and_flushes_profile_storage_on_shutdown() -> Result Ok(()) } +#[test] +fn live_sidecar_leases_profile_data_directory_for_process_lifetime() -> Result<(), Box> { + let root = TestDirectory::new()?; + let leased_dir = root.path().join("leased"); + let independent_dir = root.path().join("independent"); + let mut owner = Sidecar::spawn(&leased_dir)?; + assert!(leased_dir.join(".ely-servo-sidecar.lock").is_file()); + + let rejected = Command::new(env!("CARGO_BIN_EXE_ely_servo_sidecar")) + .arg("live") + .arg("--profile-data-dir") + .arg(&leased_dir) + .stdin(Stdio::null()) + .stdout(Stdio::null()) + .stderr(Stdio::piped()) + .output()?; + let stderr = String::from_utf8(rejected.stderr)?; + assert!(!rejected.status.success(), "second sidecar unexpectedly acquired the profile lease"); + assert!( + stderr.contains("ProfileDataDirectoryInUse"), + "second sidecar reported an unexpected error: {stderr}" + ); + + let mut independent = Sidecar::spawn(&independent_dir)?; + independent.shutdown()?; + owner.shutdown()?; + + let mut replacement = Sidecar::spawn(&leased_dir)?; + replacement.shutdown()?; + Ok(()) +} + #[test] fn servo_originated_history_url_does_not_trigger_a_second_navigation() -> Result<(), Box> {