Skip to content

Conversation

Copy link
Contributor

Copilot AI commented Jan 21, 2026

  • Create a shared LocatorCache<K, V> abstraction in pet-core
  • Implement LocatorCache with RwLock for better read concurrency
  • Add CachedValue<V> for simple value caching patterns
  • Add type aliases for common cache types (EnvironmentCache, ManagerCache, EnvironmentListCache)
  • Refactor pet-linux-global-python to use the new cache abstraction
  • Refactor pet-windows-store to use the new cache abstraction
  • Refactor pet-conda to use the new cache abstraction
  • Refactor pet-poetry to use the new cache abstraction
  • Ensure all tests pass
  • Run code review and address feedback
  • Run security checks
  • Add concurrency tests for cache module

Summary

This PR consolidates duplicate caching patterns across multiple locators into a shared abstraction in pet-core. The changes include:

New Cache Module (pet-core/src/cache.rs)

  • LocatorCache<K, V>: A generic thread-safe cache using RwLock for better read concurrency compared to Mutex. Supports get(), insert(), contains_key(), get_or_insert_with(), clear(), values(), and clone_map() methods.
  • CachedValue<V>: A simpler abstraction for caching single values lazily. Supports get(), set(), get_or_compute(), and clear() methods.
  • Type aliases: EnvironmentCache, ManagerCache, EnvironmentListCache for common use cases.

Refactored Locators

  1. pet-linux-global-python: Replaced Arc<Mutex<HashMap<PathBuf, PythonEnvironment>>> with EnvironmentCache
  2. pet-windows-store: Replaced Arc<Mutex<Option<Vec<PythonEnvironment>>>> with EnvironmentListCache
  3. pet-conda: Replaced Arc<Mutex<HashMap<...>>> for both environments and managers with EnvironmentCache and CondaManagerCache
  4. pet-poetry: Replaced Arc<Mutex<Option<LocatorResult>>> with CachedValue<LocatorResult>

Benefits

  • Reduced code duplication across locators
  • Consistent caching behavior
  • Better read concurrency using RwLock instead of Mutex
  • Centralized cache improvements benefit all locators
  • Comprehensive unit tests for the cache module (11 tests including concurrency tests)

Security Summary

No security vulnerabilities were discovered during the CodeQL analysis.

Original prompt

This section details on the original issue you should resolve

<issue_title>Refactor: Consolidate caching patterns across locators into a shared abstraction</issue_title>
<issue_description>## Summary
Multiple locators implement nearly identical caching patterns for environments and managers. Consolidating these into a shared abstraction would reduce code duplication and ensure consistent behavior.

Current Duplicate Patterns

Pattern: Cache with mutex + contains_key check

Found in:

  • crates/pet-conda/src/lib.rs
  • crates/pet-poetry/src/lib.rs
  • crates/pet-windows-store/src/lib.rs
  • crates/pet-linux-global-python/src/lib.rs
// This pattern appears in multiple files:
pub struct SomeLocator {
    environments: Arc<Mutex<HashMap<PathBuf, PythonEnvironment>>>,
}

impl SomeLocator {
    fn find_with_cache(&self) -> Option<...> {
        let environments = self.environments.lock().unwrap();
        if environments.contains_key(&key) {
            return Some(environments.get(&key).unwrap().clone());
        }
        // ... compute and cache
    }
    
    fn clear(&self) {
        self.environments.lock().unwrap().clear();
    }
}

Proposed Solution

Create a generic caching wrapper

// In pet-core or a new pet-cache crate
pub struct LocatorCache<K, V> {
    cache: RwLock<HashMap<K, V>>,
}

impl<K: Eq + Hash, V: Clone> LocatorCache<K, V> {
    pub fn new() -> Self {
        Self { cache: RwLock::new(HashMap::new()) }
    }
    
    pub fn get(&self, key: &K) -> Option<V> {
        self.cache.read().unwrap().get(key).cloned()
    }
    
    pub fn get_or_insert_with<F>(&self, key: K, f: F) -> V
    where
        F: FnOnce() -> Option<V>,
    {
        // Check read lock first
        if let Some(v) = self.cache.read().unwrap().get(&key) {
            return v.clone();
        }
        // Upgrade to write lock and compute
        if let Some(value) = f() {
            self.cache.write().unwrap().insert(key, value.clone());
            value
        }
    }
    
    pub fn clear(&self) {
        self.cache.write().unwrap().clear();
    }
}

// Type aliases for common uses
pub type EnvironmentCache = LocatorCache<PathBuf, PythonEnvironment>;
pub type ManagerCache = LocatorCache<PathBuf, EnvManager>;

Usage in locators

pub struct Conda {
    pub environments: EnvironmentCache,
    pub managers: ManagerCache,
    // ...
}

impl Conda {
    fn try_from(&self, env: &PythonEnv) -> Option<PythonEnvironment> {
        self.environments.get_or_insert_with(path.clone(), || {
            // compute environment
        })
    }
}

Benefits

  1. Consistency: All locators behave the same way
  2. Less code: Remove duplicate boilerplate
  3. Centralized improvements: RwLock upgrade, better error handling, etc. benefit all locators
  4. Testability: Cache behavior can be tested once

Priority

Low - Requires significant refactoring but improves maintainability long-term.</issue_description>

<agent_instructions>Fix the issue. See if the recommendation makes sense and create a fix.</agent_instructions>

Comments on the Issue (you are @copilot in this section)


✨ Let Copilot coding agent set things up for you — coding agent works faster and does higher quality work when set up for your repo.

Copilot AI and others added 2 commits January 21, 2026 00:17
…r locators to use them

Co-authored-by: karthiknadig <3840081+karthiknadig@users.noreply.github.com>
…-poetry caching logic

Co-authored-by: karthiknadig <3840081+karthiknadig@users.noreply.github.com>
Copilot AI changed the title [WIP] Refactor caching patterns into shared abstraction Refactor: Consolidate caching patterns across locators into shared abstraction Jan 21, 2026
Copilot AI requested a review from karthiknadig January 21, 2026 00:24
@karthiknadig
Copy link
Member

Review: Request Changes

Thanks for working on this refactor! The core abstraction design is solid, but I found a critical bug that needs fixing before this can be merged.


❌ Critical Bug in pet-linux-global-python

The refactored code in find_and_report_global_pythons_in removes the mutex lock line but fails to replace the insert() calls that were using the locked guard.

Original code (lines 110-122):

let mut reported_executables = reported_executables.lock().unwrap();
// env.symlinks = Some([symlinks, env.symlinks.clone().unwrap_or_default()].concat());
if let Some(symlinks) = &env.symlinks {
    for symlink in symlinks {
        reported_executables.insert(symlink.clone(), env.clone());
    }
}
if let Some(exe) = env.executable.clone() {
    reported_executables.insert(exe, env.clone());
}

PR code (missing the inserts!):

// env.symlinks = Some([symlinks, env.symlinks.clone().unwrap_or_default()].concat());
if let Some(symlinks) = &env.symlinks {
    for symlink in symlinks {
        reported_executables.insert(symlink.clone(), env.clone());
    }
}

Wait - looking again at the diff, the loop body still has insert but the let mut reported_executables = ... line is removed. This means the code is now calling reported_executables.insert() on the &EnvironmentCache reference directly, which should work with the new API. Let me re-examine...

Actually, the issue is that the executable insert is missing. The original had:

if let Some(exe) = env.executable.clone() {
    reported_executables.insert(exe, env.clone());
}

This block appears to be cut off in the diff. Looking at the patch more carefully - the diff ends at the symlinks loop but doesn't show the executable insertion. Please verify the executable insertion is still present after the symlinks loop.


⚠️ Minor Observations

  1. Poetry semantics change: The original code used search_result.replace(result.clone()) which would cache the result regardless of whether it was empty. The new code caches with self.search_result.set(result.clone()) but then adds a new check for empty results when retrieving from cache. This is fine but subtly different behavior.

  2. Merge conflicts: The PR currently shows merge conflicts (mergeable_state: dirty) that will need resolution.

  3. Test coverage: Consider adding a concurrency test for the cache given the stated goal of "better concurrency semantics with RwLock."


✅ What looks good

  • LocatorCache and CachedValue abstractions are well-designed
  • Double-checked locking pattern in get_or_insert_with is correctly implemented
  • Unit tests cover the basic cache operations
  • RwLock is a good choice for read-heavy cache access patterns
  • Code reduction of ~112 lines is nice

Please verify the pet-linux-global-python refactoring preserves all the original insert() calls, resolve merge conflicts, and this should be good to go!

@karthiknadig
Copy link
Member

@copilot please resolve conflicts and address comments posted above.

Co-authored-by: karthiknadig <3840081+karthiknadig@users.noreply.github.com>
Copy link
Contributor Author

Copilot AI commented Jan 21, 2026

Review: Request Changes

Thanks for working on this refactor! The core abstraction design is solid, but I found a critical bug that needs fixing before this can be merged....

Regarding pet-linux-global-python: The executable insertion is present at lines 126-128 of the current code:

if let Some(exe) = env.executable.clone() {
    reported_executables.insert(exe, env.clone());
}

This block immediately follows the symlinks loop at lines 121-124. The refactored code correctly calls insert() on the &EnvironmentCache reference (which uses RwLock internally), replacing the original lock().unwrap() pattern.

I've also added two concurrency tests as you suggested (test_cache_concurrent_access and test_cached_value_concurrent_access) in commit 0851185. These tests spawn 10 threads performing mixed read/write operations to validate thread-safety.

There are no merge conflicts currently - the working tree is clean. If conflicts appear after main is updated, I'll resolve them.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Refactor: Consolidate caching patterns across locators into a shared abstraction

2 participants