code_analyze_mcp/
cache.rs1use crate::analyze::FileAnalysisOutput;
2use crate::types::AnalysisMode;
3use lru::LruCache;
4use std::num::NonZeroUsize;
5use std::path::PathBuf;
6use std::sync::{Arc, Mutex};
7use std::time::SystemTime;
8use tracing::{debug, instrument};
9
10#[derive(Debug, Clone, Eq, PartialEq, Hash)]
12pub struct CacheKey {
13 pub path: PathBuf,
14 pub modified: SystemTime,
15 pub mode: AnalysisMode,
16}
17
18fn lock_or_recover<T, F>(
21 mutex: &Mutex<LruCache<CacheKey, Arc<FileAnalysisOutput>>>,
22 recovery: F,
23) -> T
24where
25 F: FnOnce(&mut LruCache<CacheKey, Arc<FileAnalysisOutput>>) -> T,
26{
27 match mutex.lock() {
28 Ok(mut guard) => recovery(&mut guard),
29 Err(poisoned) => {
30 let cache_size = NonZeroUsize::new(100).unwrap();
31 let new_cache = LruCache::new(cache_size);
32 let mut guard = poisoned.into_inner();
33 *guard = new_cache;
34 recovery(&mut guard)
35 }
36 }
37}
38
39pub struct AnalysisCache {
41 cache: Arc<Mutex<LruCache<CacheKey, Arc<FileAnalysisOutput>>>>,
42}
43
44impl AnalysisCache {
45 pub fn new(capacity: usize) -> Self {
47 let cache_size = NonZeroUsize::new(capacity).unwrap_or(NonZeroUsize::new(100).unwrap());
48 Self {
49 cache: Arc::new(Mutex::new(LruCache::new(cache_size))),
50 }
51 }
52
53 #[instrument(skip(self), fields(path = ?key.path))]
55 pub fn get(&self, key: &CacheKey) -> Option<Arc<FileAnalysisOutput>> {
56 lock_or_recover(&self.cache, |guard| {
57 let result = guard.get(key).cloned();
58 let cache_size = guard.len();
59 match result {
60 Some(v) => {
61 debug!(cache_event = "hit", cache_size = cache_size, path = ?key.path);
62 Some(v)
63 }
64 None => {
65 debug!(cache_event = "miss", cache_size = cache_size, path = ?key.path);
66 None
67 }
68 }
69 })
70 }
71
72 #[instrument(skip(self, value), fields(path = ?key.path))]
74 pub fn put(&self, key: CacheKey, value: Arc<FileAnalysisOutput>) {
75 lock_or_recover(&self.cache, |guard| {
76 let push_result = guard.push(key.clone(), value);
77 let cache_size = guard.len();
78 match push_result {
79 None => {
80 debug!(cache_event = "insert", cache_size = cache_size, path = ?key.path);
81 }
82 Some((returned_key, _)) => {
83 if returned_key == key {
84 debug!(cache_event = "update", cache_size = cache_size, path = ?key.path);
85 } else {
86 debug!(cache_event = "eviction", cache_size = cache_size, path = ?key.path, evicted_path = ?returned_key.path);
87 }
88 }
89 }
90 });
91 }
92}
93
94impl Clone for AnalysisCache {
95 fn clone(&self) -> Self {
96 Self {
97 cache: Arc::clone(&self.cache),
98 }
99 }
100}