@@ -31,21 +31,39 @@ impl WindowsRegistry {
3131 }
3232 #[ cfg( windows) ]
3333 fn find_with_cache ( & self , reporter : Option < & dyn Reporter > ) -> Option < Arc < LocatorResult > > {
34- let mut result = self
35- . search_result
36- . lock ( )
37- . expect ( "search_result mutex poisoned" ) ;
38- if let Some ( result) = result. as_ref ( ) {
39- return Some ( Arc :: clone ( result) ) ;
34+ // Quick cache check, then drop the lock before doing any expensive
35+ // work. Holding `search_result`'s mutex across `get_registry_pythons`
36+ // would serialize all callers behind one Defender-intercepted
37+ // registry walk and create lock-order risk against any locks the
38+ // reporter / conda locator take downstream (issue #454).
39+ {
40+ let cached = self
41+ . search_result
42+ . lock ( )
43+ . expect ( "search_result mutex poisoned" ) ;
44+ if let Some ( cached) = cached. as_ref ( ) {
45+ return Some ( Arc :: clone ( cached) ) ;
46+ }
4047 }
4148
4249 let outcome = get_registry_pythons ( & self . conda_locator , & reporter) ;
4350 let registry_result = Arc :: new ( outcome. result ) ;
51+
4452 // If any worker thread panicked, the result is potentially partial.
4553 // Skip persisting it so the next refresh can retry the walk instead
4654 // of replaying a stale empty/partial cache forever (issue #454).
4755 if !outcome. had_panic {
48- result. replace ( Arc :: clone ( & registry_result) ) ;
56+ let mut cached = self
57+ . search_result
58+ . lock ( )
59+ . expect ( "search_result mutex poisoned" ) ;
60+ // Re-check under the lock: another caller may have populated
61+ // the cache while we were walking. Prefer their value so all
62+ // callers observe the same identity.
63+ if let Some ( existing) = cached. as_ref ( ) {
64+ return Some ( Arc :: clone ( existing) ) ;
65+ }
66+ cached. replace ( Arc :: clone ( & registry_result) ) ;
4967 }
5068
5169 Some ( registry_result)
@@ -115,12 +133,27 @@ impl Locator for WindowsRegistry {
115133 }
116134 }
117135 #[ cfg( windows) ]
118- if let Some ( result) = self . find_with_cache ( None ) {
119- // Find the same env here
120- for found_env in & result. environments {
121- if let Some ( ref python_executable_path) = found_env. executable {
122- if python_executable_path == & env. executable {
123- return Some ( found_env. clone ( ) ) ;
136+ {
137+ // Read-only cache lookup. We deliberately do NOT trigger a
138+ // registry walk from `try_from`: the walk has reporter-only
139+ // side effects (notably `conda_locator.find_and_report(...)`)
140+ // and `try_from` can't supply a reporter. Populating the
141+ // cache here would let a later `find(reporter)` short-circuit
142+ // on the cache hit and silently drop those conda
143+ // notifications (issue #454).
144+ let cached = {
145+ let result = self
146+ . search_result
147+ . lock ( )
148+ . expect ( "search_result mutex poisoned" ) ;
149+ result. as_ref ( ) . map ( Arc :: clone)
150+ } ;
151+ if let Some ( result) = cached {
152+ for found_env in & result. environments {
153+ if let Some ( ref python_executable_path) = found_env. executable {
154+ if python_executable_path == & env. executable {
155+ return Some ( found_env. clone ( ) ) ;
156+ }
124157 }
125158 }
126159 }
0 commit comments