| 226 | } |
| 227 | |
| 228 | fn evict_if_needed(&self, incoming_size: usize) { |
| 229 | let current_memory = *self.current_memory.read().unwrap(); |
| 230 | let current_entries = self.entries.read().unwrap().len(); |
| 231 | |
| 232 | if current_memory + incoming_size > self.max_memory_bytes |
| 233 | || current_entries >= self.max_entries |
| 234 | { |
| 235 | // Collect candidates for eviction (least recently used with low usage count) |
| 236 | let mut candidates: Vec<(PlanCacheKey, Instant, u64)> = { |
| 237 | let entries = self.entries.read().unwrap(); |
| 238 | entries |
| 239 | .iter() |
| 240 | .map(|(key, entry)| (key.clone(), entry.last_used, entry.usage_count)) |
| 241 | .collect() |
| 242 | }; |
| 243 | |
| 244 | // Sort by last used time, then by usage count |
| 245 | candidates.sort_by(|a, b| match a.1.cmp(&b.1) { |
| 246 | std::cmp::Ordering::Equal => a.2.cmp(&b.2), |
| 247 | other => other, |
| 248 | }); |
| 249 | |
| 250 | // Evict entries until we have enough space |
| 251 | let mut entries = self.entries.write().unwrap(); |
| 252 | for (key, _, _) in candidates { |
| 253 | if let Some(evicted_entry) = entries.remove(&key) { |
| 254 | let evicted_size = evicted_entry.size_bytes(); |
| 255 | |
| 256 | { |
| 257 | let mut current_memory = self.current_memory.write().unwrap(); |
| 258 | *current_memory = current_memory.saturating_sub(evicted_size); |
| 259 | } |
| 260 | |
| 261 | { |
| 262 | let mut stats = self.stats.write().unwrap(); |
| 263 | stats.evictions += 1; |
| 264 | } |
| 265 | |
| 266 | // Check if we have enough space now |
| 267 | let new_current_memory = *self.current_memory.read().unwrap(); |
| 268 | let new_current_entries = entries.len(); |
| 269 | |
| 270 | if new_current_memory + incoming_size <= self.max_memory_bytes |
| 271 | && new_current_entries < self.max_entries |
| 272 | { |
| 273 | break; |
| 274 | } |
| 275 | } |
| 276 | } |
| 277 | } |
| 278 | } |
| 279 | |
| 280 | /// Invalidate plans by schema hash |
| 281 | pub fn invalidate_by_schema(&self, schema_hash: u64) { |