hermes_core/index/
reader.rs1use std::sync::Arc;
7use std::sync::atomic::{AtomicBool, Ordering};
8
9use arc_swap::ArcSwap;
10use parking_lot::RwLock;
11
12use crate::directories::DirectoryWriter;
13use crate::dsl::Schema;
14use crate::error::Result;
15
16use super::Searcher;
17
18struct SearcherState<D: DirectoryWriter + 'static> {
24 searcher: Arc<Searcher<D>>,
25 segment_ids: Vec<String>,
26}
27
28struct ReloadGuard<'a>(&'a AtomicBool);
32
33impl Drop for ReloadGuard<'_> {
34 fn drop(&mut self) {
35 self.0.store(false, Ordering::Release);
36 }
37}
38
39pub struct IndexReader<D: DirectoryWriter + 'static> {
40 schema: Arc<Schema>,
42 segment_manager: Arc<crate::merge::SegmentManager<D>>,
44 state: ArcSwap<SearcherState<D>>,
46 term_cache_blocks: usize,
48 last_reload_check: RwLock<std::time::Instant>,
50 reload_check_interval: std::time::Duration,
52 reloading: AtomicBool,
54}
55
56impl<D: DirectoryWriter + 'static> IndexReader<D> {
57 pub async fn from_segment_manager(
62 schema: Arc<Schema>,
63 segment_manager: Arc<crate::merge::SegmentManager<D>>,
64 term_cache_blocks: usize,
65 reload_interval_ms: u64,
66 ) -> Result<Self> {
67 let initial_segment_ids = segment_manager.get_segment_ids().await;
69
70 let reader = Self::create_reader(&schema, &segment_manager, term_cache_blocks).await?;
71
72 Ok(Self {
73 schema,
74 segment_manager,
75 state: ArcSwap::from_pointee(SearcherState {
76 searcher: Arc::new(reader),
77 segment_ids: initial_segment_ids,
78 }),
79 term_cache_blocks,
80 last_reload_check: RwLock::new(std::time::Instant::now()),
81 reload_check_interval: std::time::Duration::from_millis(reload_interval_ms),
82 reloading: AtomicBool::new(false),
83 })
84 }
85
86 async fn create_reader(
90 schema: &Arc<Schema>,
91 segment_manager: &Arc<crate::merge::SegmentManager<D>>,
92 term_cache_blocks: usize,
93 ) -> Result<Searcher<D>> {
94 let trained = segment_manager.trained();
96 let trained_centroids = trained
97 .as_ref()
98 .map(|t| t.centroids.clone())
99 .unwrap_or_default();
100
101 let snapshot = segment_manager.acquire_snapshot().await;
103
104 Searcher::from_snapshot(
105 segment_manager.directory(),
106 Arc::clone(schema),
107 snapshot,
108 trained_centroids,
109 term_cache_blocks,
110 )
111 .await
112 }
113
114 pub fn set_reload_interval(&mut self, interval: std::time::Duration) {
116 self.reload_check_interval = interval;
117 }
118
119 pub async fn searcher(&self) -> Result<Arc<Searcher<D>>> {
124 let should_check = {
126 let last = self.last_reload_check.read();
127 last.elapsed() >= self.reload_check_interval
128 };
129
130 if should_check {
131 if self
133 .reloading
134 .compare_exchange(false, true, Ordering::Acquire, Ordering::Relaxed)
135 .is_ok()
136 {
137 let _reload_guard = ReloadGuard(&self.reloading);
138 self.do_reload_check().await?;
140 }
141 }
143
144 Ok(Arc::clone(&self.state.load().searcher))
146 }
147
148 async fn do_reload_check(&self) -> Result<()> {
150 *self.last_reload_check.write() = std::time::Instant::now();
151
152 let new_segment_ids = self.segment_manager.get_segment_ids().await;
154
155 let segments_changed = {
157 let state = self.state.load();
158 state.segment_ids != new_segment_ids
159 };
160
161 if segments_changed {
162 let old_count = self.state.load().segment_ids.len();
163 let new_count = new_segment_ids.len();
164 log::info!(
165 "[index_reload] old_count={} new_count={}",
166 old_count,
167 new_count
168 );
169 self.reload_with_segments(new_segment_ids).await?;
170 }
171 Ok(())
172 }
173
174 pub async fn reload(&self) -> Result<()> {
181 loop {
185 if self
186 .reloading
187 .compare_exchange(false, true, Ordering::Acquire, Ordering::Relaxed)
188 .is_ok()
189 {
190 break;
191 }
192 tokio::task::yield_now().await;
193 }
194 let _reload_guard = ReloadGuard(&self.reloading);
195 let new_segment_ids = self.segment_manager.get_segment_ids().await;
196
197 let segments_changed = {
199 let state = self.state.load();
200 state.segment_ids != new_segment_ids
201 };
202
203 if segments_changed {
204 self.reload_with_segments(new_segment_ids).await
205 } else {
206 log::debug!("[reload] segments unchanged, skipping");
207 Ok(())
208 }
209 }
210
211 async fn reload_with_segments(&self, new_segment_ids: Vec<String>) -> Result<()> {
216 let existing_segments: Vec<Arc<crate::segment::SegmentReader>> =
218 self.state.load().searcher.segment_readers().to_vec();
219
220 let trained = self.segment_manager.trained();
222 let trained_centroids = trained
223 .as_ref()
224 .map(|t| t.centroids.clone())
225 .unwrap_or_default();
226
227 let snapshot = self.segment_manager.acquire_snapshot().await;
228
229 let new_reader = Searcher::from_snapshot_reuse(
230 self.segment_manager.directory(),
231 Arc::clone(&self.schema),
232 snapshot,
233 trained_centroids,
234 self.term_cache_blocks,
235 &existing_segments,
236 )
237 .await?;
238
239 self.state.store(Arc::new(SearcherState {
241 searcher: Arc::new(new_reader),
242 segment_ids: new_segment_ids,
243 }));
244
245 Ok(())
246 }
247
248 pub fn schema(&self) -> &Schema {
250 &self.schema
251 }
252}
253
254#[cfg(test)]
255mod tests {
256 use super::*;
257
258 #[test]
259 fn reload_guard_releases_flag_on_unwind() {
260 let reloading = AtomicBool::new(true);
261 let result = std::panic::catch_unwind(|| {
262 let _guard = ReloadGuard(&reloading);
263 panic!("cancel reload");
264 });
265 assert!(result.is_err());
266 assert!(!reloading.load(Ordering::Acquire));
267 }
268}