|
4 | 4 | //! with caching support for performance in monorepo environments. |
5 | 5 |
|
6 | 6 | use std::collections::HashMap; |
| 7 | +use std::hash::Hash; |
7 | 8 | use std::path::{Path, PathBuf}; |
8 | 9 | use std::sync::{LazyLock, RwLock}; |
9 | 10 |
|
10 | 11 | use tracing::{debug, trace}; |
11 | 12 |
|
| 13 | +/// Maximum number of entries in each cache to prevent unbounded memory growth. |
| 14 | +const MAX_CACHE_SIZE: usize = 1000; |
| 15 | + |
12 | 16 | /// Cache for discovered config paths. |
13 | 17 | /// Key is the start directory, value is the found config path (or None). |
14 | 18 | static CONFIG_CACHE: LazyLock<RwLock<HashMap<PathBuf, Option<PathBuf>>>> = |
15 | | - LazyLock::new(|| RwLock::new(HashMap::new())); |
| 19 | + LazyLock::new(|| RwLock::new(HashMap::with_capacity(MAX_CACHE_SIZE))); |
16 | 20 |
|
17 | 21 | /// Cache for project roots. |
18 | 22 | /// Key is the start directory, value is the project root path. |
19 | 23 | static PROJECT_ROOT_CACHE: LazyLock<RwLock<HashMap<PathBuf, Option<PathBuf>>>> = |
20 | | - LazyLock::new(|| RwLock::new(HashMap::new())); |
| 24 | + LazyLock::new(|| RwLock::new(HashMap::with_capacity(MAX_CACHE_SIZE))); |
| 25 | + |
| 26 | +/// Insert a key-value pair into the cache with eviction when full. |
| 27 | +/// When the cache reaches MAX_CACHE_SIZE, removes an arbitrary entry before inserting. |
| 28 | +fn insert_with_eviction<K: Eq + Hash + Clone, V>(cache: &mut HashMap<K, V>, key: K, value: V) { |
| 29 | + if cache.len() >= MAX_CACHE_SIZE { |
| 30 | + // Remove first entry (simple eviction strategy) |
| 31 | + if let Some(k) = cache.keys().next().cloned() { |
| 32 | + cache.remove(&k); |
| 33 | + } |
| 34 | + } |
| 35 | + cache.insert(key, value); |
| 36 | +} |
21 | 37 |
|
22 | 38 | /// Markers that indicate a project root directory. |
23 | 39 | const PROJECT_ROOT_MARKERS: &[&str] = &[ |
@@ -57,9 +73,9 @@ pub fn find_up(start_dir: &Path, filename: &str) -> Option<PathBuf> { |
57 | 73 |
|
58 | 74 | let result = find_up_uncached(start_dir, filename); |
59 | 75 |
|
60 | | - // Store in cache |
| 76 | + // Store in cache with eviction when full |
61 | 77 | if let Ok(mut cache) = CONFIG_CACHE.write() { |
62 | | - cache.insert(cache_key, result.clone()); |
| 78 | + insert_with_eviction(&mut cache, cache_key, result.clone()); |
63 | 79 | } |
64 | 80 |
|
65 | 81 | result |
@@ -169,9 +185,9 @@ pub fn find_project_root(start_dir: &Path) -> Option<PathBuf> { |
169 | 185 |
|
170 | 186 | let result = find_project_root_uncached(start_dir); |
171 | 187 |
|
172 | | - // Store in cache |
| 188 | + // Store in cache with eviction when full |
173 | 189 | if let Ok(mut cache) = PROJECT_ROOT_CACHE.write() { |
174 | | - cache.insert(start_dir.to_path_buf(), result.clone()); |
| 190 | + insert_with_eviction(&mut cache, start_dir.to_path_buf(), result.clone()); |
175 | 191 | } |
176 | 192 |
|
177 | 193 | result |
|
0 commit comments