Skip to content

Commit d13f6f4

Browse files
fix: normalize Conda environment cache keys (Fixes #488) (#490)
## Summary - normalize Conda environment metadata cache keys before lookup and insertion - use normalized keys when removing stale entries and pruning deleted prefixes - preserve caller-requested prefixes on cache hits - add Windows coverage for equivalent separator styles ## Validation - `cargo test -p pet-conda environment_info_cache_` - `.\scripts\rust-precommit.ps1` Fixes #488 --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 3ff38d5 commit d13f6f4

1 file changed

Lines changed: 47 additions & 6 deletions

File tree

crates/pet-conda/src/lib.rs

Lines changed: 47 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -172,24 +172,27 @@ impl Conda {
172172
where
173173
F: FnOnce() -> Option<CondaEnvironmentDetails>,
174174
{
175+
let cache_key = norm_case(path);
175176
let fingerprint_before = CondaEnvironmentFingerprint::from_prefix(path);
176177
if let Some(fingerprint) = &fingerprint_before {
177178
if let Some(cached) = self
178179
.environment_info_cache
179180
.read()
180181
.expect("conda environment info cache lock poisoned")
181-
.get(path)
182+
.get(&cache_key)
182183
.filter(|cached| &cached.fingerprint == fingerprint)
183184
{
184-
return Some(cached.details.clone());
185+
let mut details = cached.details.clone();
186+
details.environment.prefix = Some(path.to_path_buf());
187+
return Some(details);
185188
}
186189
}
187190

188191
let Some(details) = load() else {
189192
self.environment_info_cache
190193
.write()
191194
.expect("conda environment info cache lock poisoned")
192-
.remove(path);
195+
.remove(&cache_key);
193196
return None;
194197
};
195198
let fingerprint_after = CondaEnvironmentFingerprint::from_prefix(path);
@@ -199,14 +202,14 @@ impl Conda {
199202
.expect("conda environment info cache lock poisoned");
200203
if fingerprint_before.is_some() && fingerprint_before == fingerprint_after {
201204
cache.insert(
202-
path.to_path_buf(),
205+
cache_key,
203206
CachedCondaEnvironment {
204207
fingerprint: fingerprint_after.expect("fingerprint checked as present"),
205208
details: details.clone(),
206209
},
207210
);
208211
} else {
209-
cache.remove(path);
212+
cache.remove(&cache_key);
210213
}
211214

212215
Some(details)
@@ -458,7 +461,8 @@ impl Locator for Conda {
458461
}
459462

460463
let possible_conda_envs = get_conda_environment_paths(&env_vars, &executable);
461-
let active_prefixes: HashSet<PathBuf> = possible_conda_envs.iter().cloned().collect();
464+
let active_prefixes: HashSet<PathBuf> =
465+
possible_conda_envs.iter().map(norm_case).collect();
462466
for path in possible_conda_envs {
463467
s.spawn(move || {
464468
let details = self.get_environment_details(&path)?;
@@ -594,4 +598,41 @@ mod tests {
594598

595599
fs::remove_dir_all(prefix).unwrap();
596600
}
601+
602+
#[cfg(windows)]
603+
#[test]
604+
fn environment_info_cache_normalizes_windows_keys() {
605+
static NEXT_ID: AtomicUsize = AtomicUsize::new(0);
606+
607+
let prefix = std::env::temp_dir().join(format!(
608+
"pet-conda-environment-cache-case-{}-{}",
609+
std::process::id(),
610+
NEXT_ID.fetch_add(1, Ordering::Relaxed)
611+
));
612+
let conda_meta = prefix.join("conda-meta");
613+
fs::create_dir_all(&conda_meta).unwrap();
614+
fs::write(conda_meta.join("history"), "history").unwrap();
615+
616+
let alternate_separators = PathBuf::from(prefix.to_string_lossy().replace('\\', "/"));
617+
let environment = EnvironmentApi::new();
618+
let locator = Conda::from(&environment);
619+
let loads = AtomicUsize::new(0);
620+
621+
locator
622+
.get_or_load_environment_details(&prefix, || {
623+
loads.fetch_add(1, Ordering::Relaxed);
624+
Some(test_details(&prefix, 1))
625+
})
626+
.unwrap();
627+
let cached = locator
628+
.get_or_load_environment_details(&alternate_separators, || {
629+
panic!("equivalent Windows paths should reuse the cache")
630+
})
631+
.unwrap();
632+
633+
assert_eq!(loads.load(Ordering::Relaxed), 1);
634+
assert_eq!(cached.environment.prefix, Some(alternate_separators));
635+
636+
fs::remove_dir_all(prefix).unwrap();
637+
}
597638
}

0 commit comments

Comments
 (0)