@@ -153,6 +153,13 @@ impl Conda {
153153 self . mamba_managers . clear ( ) ;
154154 }
155155
156+ fn prune_environment_info_cache ( & self , active_prefixes : & HashSet < PathBuf > ) {
157+ self . environment_info_cache
158+ . write ( )
159+ . expect ( "conda environment info cache lock poisoned" )
160+ . retain ( |prefix, _| active_prefixes. contains ( prefix) ) ;
161+ }
162+
156163 fn get_environment_details ( & self , path : & Path ) -> Option < CondaEnvironmentDetails > {
157164 self . get_or_load_environment_details ( path, || {
158165 let environment = get_conda_environment_info ( path, & None ) ?;
@@ -517,10 +524,7 @@ impl Locator for Conda {
517524 } ) ;
518525 }
519526
520- self . environment_info_cache
521- . write ( )
522- . expect ( "conda environment info cache lock poisoned" )
523- . retain ( |prefix, _| active_prefixes. contains ( prefix) ) ;
527+ self . prune_environment_info_cache ( & active_prefixes) ;
524528 } ) ;
525529 }
526530}
@@ -552,6 +556,82 @@ mod tests {
552556 }
553557 }
554558
559+ #[ test]
560+ fn environment_info_cache_pruning_retains_active_and_reloads_removed_entries ( ) {
561+ let root = tempfile:: tempdir ( ) . unwrap ( ) ;
562+ let active = root. path ( ) . join ( "active" ) ;
563+ let stale = root. path ( ) . join ( "stale" ) ;
564+ let environment = EnvironmentApi :: new ( ) ;
565+ let shared = Conda :: from ( & environment) ;
566+ let refresh = Conda :: from_shared_environment_cache ( & environment, & shared) ;
567+ let loads = AtomicUsize :: new ( 0 ) ;
568+
569+ for prefix in [ & active, & stale] {
570+ let metadata = prefix. join ( "conda-meta" ) ;
571+ fs:: create_dir_all ( & metadata) . unwrap ( ) ;
572+ fs:: write ( metadata. join ( "history" ) , "unchanged history" ) . unwrap ( ) ;
573+ shared
574+ . get_or_load_environment_details ( prefix, || {
575+ let load = loads. fetch_add ( 1 , Ordering :: Relaxed ) + 1 ;
576+ Some ( test_details ( prefix, load) )
577+ } )
578+ . unwrap ( ) ;
579+ }
580+ assert_eq ! ( shared. environment_info_cache. read( ) . unwrap( ) . len( ) , 2 ) ;
581+
582+ #[ cfg( windows) ]
583+ let active_alias = PathBuf :: from ( active. to_string_lossy ( ) . replace ( '\\' , "/" ) ) ;
584+ #[ cfg( not( windows) ) ]
585+ let active_alias = active. clone ( ) ;
586+ let active_prefixes = HashSet :: from ( [ norm_case ( & active_alias) ] ) ;
587+ refresh. prune_environment_info_cache ( & active_prefixes) ;
588+
589+ {
590+ let cache = shared. environment_info_cache . read ( ) . unwrap ( ) ;
591+ assert_eq ! ( cache. len( ) , 1 ) ;
592+ assert ! ( cache. contains_key( & norm_case( & active) ) ) ;
593+ assert ! ( !cache. contains_key( & norm_case( & stale) ) ) ;
594+ }
595+ let retained = shared
596+ . get_or_load_environment_details ( & active_alias, || {
597+ panic ! ( "active metadata should survive pruning without reloading" )
598+ } )
599+ . unwrap ( ) ;
600+ assert_eq ! ( retained. environment. prefix, Some ( active_alias) ) ;
601+ assert_eq ! ( retained. environment. version. as_deref( ) , Some ( "1" ) ) ;
602+ assert_eq ! ( loads. load( Ordering :: Relaxed ) , 2 ) ;
603+
604+ let reloaded = shared
605+ . get_or_load_environment_details ( & stale, || {
606+ let load = loads. fetch_add ( 1 , Ordering :: Relaxed ) + 1 ;
607+ Some ( test_details ( & stale, load) )
608+ } )
609+ . unwrap ( ) ;
610+ assert_eq ! ( reloaded. environment. version. as_deref( ) , Some ( "3" ) ) ;
611+ assert_eq ! ( loads. load( Ordering :: Relaxed ) , 3 ) ;
612+
613+ refresh. prune_environment_info_cache ( & HashSet :: new ( ) ) ;
614+ assert ! ( shared. environment_info_cache. read( ) . unwrap( ) . is_empty( ) ) ;
615+ for prefix in [ & active, & stale] {
616+ shared
617+ . get_or_load_environment_details ( prefix, || {
618+ let load = loads. fetch_add ( 1 , Ordering :: Relaxed ) + 1 ;
619+ Some ( test_details ( prefix, load) )
620+ } )
621+ . unwrap ( ) ;
622+ }
623+ assert_eq ! ( loads. load( Ordering :: Relaxed ) , 5 ) ;
624+ }
625+
626+ #[ test]
627+ fn environment_info_cache_pruning_empty_cache_does_not_create_entries ( ) {
628+ let root = tempfile:: tempdir ( ) . unwrap ( ) ;
629+ let locator = Conda :: from ( & EnvironmentApi :: new ( ) ) ;
630+ locator. prune_environment_info_cache ( & HashSet :: new ( ) ) ;
631+ locator. prune_environment_info_cache ( & HashSet :: from ( [ norm_case ( root. path ( ) ) ] ) ) ;
632+ assert ! ( locator. environment_info_cache. read( ) . unwrap( ) . is_empty( ) ) ;
633+ }
634+
555635 #[ test]
556636 fn environment_info_cache_is_shared_and_invalidated_by_history_changes ( ) {
557637 static NEXT_ID : AtomicUsize = AtomicUsize :: new ( 0 ) ;
0 commit comments