cranpose_core/
snapshot_pinning.rs1use crate::snapshot_double_index_heap::SnapshotDoubleIndexHeap;
10use crate::snapshot_id_set::{SnapshotId, SnapshotIdSet};
11use std::cell::RefCell;
12
13#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
17pub struct PinHandle(usize);
18
19impl PinHandle {
20 pub const INVALID: PinHandle = PinHandle(0);
22
23 pub fn is_valid(&self) -> bool {
25 self.0 != 0
26 }
27}
28
29struct PinningTable {
31 heap: SnapshotDoubleIndexHeap,
33}
34
35impl PinningTable {
36 fn new() -> Self {
37 Self {
38 heap: SnapshotDoubleIndexHeap::new(),
39 }
40 }
41
42 fn add(&mut self, snapshot_id: SnapshotId) -> PinHandle {
46 let heap_handle = self.heap.add(snapshot_id);
47 PinHandle(heap_handle + 1)
50 }
51
52 fn remove(&mut self, handle: PinHandle) -> bool {
56 if !handle.is_valid() {
57 return false;
58 }
59
60 let heap_handle = handle.0 - 1;
62
63 if heap_handle < usize::MAX {
65 self.heap.remove(heap_handle);
66 true
67 } else {
68 false
69 }
70 }
71
72 fn lowest_pinned(&self) -> Option<SnapshotId> {
76 if self.heap.is_empty() {
77 None
78 } else {
79 Some(self.heap.lowest_or_default(0))
81 }
82 }
83
84 fn pin_count(&self) -> usize {
86 self.heap.len()
87 }
88}
89
90thread_local! {
91 static PINNING_TABLE: RefCell<PinningTable> = RefCell::new(PinningTable::new());
93}
94
95pub fn track_pinning(snapshot_id: SnapshotId, invalid: &SnapshotIdSet) -> PinHandle {
110 let pinned_id = invalid.lowest(snapshot_id);
112
113 PINNING_TABLE.with(|cell| cell.borrow_mut().add(pinned_id))
114}
115
116pub fn release_pinning(handle: PinHandle) {
126 if !handle.is_valid() {
127 return;
128 }
129
130 PINNING_TABLE.with(|cell| {
131 cell.borrow_mut().remove(handle);
132 });
133}
134
135pub fn lowest_pinned_snapshot() -> Option<SnapshotId> {
143 PINNING_TABLE.with(|cell| cell.borrow().lowest_pinned())
144}
145
146pub fn pin_count() -> usize {
149 PINNING_TABLE.with(|cell| cell.borrow().pin_count())
150}
151
152#[cfg(test)]
154pub fn reset_pinning_table() {
155 PINNING_TABLE.with(|cell| {
156 let mut table = cell.borrow_mut();
157 table.heap = SnapshotDoubleIndexHeap::new();
158 });
159}
160
161#[cfg(test)]
162mod tests {
163 use super::*;
164
165 fn setup() {
167 reset_pinning_table();
168 }
169
170 #[test]
171 fn test_invalid_handle() {
172 let handle = PinHandle::INVALID;
173 assert!(!handle.is_valid());
174 assert_eq!(handle.0, 0);
175 }
176
177 #[test]
178 fn test_valid_handle() {
179 setup();
180 let invalid = SnapshotIdSet::new().set(10);
181 let handle = track_pinning(20, &invalid);
182 assert!(handle.is_valid());
183 assert!(handle.0 > 0);
184 }
185
186 #[test]
187 fn test_track_and_release() {
188 setup();
189
190 let invalid = SnapshotIdSet::new().set(10);
191 let handle = track_pinning(20, &invalid);
192
193 assert_eq!(pin_count(), 1);
194 assert_eq!(lowest_pinned_snapshot(), Some(10));
195
196 release_pinning(handle);
197 assert_eq!(pin_count(), 0);
198 assert_eq!(lowest_pinned_snapshot(), None);
199 }
200
201 #[test]
202 fn test_multiple_pins() {
203 setup();
204
205 let invalid1 = SnapshotIdSet::new().set(10);
206 let handle1 = track_pinning(20, &invalid1);
207
208 let invalid2 = SnapshotIdSet::new().set(5).set(15);
209 let handle2 = track_pinning(30, &invalid2);
210
211 assert_eq!(pin_count(), 2);
212 assert_eq!(lowest_pinned_snapshot(), Some(5));
213
214 release_pinning(handle1);
216 assert_eq!(pin_count(), 1);
217 assert_eq!(lowest_pinned_snapshot(), Some(5));
218
219 release_pinning(handle2);
221 assert_eq!(pin_count(), 0);
222 assert_eq!(lowest_pinned_snapshot(), None);
223 }
224
225 #[test]
226 fn test_duplicate_pins() {
227 setup();
228
229 let invalid = SnapshotIdSet::new().set(10);
231 let handle1 = track_pinning(20, &invalid);
232 let handle2 = track_pinning(25, &invalid);
233
234 assert_eq!(pin_count(), 2);
235 assert_eq!(lowest_pinned_snapshot(), Some(10));
236
237 release_pinning(handle1);
239 assert_eq!(pin_count(), 1);
240 assert_eq!(lowest_pinned_snapshot(), Some(10));
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_pin_ordering() {
250 setup();
251
252 let invalid1 = SnapshotIdSet::new().set(30);
254 let _handle1 = track_pinning(40, &invalid1);
255
256 let invalid2 = SnapshotIdSet::new().set(10);
257 let _handle2 = track_pinning(20, &invalid2);
258
259 let invalid3 = SnapshotIdSet::new().set(20);
260 let _handle3 = track_pinning(30, &invalid3);
261
262 assert_eq!(lowest_pinned_snapshot(), Some(10));
264 }
265
266 #[test]
267 fn test_release_invalid_handle() {
268 setup();
269
270 release_pinning(PinHandle::INVALID);
272 assert_eq!(pin_count(), 0);
273 }
274
275 #[test]
276 fn test_empty_invalid_set() {
277 setup();
278
279 let invalid = SnapshotIdSet::new();
281 let handle = track_pinning(100, &invalid);
282
283 assert_eq!(pin_count(), 1);
285 assert_eq!(lowest_pinned_snapshot(), Some(100));
286
287 release_pinning(handle);
288 }
289
290 #[test]
291 fn test_lowest_from_invalid_set() {
292 setup();
293
294 let invalid = SnapshotIdSet::new().set(5).set(10).set(15).set(20);
296 let handle = track_pinning(25, &invalid);
297
298 assert_eq!(lowest_pinned_snapshot(), Some(5));
300
301 release_pinning(handle);
302 }
303
304 #[test]
305 fn test_concurrent_snapshots() {
306 setup();
307
308 let handles: Vec<_> = (0..10)
310 .map(|i| {
311 let invalid = SnapshotIdSet::new().set(i * 10);
312 track_pinning(i * 10 + 5, &invalid)
313 })
314 .collect();
315
316 assert_eq!(pin_count(), 10);
317 assert_eq!(lowest_pinned_snapshot(), Some(0));
318
319 for handle in handles {
321 release_pinning(handle);
322 }
323
324 assert_eq!(pin_count(), 0);
325 assert_eq!(lowest_pinned_snapshot(), None);
326 }
327
328 #[test]
329 fn test_heap_handle_based_removal() {
330 setup();
331
332 let invalid1 = SnapshotIdSet::new().set(42);
334 let invalid2 = SnapshotIdSet::new().set(17);
335 let invalid3 = SnapshotIdSet::new().set(99);
336
337 let h1 = track_pinning(50, &invalid1);
338 let h2 = track_pinning(25, &invalid2);
339 let h3 = track_pinning(100, &invalid3);
340
341 assert_eq!(pin_count(), 3);
342 assert_eq!(lowest_pinned_snapshot(), Some(17));
343
344 release_pinning(h1);
346 assert_eq!(pin_count(), 2);
347 assert_eq!(lowest_pinned_snapshot(), Some(17));
348
349 release_pinning(h2);
351 assert_eq!(pin_count(), 1);
352 assert_eq!(lowest_pinned_snapshot(), Some(99));
353
354 release_pinning(h3);
355 assert!(pin_count() == 0);
356 }
357}