Skip to content

Commit e7b0d0c

Browse files
committed
fix: read-only try_from cache lookup; release mutex during registry walk (PR #458)
1 parent f602708 commit e7b0d0c

1 file changed

Lines changed: 46 additions & 13 deletions

File tree

  • crates/pet-windows-registry/src

crates/pet-windows-registry/src/lib.rs

Lines changed: 46 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)