hermes_core/segment/
tracker.rs1use std::collections::{HashMap, HashSet};
11use std::sync::Arc;
12
13use parking_lot::Mutex;
14
15use crate::segment::SegmentId;
16
17struct TrackerInner {
19 ref_counts: HashMap<String, usize>,
20 pending_deletions: HashMap<String, SegmentId>,
21 scheduled_deletions: HashSet<String>,
23}
24
25pub struct SegmentTracker {
27 inner: Mutex<TrackerInner>,
28}
29
30impl SegmentTracker {
31 pub fn new() -> Self {
33 Self {
34 inner: Mutex::new(TrackerInner {
35 ref_counts: HashMap::new(),
36 pending_deletions: HashMap::new(),
37 scheduled_deletions: HashSet::new(),
38 }),
39 }
40 }
41
42 pub fn register(&self, segment_id: &str) {
44 let mut inner = self.inner.lock();
45 inner.ref_counts.entry(segment_id.to_string()).or_insert(0);
46 }
47
48 pub fn acquire(&self, segment_ids: &[String]) -> Vec<String> {
51 let mut inner = self.inner.lock();
52 let mut acquired = Vec::with_capacity(segment_ids.len());
53 for id in segment_ids {
54 if inner.pending_deletions.contains_key(id) || inner.scheduled_deletions.contains(id) {
55 continue;
56 }
57 *inner.ref_counts.entry(id.clone()).or_insert(0) += 1;
58 acquired.push(id.clone());
59 }
60 acquired
61 }
62
63 pub fn release(&self, segment_ids: &[String]) -> Vec<SegmentId> {
66 let mut inner = self.inner.lock();
67 let mut ready_for_deletion = Vec::new();
68
69 for id in segment_ids {
70 if let Some(count) = inner.ref_counts.get_mut(id) {
71 *count = count.saturating_sub(1);
72
73 if *count == 0
74 && let Some(segment_id) = inner.pending_deletions.remove(id)
75 {
76 inner.ref_counts.remove(id);
77 inner.scheduled_deletions.insert(id.clone());
78 ready_for_deletion.push(segment_id);
79 }
80 }
81 }
82
83 ready_for_deletion
84 }
85
86 pub fn mark_for_deletion(&self, segment_ids: &[String]) -> Vec<SegmentId> {
90 let mut inner = self.inner.lock();
91 let mut ready_for_deletion = Vec::new();
92
93 for id_str in segment_ids {
94 let Some(segment_id) = SegmentId::from_hex(id_str) else {
95 continue;
96 };
97
98 let Some(&ref_count) = inner.ref_counts.get(id_str) else {
100 continue;
101 };
102
103 if ref_count == 0 {
104 inner.ref_counts.remove(id_str);
105 inner.scheduled_deletions.insert(id_str.clone());
106 ready_for_deletion.push(segment_id);
107 } else {
108 inner.pending_deletions.insert(id_str.clone(), segment_id);
109 }
110 }
111
112 ready_for_deletion
113 }
114
115 pub fn is_pending_deletion(&self, segment_id: &str) -> bool {
117 self.inner.lock().pending_deletions.contains_key(segment_id)
118 }
119
120 pub fn is_deletion_protected(&self, segment_id: &str) -> bool {
127 let inner = self.inner.lock();
128 inner.pending_deletions.contains_key(segment_id)
129 || inner.scheduled_deletions.contains(segment_id)
130 }
131
132 pub fn complete_deletion(&self, segment_ids: &[SegmentId]) {
137 let mut inner = self.inner.lock();
138 for segment_id in segment_ids {
139 inner.scheduled_deletions.remove(&segment_id.to_hex());
140 }
141 }
142
143 pub fn ref_count(&self, segment_id: &str) -> usize {
145 self.inner
146 .lock()
147 .ref_counts
148 .get(segment_id)
149 .copied()
150 .unwrap_or(0)
151 }
152}
153
154impl Default for SegmentTracker {
155 fn default() -> Self {
156 Self::new()
157 }
158}
159
160pub struct SegmentSnapshot {
165 tracker: Arc<SegmentTracker>,
166 segment_ids: Vec<String>,
167 delete_fn: Option<Arc<dyn Fn(Vec<SegmentId>) + Send + Sync>>,
169}
170
171impl SegmentSnapshot {
172 pub fn new(tracker: Arc<SegmentTracker>, segment_ids: Vec<String>) -> Self {
174 Self {
175 tracker,
176 segment_ids,
177 delete_fn: None,
178 }
179 }
180
181 pub fn with_delete_fn(
183 tracker: Arc<SegmentTracker>,
184 segment_ids: Vec<String>,
185 delete_fn: Arc<dyn Fn(Vec<SegmentId>) + Send + Sync>,
186 ) -> Self {
187 Self {
188 tracker,
189 segment_ids,
190 delete_fn: Some(delete_fn),
191 }
192 }
193
194 pub fn segment_ids(&self) -> &[String] {
196 &self.segment_ids
197 }
198
199 pub fn is_empty(&self) -> bool {
201 self.segment_ids.is_empty()
202 }
203
204 pub fn len(&self) -> usize {
206 self.segment_ids.len()
207 }
208}
209
210impl Drop for SegmentSnapshot {
211 fn drop(&mut self) {
212 let to_delete = self.tracker.release(&self.segment_ids);
213 if !to_delete.is_empty() {
214 if let Some(delete_fn) = &self.delete_fn {
215 log::info!(
216 "[segment_snapshot] dropping snapshot, deleting {} deferred segments",
217 to_delete.len()
218 );
219 delete_fn(to_delete);
220 } else {
221 log::warn!(
222 "[segment_snapshot] {} segments ready for deletion but no delete_fn provided",
223 to_delete.len()
224 );
225 }
226 }
227 }
228}
229
230#[cfg(test)]
231mod tests {
232 use super::*;
233
234 const SEG1: &str = "00000000000000000000000000000001";
235 const SEG2: &str = "00000000000000000000000000000002";
236
237 #[test]
238 fn test_tracker_register_and_acquire() {
239 let tracker = SegmentTracker::new();
240
241 tracker.register(SEG1);
242 tracker.register(SEG2);
243
244 let acquired = tracker.acquire(&[SEG1.to_string(), SEG2.to_string()]);
245 assert_eq!(acquired.len(), 2);
246
247 assert_eq!(tracker.ref_count(SEG1), 1);
248 assert_eq!(tracker.ref_count(SEG2), 1);
249 }
250
251 #[test]
252 fn test_tracker_release() {
253 let tracker = SegmentTracker::new();
254
255 tracker.register(SEG1);
256 tracker.acquire(&[SEG1.to_string()]);
257 tracker.acquire(&[SEG1.to_string()]);
258
259 assert_eq!(tracker.ref_count(SEG1), 2);
260
261 tracker.release(&[SEG1.to_string()]);
262 assert_eq!(tracker.ref_count(SEG1), 1);
263
264 tracker.release(&[SEG1.to_string()]);
265 assert_eq!(tracker.ref_count(SEG1), 0);
266 }
267
268 #[test]
269 fn test_tracker_mark_for_deletion_no_refs() {
270 let tracker = SegmentTracker::new();
271
272 tracker.register(SEG1);
273
274 let ready = tracker.mark_for_deletion(&[SEG1.to_string()]);
275 assert_eq!(ready.len(), 1);
276 assert!(!tracker.is_pending_deletion(SEG1));
277 assert!(tracker.is_deletion_protected(SEG1));
278
279 tracker.complete_deletion(&ready);
280 assert!(!tracker.is_deletion_protected(SEG1));
281 }
282
283 #[test]
284 fn test_tracker_mark_for_deletion_with_refs() {
285 let tracker = SegmentTracker::new();
286
287 tracker.register(SEG1);
288 tracker.acquire(&[SEG1.to_string()]);
289
290 let ready = tracker.mark_for_deletion(&[SEG1.to_string()]);
291 assert!(ready.is_empty());
292 assert!(tracker.is_pending_deletion(SEG1));
293
294 let deleted = tracker.release(&[SEG1.to_string()]);
295 assert_eq!(deleted.len(), 1);
296 assert!(!tracker.is_pending_deletion(SEG1));
297 assert!(tracker.is_deletion_protected(SEG1));
298
299 tracker.complete_deletion(&deleted);
300 assert!(!tracker.is_deletion_protected(SEG1));
301 }
302
303 #[test]
304 fn test_tracker_double_mark_for_deletion() {
305 let tracker = SegmentTracker::new();
306 tracker.register(SEG1);
307
308 let ready1 = tracker.mark_for_deletion(&[SEG1.to_string()]);
309 assert_eq!(ready1.len(), 1);
310
311 let ready2 = tracker.mark_for_deletion(&[SEG1.to_string()]);
313 assert!(ready2.is_empty());
314 }
315
316 #[test]
317 fn test_tracker_acquire_unregistered() {
318 let tracker = SegmentTracker::new();
319
320 let acquired = tracker.acquire(&[SEG1.to_string()]);
322 assert_eq!(acquired.len(), 1);
323 assert_eq!(tracker.ref_count(SEG1), 1);
324 }
325
326 #[test]
327 fn test_tracker_release_without_acquire() {
328 let tracker = SegmentTracker::new();
329 tracker.register(SEG1);
330
331 let deleted = tracker.release(&[SEG1.to_string()]);
333 assert!(deleted.is_empty());
334 assert_eq!(tracker.ref_count(SEG1), 0);
335 }
336
337 #[test]
338 fn test_snapshot_drop_triggers_deferred_delete() {
339 use std::sync::atomic::{AtomicUsize, Ordering};
340
341 let tracker = Arc::new(SegmentTracker::new());
342 tracker.register(SEG1);
343 tracker.register(SEG2);
344
345 let delete_count = Arc::new(AtomicUsize::new(0));
346 let dc = Arc::clone(&delete_count);
347 let delete_fn: Arc<dyn Fn(Vec<SegmentId>) + Send + Sync> = Arc::new(move |ids| {
348 dc.fetch_add(ids.len(), Ordering::SeqCst);
349 });
350
351 let acquired = tracker.acquire(&[SEG1.to_string(), SEG2.to_string()]);
353 let snapshot =
354 SegmentSnapshot::with_delete_fn(Arc::clone(&tracker), acquired, Arc::clone(&delete_fn));
355
356 let ready = tracker.mark_for_deletion(&[SEG1.to_string(), SEG2.to_string()]);
358 assert!(ready.is_empty());
359 assert!(tracker.is_pending_deletion(SEG1));
360 assert!(tracker.is_pending_deletion(SEG2));
361
362 drop(snapshot);
364 assert_eq!(delete_count.load(Ordering::SeqCst), 2);
365 assert!(!tracker.is_pending_deletion(SEG1));
366 assert!(!tracker.is_pending_deletion(SEG2));
367 assert!(tracker.is_deletion_protected(SEG1));
368 assert!(tracker.is_deletion_protected(SEG2));
369
370 tracker.complete_deletion(&[
371 SegmentId::from_hex(SEG1).unwrap(),
372 SegmentId::from_hex(SEG2).unwrap(),
373 ]);
374 assert!(!tracker.is_deletion_protected(SEG1));
375 assert!(!tracker.is_deletion_protected(SEG2));
376 }
377}