1- //! CEF cache-lock preflight check (macOS).
1+ //! CEF cache-lock preflight check (macOS and Linux ).
22//!
33//! When another OpenHuman instance is already running, it holds an exclusive
4- //! lock on the CEF user-data-dir at `~/Library/Caches/com.openhuman.app/cef`.
4+ //! lock on the CEF user-data-dir. On macOS this is
5+ //! `~/Library/Caches/com.openhuman.app/cef`; on Linux it is the path in
6+ //! `OPENHUMAN_CEF_CACHE_PATH` (set by `cef_profile::prepare_process_cache_path`
7+ //! before this module runs), falling back to `$XDG_CACHE_HOME/<id>/cef` or
8+ //! `$HOME/.cache/<id>/cef` when the env var is absent.
9+ //!
510//! The vendored `tauri-runtime-cef` crate calls `cef::initialize()` and
611//! asserts the result equals `1`; on lock collision it returns `0` and the
712//! assertion panics with a Rust backtrace and no actionable message
8- //! (see issue #864).
13+ //! (Sentry OPENHUMAN-TAURI-K1 on Linux, issue #864 on macOS ).
914//!
1015//! This module runs *before* the Tauri builder constructs the runtime.
1116//! It detects the lock-holder PID via Chromium's `SingletonLock` symlink and
@@ -72,7 +77,12 @@ impl fmt::Display for CefLockError {
7277
7378impl std:: error:: Error for CefLockError { }
7479
75- /// Resolves the macOS default CEF cache directory and runs the preflight.
80+ /// Resolves the platform default CEF cache directory and runs the preflight.
81+ ///
82+ /// Checks `OPENHUMAN_CEF_CACHE_PATH` first (always set by
83+ /// `cef_profile::prepare_process_cache_path` before this runs). Falls back
84+ /// to the platform-specific default: `~/Library/Caches/<id>/cef` on macOS,
85+ /// `$XDG_CACHE_HOME/<id>/cef` or `$HOME/.cache/<id>/cef` on Linux.
7686pub fn check_default_cache ( ) -> Result < ( ) , CefLockError > {
7787 if let Some ( configured) = std:: env:: var_os ( "OPENHUMAN_CEF_CACHE_PATH" ) {
7888 let configured = PathBuf :: from ( configured) ;
@@ -84,10 +94,22 @@ pub fn check_default_cache() -> Result<(), CefLockError> {
8494 }
8595
8696 let home = std:: env:: var_os ( "HOME" ) . ok_or ( CefLockError :: NoHomeDir ) ?;
87- let cache_path = PathBuf :: from ( home)
88- . join ( "Library/Caches" )
97+ let home = PathBuf :: from ( home) ;
98+
99+ #[ cfg( target_os = "macos" ) ]
100+ let cache_path = home. join ( "Library/Caches" ) . join ( APP_IDENTIFIER ) . join ( "cef" ) ;
101+
102+ // On Linux: $XDG_CACHE_HOME/<id>/cef or $HOME/.cache/<id>/cef.
103+ // This matches the fallback path in tauri-runtime-cef's CefRuntime::init
104+ // (via `dirs::cache_dir()`).
105+ #[ cfg( target_os = "linux" ) ]
106+ let cache_path = std:: env:: var_os ( "XDG_CACHE_HOME" )
107+ . map ( PathBuf :: from)
108+ . filter ( |p| p. is_absolute ( ) )
109+ . unwrap_or_else ( || home. join ( ".cache" ) )
89110 . join ( APP_IDENTIFIER )
90111 . join ( "cef" ) ;
112+
91113 log:: debug!( "[cef-preflight] cache_path={}" , cache_path. display( ) ) ;
92114 check_cef_cache_lock ( & cache_path)
93115}
@@ -201,6 +223,12 @@ mod tests {
201223 use super :: * ;
202224 use std:: os:: unix:: fs:: symlink;
203225
226+ // Shared lock for all tests that mutate process-global env vars.
227+ // Each test previously had its own local `static ENV_LOCK`, allowing
228+ // concurrent test threads to race on OPENHUMAN_CEF_CACHE_PATH /
229+ // XDG_CACHE_HOME. A single module-level lock serialises them.
230+ static ENV_LOCK : std:: sync:: Mutex < ( ) > = std:: sync:: Mutex :: new ( ( ) ) ;
231+
204232 #[ test]
205233 fn parse_target_simple ( ) {
206234 assert_eq ! (
@@ -310,4 +338,86 @@ mod tests {
310338 ) ;
311339 let _ = fs:: remove_dir_all ( & tmp) ;
312340 }
341+
342+ /// `check_default_cache` must use `OPENHUMAN_CEF_CACHE_PATH` when set —
343+ /// on both macOS and Linux the profile module always sets this before the
344+ /// preflight runs, so the platform-specific fallback paths are irrelevant
345+ /// in production, but the configured-path branch must work on all platforms.
346+ #[ test]
347+ fn check_default_cache_uses_configured_env_path ( ) {
348+ let _guard = ENV_LOCK . lock ( ) . unwrap_or_else ( |e| e. into_inner ( ) ) ;
349+
350+ let prior = std:: env:: var_os ( "OPENHUMAN_CEF_CACHE_PATH" ) ;
351+ let tmp = fresh_tmp ( "default-cache-env" ) ;
352+
353+ std:: env:: set_var ( "OPENHUMAN_CEF_CACHE_PATH" , & tmp) ;
354+ let result = check_default_cache ( ) ;
355+
356+ match prior {
357+ Some ( v) => std:: env:: set_var ( "OPENHUMAN_CEF_CACHE_PATH" , v) ,
358+ None => std:: env:: remove_var ( "OPENHUMAN_CEF_CACHE_PATH" ) ,
359+ }
360+
361+ assert ! ( result. is_ok( ) , "expected Ok with no lock, got {result:?}" ) ;
362+ let _ = fs:: remove_dir_all ( & tmp) ;
363+ }
364+
365+ /// `check_default_cache` with env-path pointing to a dir holding a live lock
366+ /// must return `CefLockError::Held`.
367+ #[ test]
368+ fn check_default_cache_env_path_held_returns_err ( ) {
369+ let _guard = ENV_LOCK . lock ( ) . unwrap_or_else ( |e| e. into_inner ( ) ) ;
370+
371+ let prior = std:: env:: var_os ( "OPENHUMAN_CEF_CACHE_PATH" ) ;
372+ let tmp = fresh_tmp ( "default-cache-held" ) ;
373+ let me = std:: process:: id ( ) as i32 ;
374+ symlink ( format ! ( "testhost-{me}" ) , tmp. join ( "SingletonLock" ) ) . unwrap ( ) ;
375+
376+ std:: env:: set_var ( "OPENHUMAN_CEF_CACHE_PATH" , & tmp) ;
377+ let result = check_default_cache ( ) ;
378+
379+ match prior {
380+ Some ( v) => std:: env:: set_var ( "OPENHUMAN_CEF_CACHE_PATH" , v) ,
381+ None => std:: env:: remove_var ( "OPENHUMAN_CEF_CACHE_PATH" ) ,
382+ }
383+
384+ match result {
385+ Err ( CefLockError :: Held { pid, .. } ) => assert_eq ! ( pid, me) ,
386+ other => panic ! ( "expected Held, got {other:?}" ) ,
387+ }
388+ let _ = fs:: remove_dir_all ( & tmp) ;
389+ }
390+
391+ /// On Linux, `check_default_cache` without `OPENHUMAN_CEF_CACHE_PATH` set
392+ /// must fall back to `$XDG_CACHE_HOME/<id>/cef` and return Ok when no lock
393+ /// is present.
394+ #[ cfg( target_os = "linux" ) ]
395+ #[ test]
396+ fn check_default_cache_linux_xdg_fallback_no_lock ( ) {
397+ let _guard = ENV_LOCK . lock ( ) . unwrap_or_else ( |e| e. into_inner ( ) ) ;
398+
399+ let prior_cache = std:: env:: var_os ( "OPENHUMAN_CEF_CACHE_PATH" ) ;
400+ let prior_xdg = std:: env:: var_os ( "XDG_CACHE_HOME" ) ;
401+ std:: env:: remove_var ( "OPENHUMAN_CEF_CACHE_PATH" ) ;
402+
403+ // Redirect XDG_CACHE_HOME to a temp dir we control.
404+ let tmp = fresh_tmp ( "linux-xdg-fallback" ) ;
405+ std:: env:: set_var ( "XDG_CACHE_HOME" , & tmp) ;
406+
407+ let result = check_default_cache ( ) ;
408+
409+ std:: env:: remove_var ( "XDG_CACHE_HOME" ) ;
410+ match prior_cache {
411+ Some ( v) => std:: env:: set_var ( "OPENHUMAN_CEF_CACHE_PATH" , v) ,
412+ None => { }
413+ }
414+ match prior_xdg {
415+ Some ( v) => std:: env:: set_var ( "XDG_CACHE_HOME" , v) ,
416+ None => { }
417+ }
418+
419+ // No SingletonLock under tmp/<id>/cef — should be Ok.
420+ assert ! ( result. is_ok( ) , "expected Ok with no lock, got {result:?}" ) ;
421+ let _ = fs:: remove_dir_all ( & tmp) ;
422+ }
313423}
0 commit comments