@@ -46,6 +46,10 @@ pub fn get_cache_directory() -> Option<PathBuf> {
4646}
4747
4848pub fn set_cache_directory ( cache_dir : PathBuf ) {
49+ CACHE . set_cache_directory ( cache_dir) ;
50+ }
51+
52+ pub fn set_cache_directory_and_get_effective ( cache_dir : PathBuf ) -> PathBuf {
4953 CACHE . set_cache_directory ( cache_dir)
5054}
5155
@@ -75,24 +79,18 @@ impl CacheImpl {
7579
7680 /// Once a cache directory has been set, you cannot change it.
7781 /// No point supporting such a scenario.
78- fn set_cache_directory ( & self , cache_dir : PathBuf ) {
79- if let Some ( cache_dir) = self
80- . cache_dir
81- . lock ( )
82- . expect ( "cache_dir mutex poisoned" )
83- . clone ( )
84- {
82+ fn set_cache_directory ( & self , cache_dir : PathBuf ) -> PathBuf {
83+ let mut effective = self . cache_dir . lock ( ) . expect ( "cache_dir mutex poisoned" ) ;
84+ if let Some ( existing) = effective. as_ref ( ) {
8585 warn ! (
8686 "Cache directory has already been set to {:?}. Cannot change it now." ,
87- cache_dir
87+ existing
8888 ) ;
89- return ;
89+ return existing . clone ( ) ;
9090 }
9191 trace ! ( "Setting cache directory to {:?}" , cache_dir) ;
92- self . cache_dir
93- . lock ( )
94- . expect ( "cache_dir mutex poisoned" )
95- . replace ( cache_dir) ;
92+ effective. replace ( cache_dir. clone ( ) ) ;
93+ cache_dir
9694 }
9795 fn clear ( & self ) -> io:: Result < ( ) > {
9896 trace ! ( "Clearing cache" ) ;
@@ -389,7 +387,11 @@ impl CacheEntry for CacheEntryImpl {
389387#[ cfg( test) ]
390388mod tests {
391389 use super :: * ;
392- use std:: sync:: atomic:: { AtomicUsize , Ordering } ;
390+ use std:: sync:: {
391+ atomic:: { AtomicUsize , Ordering } ,
392+ mpsc, Barrier ,
393+ } ;
394+ use std:: time:: Duration ;
393395 use tempfile:: tempdir_in;
394396
395397 fn environment ( executable : PathBuf , aliases : Vec < PathBuf > ) -> ResolvedPythonEnv {
@@ -523,4 +525,75 @@ mod tests {
523525
524526 assert ! ( entry. get( ) . is_none( ) ) ;
525527 }
528+
529+ #[ test]
530+ fn concurrent_first_cache_directory_set_has_one_effective_path ( ) {
531+ let temp_dir = tempfile:: tempdir ( ) . unwrap ( ) ;
532+ let cache = Arc :: new ( CacheImpl :: new ( None ) ) ;
533+ let barrier = Arc :: new ( Barrier :: new ( 3 ) ) ;
534+ let ( result_tx, result_rx) = mpsc:: channel ( ) ;
535+ let mut workers = Vec :: new ( ) ;
536+
537+ for name in [ "cache-a" , "cache-b" ] {
538+ let cache = cache. clone ( ) ;
539+ let barrier = barrier. clone ( ) ;
540+ let result_tx = result_tx. clone ( ) ;
541+ let requested = temp_dir. path ( ) . join ( name) ;
542+ workers. push ( std:: thread:: spawn ( move || {
543+ barrier. wait ( ) ;
544+ result_tx
545+ . send ( cache. set_cache_directory ( requested) )
546+ . unwrap ( ) ;
547+ } ) ) ;
548+ }
549+
550+ barrier. wait ( ) ;
551+ let first = result_rx. recv_timeout ( Duration :: from_secs ( 5 ) ) . unwrap ( ) ;
552+ let second = result_rx. recv_timeout ( Duration :: from_secs ( 5 ) ) . unwrap ( ) ;
553+ for worker in workers {
554+ worker. join ( ) . unwrap ( ) ;
555+ }
556+
557+ assert_eq ! ( first, second) ;
558+ assert_eq ! ( cache. get_cache_directory( ) , Some ( first) ) ;
559+ }
560+
561+ #[ test]
562+ fn in_flight_entry_can_repopulate_persistent_cache_after_clear ( ) {
563+ let temp_dir = tempfile:: tempdir ( ) . unwrap ( ) ;
564+ let cache_directory = temp_dir. path ( ) . join ( "cache" ) ;
565+ std:: fs:: create_dir_all ( & cache_directory) . unwrap ( ) ;
566+ let executable = temp_dir. path ( ) . join ( "python" ) ;
567+ std:: fs:: write ( & executable, "python" ) . unwrap ( ) ;
568+ let cache = Arc :: new ( CacheImpl :: new ( Some ( cache_directory) ) ) ;
569+ let entry = cache. create_cache ( executable. clone ( ) ) ;
570+ let entry_guard = entry. lock ( ) . unwrap ( ) ;
571+ let ( clear_done_tx, clear_done_rx) = mpsc:: channel ( ) ;
572+ let clear_cache = cache. clone ( ) ;
573+
574+ let clear_worker = std:: thread:: spawn ( move || {
575+ clear_done_tx. send ( clear_cache. clear ( ) ) . unwrap ( ) ;
576+ } ) ;
577+ clear_done_rx
578+ . recv_timeout ( Duration :: from_secs ( 5 ) )
579+ . expect ( "clear must complete while the old entry is held" )
580+ . unwrap ( ) ;
581+
582+ let expected = environment ( executable. clone ( ) , vec ! [ executable. clone( ) ] ) ;
583+ entry_guard. store ( expected. clone ( ) ) ;
584+ drop ( entry_guard) ;
585+ clear_worker. join ( ) . unwrap ( ) ;
586+
587+ let next_entry = cache. create_cache ( executable) ;
588+ assert ! ( !Arc :: ptr_eq( & entry, & next_entry) ) ;
589+ let repopulated = next_entry
590+ . lock ( )
591+ . unwrap ( )
592+ . get ( )
593+ . expect ( "post-clear write must be visible to the next entry" ) ;
594+ assert_eq ! ( repopulated. executable, expected. executable) ;
595+ assert_eq ! ( repopulated. prefix, expected. prefix) ;
596+ assert_eq ! ( repopulated. version, expected. version) ;
597+ assert_eq ! ( repopulated. symlinks, expected. symlinks) ;
598+ }
526599}
0 commit comments