@@ -26,23 +26,45 @@ fn empty_result() -> LocatorResult {
2626 }
2727}
2828
29+ /// What a single company-level worker thread produces: the registry-derived
30+ /// `PythonEnvironment`s plus the install dirs that turned out to be conda
31+ /// roots. We capture conda dirs (instead of just calling
32+ /// `conda_locator.find_and_report` and forgetting) so that subsequent
33+ /// `find()` calls on a cache hit can replay them — otherwise registry-only
34+ /// conda installs would silently disappear after the first refresh.
35+ #[ cfg( windows) ]
36+ struct CompanyWalkOutcome {
37+ result : LocatorResult ,
38+ conda_install_dirs : Vec < PathBuf > ,
39+ }
40+
41+ #[ cfg( windows) ]
42+ impl CompanyWalkOutcome {
43+ fn empty ( ) -> Self {
44+ Self {
45+ result : empty_result ( ) ,
46+ conda_install_dirs : vec ! [ ] ,
47+ }
48+ }
49+ }
50+
2951/// Logs a warning if a spawned registry-walk thread panicked, then
30- /// substitutes an empty result so the surviving hive/companies still
52+ /// substitutes an empty outcome so the surviving hive/companies still
3153/// surface their environments. Without this we'd silently lose the entire
3254/// hive when one company's walk panics — exactly the kind of regression
3355/// that's hardest to debug after the fact.
3456///
35- /// Returns `(result , had_panic)` so callers can decide not to cache a
57+ /// Returns `(outcome , had_panic)` so callers can decide not to cache a
3658/// partial result (otherwise a single transient panic would persist as a
3759/// stale empty/partial cache across refreshes — see issue #454).
3860#[ cfg( windows) ]
3961fn join_or_warn (
40- join_result : std:: thread:: Result < LocatorResult > ,
62+ join_result : std:: thread:: Result < CompanyWalkOutcome > ,
4163 label : & str ,
42- ) -> ( LocatorResult , bool ) {
64+ ) -> ( CompanyWalkOutcome , bool ) {
4365 use log:: warn;
4466 match join_result {
45- Ok ( result ) => ( result , false ) ,
67+ Ok ( outcome ) => ( outcome , false ) ,
4668 Err ( panic_payload) => {
4769 // Try to render the payload for the log; payloads are commonly
4870 // either a `&'static str` or a `String`.
@@ -52,18 +74,21 @@ fn join_or_warn(
5274 . or_else ( || panic_payload. downcast_ref :: < String > ( ) . cloned ( ) )
5375 . unwrap_or_else ( || "<non-string panic payload>" . to_string ( ) ) ;
5476 warn ! ( "Registry walk thread for {} panicked: {}" , label, message) ;
55- ( empty_result ( ) , true )
77+ ( CompanyWalkOutcome :: empty ( ) , true )
5678 }
5779 }
5880}
5981
60- /// Outcome of a registry walk: the discovered environments/managers and a
61- /// flag indicating whether any worker thread panicked. Callers should
62- /// avoid caching a result with `had_panic = true` so a transient failure
63- /// can be retried on the next refresh instead of becoming sticky.
82+ /// Outcome of a registry walk: the discovered environments/managers, the
83+ /// conda install dirs found via the registry (so cache-hit replays can
84+ /// re-invoke `conda_locator.find_and_report`), and a flag indicating
85+ /// whether any worker thread panicked. Callers should avoid caching a
86+ /// result with `had_panic = true` so a transient failure can be retried
87+ /// on the next refresh instead of becoming sticky.
6488#[ cfg( windows) ]
6589pub struct RegistryWalkOutcome {
6690 pub result : LocatorResult ,
91+ pub conda_install_dirs : Vec < PathBuf > ,
6792 pub had_panic : bool ,
6893}
6994
@@ -79,7 +104,7 @@ pub fn get_registry_pythons(
79104 // independent registry trees and Defender intercepts every read, so the
80105 // serial baseline was paying for both round-trips back to back; the
81106 // scope-spawn pattern matches `pet-pyenv` / `pet-homebrew` / `pet-conda`.
82- let ( ( hklm_result , hklm_panic ) , ( hkcu_result , hkcu_panic ) ) = thread:: scope ( |s| {
107+ let ( hklm_outcome , hkcu_outcome ) = thread:: scope ( |s| {
83108 let hklm = s. spawn ( || {
84109 get_registry_pythons_for_hive (
85110 "HKLM" ,
@@ -102,39 +127,46 @@ pub fn get_registry_pythons(
102127 )
103128 } ) ;
104129
105- let mut environments = hklm_result. environments ;
106- environments. extend ( hkcu_result. environments ) ;
107- let mut managers = hklm_result. managers ;
108- managers. extend ( hkcu_result. managers ) ;
130+ let mut environments = hklm_outcome. result . environments ;
131+ environments. extend ( hkcu_outcome. result . environments ) ;
132+ let mut managers = hklm_outcome. result . managers ;
133+ managers. extend ( hkcu_outcome. result . managers ) ;
134+ let mut conda_install_dirs = hklm_outcome. conda_install_dirs ;
135+ conda_install_dirs. extend ( hkcu_outcome. conda_install_dirs ) ;
109136
110137 RegistryWalkOutcome {
111138 result : LocatorResult {
112139 environments,
113140 managers,
114141 } ,
115- had_panic : hklm_panic || hkcu_panic,
142+ conda_install_dirs,
143+ had_panic : hklm_outcome. had_panic || hkcu_outcome. had_panic ,
116144 }
117145}
118146
119147/// Sibling of `join_or_warn` for hive-level threads. Returns the recovered
120- /// `LocatorResult` plus a `had_panic` flag that already accounts for any
121- /// company- level panics propagated through `RegistryWalkOutcome` .
148+ /// outcome (already aggregating any company-level panics) so the top
149+ /// level can OR the panic flags and concatenate the conda install dirs .
122150#[ cfg( windows) ]
123151fn join_hive_outcome (
124152 join_result : std:: thread:: Result < RegistryWalkOutcome > ,
125153 label : & str ,
126- ) -> ( LocatorResult , bool ) {
154+ ) -> RegistryWalkOutcome {
127155 use log:: warn;
128156 match join_result {
129- Ok ( outcome) => ( outcome. result , outcome . had_panic ) ,
157+ Ok ( outcome) => outcome,
130158 Err ( panic_payload) => {
131159 let message = panic_payload
132160 . downcast_ref :: < & ' static str > ( )
133161 . map ( |s| ( * s) . to_string ( ) )
134162 . or_else ( || panic_payload. downcast_ref :: < String > ( ) . cloned ( ) )
135163 . unwrap_or_else ( || "<non-string panic payload>" . to_string ( ) ) ;
136164 warn ! ( "Registry walk thread for {} panicked: {}" , label, message) ;
137- ( empty_result ( ) , true )
165+ RegistryWalkOutcome {
166+ result : empty_result ( ) ,
167+ conda_install_dirs : vec ! [ ] ,
168+ had_panic : true ,
169+ }
138170 }
139171 }
140172}
@@ -158,6 +190,7 @@ fn get_registry_pythons_for_hive(
158190 warn ! ( "Failed to open {}\\ Software\\ Python, {:?}" , name, err) ;
159191 return RegistryWalkOutcome {
160192 result : empty_result ( ) ,
193+ conda_install_dirs : vec ! [ ] ,
161194 had_panic : false ,
162195 } ;
163196 }
@@ -183,7 +216,7 @@ fn get_registry_pythons_for_hive(
183216 } )
184217 . collect ( ) ;
185218
186- let results: Vec < ( LocatorResult , bool ) > = thread:: scope ( |s| {
219+ let results: Vec < ( CompanyWalkOutcome , bool ) > = thread:: scope ( |s| {
187220 let handles: Vec < _ > = companies
188221 . into_iter ( )
189222 . map ( |( company, company_key) | {
@@ -214,17 +247,20 @@ fn get_registry_pythons_for_hive(
214247
215248 let mut environments = vec ! [ ] ;
216249 let mut managers = vec ! [ ] ;
250+ let mut conda_install_dirs = vec ! [ ] ;
217251 let mut had_panic = false ;
218- for ( r, panicked) in results {
219- environments. extend ( r. environments ) ;
220- managers. extend ( r. managers ) ;
252+ for ( outcome, panicked) in results {
253+ environments. extend ( outcome. result . environments ) ;
254+ managers. extend ( outcome. result . managers ) ;
255+ conda_install_dirs. extend ( outcome. conda_install_dirs ) ;
221256 had_panic |= panicked;
222257 }
223258 RegistryWalkOutcome {
224259 result : LocatorResult {
225260 environments,
226261 managers,
227262 } ,
263+ conda_install_dirs,
228264 had_panic,
229265 }
230266}
@@ -236,12 +272,13 @@ fn get_registry_pythons_from_key_for_company(
236272 company : & str ,
237273 conda_locator : & Arc < dyn CondaLocator > ,
238274 reporter : & Option < & dyn Reporter > ,
239- ) -> LocatorResult {
275+ ) -> CompanyWalkOutcome {
240276 use log:: { trace, warn} ;
241277 use pet_conda:: utils:: is_conda_env;
242278 use pet_fs:: path:: norm_case;
243279
244280 let mut environments = vec ! [ ] ;
281+ let mut conda_install_dirs = vec ! [ ] ;
245282 // let company_display_name: Option<String> = company_key.get_value("DisplayName").ok();
246283 for installed_python in company_key. enum_keys ( ) . filter_map ( Result :: ok) {
247284 match company_key. open_subkey ( installed_python. clone ( ) ) {
@@ -281,6 +318,15 @@ fn get_registry_pythons_from_key_for_company(
281318 if let Some ( reporter) = reporter {
282319 conda_locator. find_and_report ( * reporter, & env_path) ;
283320 }
321+ // Capture the dir even when no reporter is
322+ // attached (e.g. cache-warming via
323+ // `find_with_cache(None)`) so a later cache
324+ // hit in `find()` can replay
325+ // `conda_locator.find_and_report` against
326+ // each dir; without this, registry-only conda
327+ // installs would silently disappear from
328+ // every refresh after the first (#454).
329+ conda_install_dirs. push ( env_path) ;
284330 continue ;
285331 }
286332
@@ -366,8 +412,11 @@ fn get_registry_pythons_from_key_for_company(
366412 }
367413 }
368414
369- LocatorResult {
370- environments,
371- managers : vec ! [ ] ,
415+ CompanyWalkOutcome {
416+ result : LocatorResult {
417+ environments,
418+ managers : vec ! [ ] ,
419+ } ,
420+ conda_install_dirs,
372421 }
373422}
0 commit comments