@@ -8,7 +8,6 @@ use pet_core::reporter::Reporter;
88#[ cfg( windows) ]
99use pet_core:: {
1010 arch:: Architecture ,
11- manager:: EnvManager ,
1211 python_environment:: { PythonEnvironmentBuilder , PythonEnvironmentKind } ,
1312 LocatorResult ,
1413} ;
@@ -19,60 +18,155 @@ use std::{path::PathBuf, sync::Arc};
1918#[ cfg( windows) ]
2019use winreg:: RegKey ;
2120
21+ #[ cfg( windows) ]
22+ fn empty_result ( ) -> LocatorResult {
23+ LocatorResult {
24+ environments : vec ! [ ] ,
25+ managers : vec ! [ ] ,
26+ }
27+ }
28+
29+ /// Logs a warning if a spawned registry-walk thread panicked, then
30+ /// substitutes an empty result so the surviving hive/companies still
31+ /// surface their environments. Without this we'd silently lose the entire
32+ /// hive when one company's walk panics — exactly the kind of regression
33+ /// that's hardest to debug after the fact.
34+ #[ cfg( windows) ]
35+ fn join_or_warn ( join_result : std:: thread:: Result < LocatorResult > , label : & str ) -> LocatorResult {
36+ use log:: warn;
37+ match join_result {
38+ Ok ( result) => result,
39+ Err ( panic_payload) => {
40+ // Try to render the payload for the log; payloads are commonly
41+ // either a `&'static str` or a `String`.
42+ let message = panic_payload
43+ . downcast_ref :: < & ' static str > ( )
44+ . map ( |s| ( * s) . to_string ( ) )
45+ . or_else ( || panic_payload. downcast_ref :: < String > ( ) . cloned ( ) )
46+ . unwrap_or_else ( || "<non-string panic payload>" . to_string ( ) ) ;
47+ warn ! ( "Registry walk thread for {} panicked: {}" , label, message) ;
48+ empty_result ( )
49+ }
50+ }
51+ }
52+
2253#[ cfg( windows) ]
2354pub fn get_registry_pythons (
2455 conda_locator : & Arc < dyn CondaLocator > ,
2556 reporter : & Option < & dyn Reporter > ,
2657) -> LocatorResult {
27- use log :: { trace , warn } ;
58+ use std :: thread ;
2859
29- let mut environments = vec ! [ ] ;
30- let mut managers: Vec < EnvManager > = vec ! [ ] ;
60+ // Walk both hives in parallel. Each hive walks its companies in parallel
61+ // too (see `get_registry_pythons_for_hive`). HKLM and HKCU sit on
62+ // independent registry trees and Defender intercepts every read, so the
63+ // serial baseline was paying for both round-trips back to back; the
64+ // scope-spawn pattern matches `pet-pyenv` / `pet-homebrew` / `pet-conda`.
65+ let ( hklm_result, hkcu_result) = thread:: scope ( |s| {
66+ let hklm = s. spawn ( || {
67+ get_registry_pythons_for_hive (
68+ "HKLM" ,
69+ RegKey :: predef ( winreg:: enums:: HKEY_LOCAL_MACHINE ) ,
70+ conda_locator,
71+ reporter,
72+ )
73+ } ) ;
74+ let hkcu = s. spawn ( || {
75+ get_registry_pythons_for_hive (
76+ "HKCU" ,
77+ RegKey :: predef ( winreg:: enums:: HKEY_CURRENT_USER ) ,
78+ conda_locator,
79+ reporter,
80+ )
81+ } ) ;
82+ (
83+ join_or_warn ( hklm. join ( ) , "HKLM" ) ,
84+ join_or_warn ( hkcu. join ( ) , "HKCU" ) ,
85+ )
86+ } ) ;
87+
88+ let mut environments = hklm_result. environments ;
89+ environments. extend ( hkcu_result. environments ) ;
90+ let mut managers = hklm_result. managers ;
91+ managers. extend ( hkcu_result. managers ) ;
3192
32- struct RegistryKey {
33- pub name : & ' static str ,
34- pub key : winreg :: RegKey ,
93+ LocatorResult {
94+ environments ,
95+ managers ,
3596 }
36- let search_keys = [
37- RegistryKey {
38- name : "HKLM" ,
39- key : winreg:: RegKey :: predef ( winreg:: enums:: HKEY_LOCAL_MACHINE ) ,
40- } ,
41- RegistryKey {
42- name : "HKCU" ,
43- key : winreg:: RegKey :: predef ( winreg:: enums:: HKEY_CURRENT_USER ) ,
44- } ,
45- ] ;
46- for ( name, key) in search_keys. iter ( ) . map ( |f| ( f. name , & f. key ) ) {
47- match key. open_subkey ( "Software\\ Python" ) {
48- Ok ( python_key) => {
49- for company in python_key. enum_keys ( ) . filter_map ( Result :: ok) {
50- trace ! ( "Searching {}\\ Software\\ Python\\ {}" , name, company) ;
51- match python_key. open_subkey ( & company) {
52- Ok ( company_key) => {
53- let result = get_registry_pythons_from_key_for_company (
54- name,
55- & company_key,
56- & company,
57- conda_locator,
58- reporter,
59- ) ;
60- managers. extend ( result. managers ) ;
61- environments. extend ( result. environments ) ;
62- }
63- Err ( err) => {
64- warn ! (
65- "Failed to open {}\\ Software\\ Python\\ {}, {:?}" ,
66- name, company, err
67- ) ;
68- }
69- }
70- }
71- }
97+ }
98+
99+ /// Walks `<hive>\Software\Python\<company>` for every company in the given
100+ /// hive. Companies are processed in parallel; each spawned thread owns its
101+ /// own `RegKey` handle (which is `Send` but not `Sync` in `winreg`).
102+ #[ cfg( windows) ]
103+ fn get_registry_pythons_for_hive (
104+ name : & ' static str ,
105+ hive : RegKey ,
106+ conda_locator : & Arc < dyn CondaLocator > ,
107+ reporter : & Option < & dyn Reporter > ,
108+ ) -> LocatorResult {
109+ use log:: { trace, warn} ;
110+ use std:: thread;
111+
112+ let python_key = match hive. open_subkey ( "Software\\ Python" ) {
113+ Ok ( k) => k,
114+ Err ( err) => {
115+ warn ! ( "Failed to open {}\\ Software\\ Python, {:?}" , name, err) ;
116+ return empty_result ( ) ;
117+ }
118+ } ;
119+
120+ // Open each company subkey serially. Opening a registry handle is cheap
121+ // (no recursive enumeration); the heavy work happens once we start
122+ // pulling values out of `<company>\<install>\InstallPath`. Collecting
123+ // owned `(String, RegKey)` pairs lets us hand each company to its own
124+ // thread without sharing a `RegKey` (which is `Send` but not `Sync`).
125+ let companies: Vec < ( String , RegKey ) > = python_key
126+ . enum_keys ( )
127+ . filter_map ( Result :: ok)
128+ . filter_map ( |company| match python_key. open_subkey ( & company) {
129+ Ok ( company_key) => Some ( ( company, company_key) ) ,
72130 Err ( err) => {
73- warn ! ( "Failed to open {}\\ Software\\ Python, {:?}" , name, err)
131+ warn ! (
132+ "Failed to open {}\\ Software\\ Python\\ {}, {:?}" ,
133+ name, company, err
134+ ) ;
135+ None
74136 }
75- }
137+ } )
138+ . collect ( ) ;
139+
140+ let results: Vec < LocatorResult > = thread:: scope ( |s| {
141+ let handles: Vec < _ > = companies
142+ . into_iter ( )
143+ . map ( |( company, company_key) | {
144+ s. spawn ( move || {
145+ // Trace order is intentionally relaxed: companies are
146+ // walked in parallel, so this line interleaves with the
147+ // others from the same hive.
148+ trace ! ( "Searching {}\\ Software\\ Python\\ {}" , name, company) ;
149+ get_registry_pythons_from_key_for_company (
150+ name,
151+ & company_key,
152+ & company,
153+ conda_locator,
154+ reporter,
155+ )
156+ } )
157+ } )
158+ . collect ( ) ;
159+ handles
160+ . into_iter ( )
161+ . map ( |h| join_or_warn ( h. join ( ) , "per-company walk" ) )
162+ . collect ( )
163+ } ) ;
164+
165+ let mut environments = vec ! [ ] ;
166+ let mut managers = vec ! [ ] ;
167+ for r in results {
168+ environments. extend ( r. environments ) ;
169+ managers. extend ( r. managers ) ;
76170 }
77171 LocatorResult {
78172 environments,
0 commit comments