cranpose_core/
snapshot_pinning.rs1use std::cell::RefCell;
2
3use crate::snapshot_double_index_heap::SnapshotDoubleIndexHeap;
12use crate::{
13 snapshot_double_index_heap::SnapshotDoubleIndexHeapDebugStats,
14 snapshot_id_set::{SnapshotId, SnapshotIdSet},
15};
16
17#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
21pub struct PinHandle(usize);
22
23impl PinHandle {
24 pub const INVALID: PinHandle = PinHandle(0);
26
27 pub fn is_valid(&self) -> bool {
29 self.0 != 0
30 }
31}
32
33struct PinningTable {
35 heap: SnapshotDoubleIndexHeap,
37}
38
39impl PinningTable {
40 fn new() -> Self {
41 Self {
42 heap: SnapshotDoubleIndexHeap::new(),
43 }
44 }
45
46 fn add(&mut self, snapshot_id: SnapshotId) -> PinHandle {
50 let heap_handle = self.heap.add(snapshot_id);
51 PinHandle(heap_handle + 1)
54 }
55
56 fn remove(&mut self, handle: PinHandle) -> bool {
60 if !handle.is_valid() {
61 return false;
62 }
63
64 let heap_handle = handle.0 - 1;
66
67 if heap_handle < usize::MAX {
69 self.heap.remove(heap_handle);
70 true
71 } else {
72 false
73 }
74 }
75
76 fn lowest_pinned(&self) -> Option<SnapshotId> {
80 if self.heap.is_empty() {
81 None
82 } else {
83 Some(self.heap.lowest_or_default(0))
85 }
86 }
87
88 fn pin_count(&self) -> usize {
90 self.heap.len()
91 }
92
93 fn debug_stats(&self) -> SnapshotPinningDebugStats {
94 SnapshotPinningDebugStats {
95 pin_count: self.pin_count(),
96 lowest_pinned_snapshot: self.lowest_pinned(),
97 heap: self.heap.debug_stats(),
98 }
99 }
100}
101
102thread_local! {
103 static PINNING_TABLE: RefCell<PinningTable> = RefCell::new(PinningTable::new());
105}
106
107#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
108pub struct SnapshotPinningDebugStats {
109 pub pin_count: usize,
110 pub lowest_pinned_snapshot: Option<SnapshotId>,
111 pub heap: SnapshotDoubleIndexHeapDebugStats,
112}
113
114pub fn track_pinning(snapshot_id: SnapshotId, invalid: &SnapshotIdSet) -> PinHandle {
129 let pinned_id = invalid.lowest(snapshot_id);
131
132 PINNING_TABLE.with(|cell| cell.borrow_mut().add(pinned_id))
133}
134
135pub fn release_pinning(handle: PinHandle) {
145 if !handle.is_valid() {
146 return;
147 }
148
149 PINNING_TABLE.with(|cell| {
150 cell.borrow_mut().remove(handle);
151 });
152}
153
154pub fn lowest_pinned_snapshot() -> Option<SnapshotId> {
162 PINNING_TABLE.with(|cell| cell.borrow().lowest_pinned())
163}
164
165pub fn pin_count() -> usize {
168 PINNING_TABLE.with(|cell| cell.borrow().pin_count())
169}
170
171pub fn debug_snapshot_pinning_stats() -> SnapshotPinningDebugStats {
172 PINNING_TABLE.with(|cell| cell.borrow().debug_stats())
173}
174
175#[cfg(test)]
177pub fn reset_pinning_table() {
178 PINNING_TABLE.with(|cell| {
179 let mut table = cell.borrow_mut();
180 table.heap = SnapshotDoubleIndexHeap::new();
181 });
182}
183
184#[cfg(test)]
185mod tests {
186 use super::*;
187
188 fn setup() {
190 reset_pinning_table();
191 }
192
193 #[test]
194 fn test_invalid_handle() {
195 let handle = PinHandle::INVALID;
196 assert!(!handle.is_valid());
197 assert_eq!(handle.0, 0);
198 }
199
200 #[test]
201 fn test_valid_handle() {
202 setup();
203 let invalid = SnapshotIdSet::new().set(10);
204 let handle = track_pinning(20, &invalid);
205 assert!(handle.is_valid());
206 assert!(handle.0 > 0);
207 }
208
209 #[test]
210 fn test_track_and_release() {
211 setup();
212
213 let invalid = SnapshotIdSet::new().set(10);
214 let handle = track_pinning(20, &invalid);
215
216 assert_eq!(pin_count(), 1);
217 assert_eq!(lowest_pinned_snapshot(), Some(10));
218
219 release_pinning(handle);
220 assert_eq!(pin_count(), 0);
221 assert_eq!(lowest_pinned_snapshot(), None);
222 }
223
224 #[test]
225 fn test_multiple_pins() {
226 setup();
227
228 let invalid1 = SnapshotIdSet::new().set(10);
229 let handle1 = track_pinning(20, &invalid1);
230
231 let invalid2 = SnapshotIdSet::new().set(5).set(15);
232 let handle2 = track_pinning(30, &invalid2);
233
234 assert_eq!(pin_count(), 2);
235 assert_eq!(lowest_pinned_snapshot(), Some(5));
236
237 release_pinning(handle1);
239 assert_eq!(pin_count(), 1);
240 assert_eq!(lowest_pinned_snapshot(), Some(5));
241
242 release_pinning(handle2);
244 assert_eq!(pin_count(), 0);
245 assert_eq!(lowest_pinned_snapshot(), None);
246 }
247
248 #[test]
249 fn test_duplicate_pins() {
250 setup();
251
252 let invalid = SnapshotIdSet::new().set(10);
254 let handle1 = track_pinning(20, &invalid);
255 let handle2 = track_pinning(25, &invalid);
256
257 assert_eq!(pin_count(), 2);
258 assert_eq!(lowest_pinned_snapshot(), Some(10));
259
260 release_pinning(handle1);
262 assert_eq!(pin_count(), 1);
263 assert_eq!(lowest_pinned_snapshot(), Some(10));
264
265 release_pinning(handle2);
267 assert_eq!(pin_count(), 0);
268 assert_eq!(lowest_pinned_snapshot(), None);
269 }
270
271 #[test]
272 fn test_pin_ordering() {
273 setup();
274
275 let invalid1 = SnapshotIdSet::new().set(30);
277 let _handle1 = track_pinning(40, &invalid1);
278
279 let invalid2 = SnapshotIdSet::new().set(10);
280 let _handle2 = track_pinning(20, &invalid2);
281
282 let invalid3 = SnapshotIdSet::new().set(20);
283 let _handle3 = track_pinning(30, &invalid3);
284
285 assert_eq!(lowest_pinned_snapshot(), Some(10));
287 }
288
289 #[test]
290 fn test_release_invalid_handle() {
291 setup();
292
293 release_pinning(PinHandle::INVALID);
295 assert_eq!(pin_count(), 0);
296 }
297
298 #[test]
299 fn test_empty_invalid_set() {
300 setup();
301
302 let invalid = SnapshotIdSet::new();
304 let handle = track_pinning(100, &invalid);
305
306 assert_eq!(pin_count(), 1);
308 assert_eq!(lowest_pinned_snapshot(), Some(100));
309
310 release_pinning(handle);
311 }
312
313 #[test]
314 fn test_lowest_from_invalid_set() {
315 setup();
316
317 let invalid = SnapshotIdSet::new().set(5).set(10).set(15).set(20);
319 let handle = track_pinning(25, &invalid);
320
321 assert_eq!(lowest_pinned_snapshot(), Some(5));
323
324 release_pinning(handle);
325 }
326
327 #[test]
328 fn test_concurrent_snapshots() {
329 setup();
330
331 let handles: Vec<_> = (0..10)
333 .map(|i| {
334 let invalid = SnapshotIdSet::new().set(i * 10);
335 track_pinning(i * 10 + 5, &invalid)
336 })
337 .collect();
338
339 assert_eq!(pin_count(), 10);
340 assert_eq!(lowest_pinned_snapshot(), Some(0));
341
342 for handle in handles {
344 release_pinning(handle);
345 }
346
347 assert_eq!(pin_count(), 0);
348 assert_eq!(lowest_pinned_snapshot(), None);
349 }
350
351 #[test]
352 fn test_heap_handle_based_removal() {
353 setup();
354
355 let invalid1 = SnapshotIdSet::new().set(42);
357 let invalid2 = SnapshotIdSet::new().set(17);
358 let invalid3 = SnapshotIdSet::new().set(99);
359
360 let h1 = track_pinning(50, &invalid1);
361 let h2 = track_pinning(25, &invalid2);
362 let h3 = track_pinning(100, &invalid3);
363
364 assert_eq!(pin_count(), 3);
365 assert_eq!(lowest_pinned_snapshot(), Some(17));
366
367 release_pinning(h1);
369 assert_eq!(pin_count(), 2);
370 assert_eq!(lowest_pinned_snapshot(), Some(17));
371
372 release_pinning(h2);
374 assert_eq!(pin_count(), 1);
375 assert_eq!(lowest_pinned_snapshot(), Some(99));
376
377 release_pinning(h3);
378 assert!(pin_count() == 0);
379 }
380}