lean_ctx/core/
bm25_store.rs1use std::path::Path;
6
7use crate::core::bm25_index::BM25Index;
8
9std::thread_local! {
10 static BM25_SHARED_CACHE: std::cell::RefCell<Option<crate::core::bm25_cache::SharedBm25Cache>> =
11 const { std::cell::RefCell::new(None) };
12}
13
14pub fn set_thread_cache(cache: crate::core::bm25_cache::SharedBm25Cache) {
16 BM25_SHARED_CACHE.with(|c| {
17 *c.borrow_mut() = Some(cache);
18 });
19}
20
21pub fn get_thread_cache() -> Option<crate::core::bm25_cache::SharedBm25Cache> {
25 BM25_SHARED_CACHE.with(|c| c.borrow().clone())
26}
27
28pub enum Bm25LoadResult {
30 Ready(std::sync::Arc<BM25Index>),
31 Building,
32}
33
34pub fn load_or_refresh_bm25(root: &Path) -> Bm25LoadResult {
35 let cached = BM25_SHARED_CACHE.with(|c| {
36 let borrow = c.borrow();
37 borrow
38 .as_ref()
39 .and_then(|cache| crate::core::bm25_cache::get_or_background(cache, root))
40 });
41 if let Some(idx) = cached {
42 return Bm25LoadResult::Ready(idx);
43 }
44
45 let root_str = root.to_string_lossy().to_string();
46
47 if let Some(idx) = crate::core::index_orchestrator::try_load_bm25_index(&root_str) {
56 if crate::core::bm25_index::bm25_index_looks_stale_fast(&idx, root) {
57 tracing::debug!("[bm25_store: stale on-disk index for {root_str}; refreshing]");
58 crate::core::index_orchestrator::ensure_all_background(&root_str);
59 return Bm25LoadResult::Ready(std::sync::Arc::new(idx));
60 }
61 let idx = std::sync::Arc::new(idx);
62 store_in_thread_cache(root, &idx);
63 return Bm25LoadResult::Ready(idx);
64 }
65
66 if crate::core::index_orchestrator::is_building() {
67 return Bm25LoadResult::Building;
68 }
69
70 crate::core::index_orchestrator::ensure_all_background(&root_str);
76
77 let deadline = std::time::Instant::now() + bm25_cold_build_budget();
78 loop {
79 if let Some(idx) = crate::core::index_orchestrator::try_load_bm25_index(&root_str) {
80 let idx = std::sync::Arc::new(idx);
81 store_in_thread_cache(root, &idx);
82 return Bm25LoadResult::Ready(idx);
83 }
84 if std::time::Instant::now() >= deadline {
85 return Bm25LoadResult::Building;
86 }
87 std::thread::sleep(std::time::Duration::from_millis(50));
88 }
89}
90
91pub(crate) fn bm25_cold_build_budget() -> std::time::Duration {
94 let ms = std::env::var("LEAN_CTX_BM25_COLD_BUDGET_MS")
95 .ok()
96 .and_then(|v| v.parse::<u64>().ok())
97 .unwrap_or(60_000);
98 std::time::Duration::from_millis(ms)
99}
100
101pub(crate) fn store_in_thread_cache(root: &Path, idx: &std::sync::Arc<BM25Index>) {
102 BM25_SHARED_CACHE.with(|c| {
103 let borrow = c.borrow();
104 if let Some(cache) = borrow.as_ref() {
105 let mut guard = cache
106 .lock()
107 .unwrap_or_else(std::sync::PoisonError::into_inner);
108 *guard = Some(crate::core::bm25_cache::Bm25CacheEntry {
109 root: root.to_path_buf(),
110 index: std::sync::Arc::clone(idx),
111 loaded_at: std::time::Instant::now(),
112 fingerprint: crate::core::bm25_cache::index_fingerprint(root),
113 });
114 }
115 });
116}
117
118pub fn filtered_candidate_k(top_k: usize, filtered: bool) -> usize {
119 if !filtered {
120 return top_k;
121 }
122 let candidates = (top_k.max(10)).saturating_mul(10);
123 candidates.clamp(50, 500)
124}