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