Skip to main content

cranpose_core/
snapshot_pinning.rs

1use crate::{
2    snapshot_double_index_heap::{SnapshotDoubleIndexHeap, SnapshotDoubleIndexHeapDebugStats},
3    snapshot_id_set::{SnapshotId, SnapshotIdSet},
4};
5
6/// A handle to a pinned snapshot. Dropping this handle releases the pin.
7///
8/// Internally stores a heap handle for O(log N) removal.
9#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
10pub struct PinHandle(usize);
11
12impl PinHandle {
13    /// Invalid pin handle constant (0 is reserved as invalid).
14    pub const INVALID: PinHandle = PinHandle(0);
15
16    /// Check if this handle is valid (non-zero).
17    pub fn is_valid(&self) -> bool {
18        self.0 != 0
19    }
20}
21
22pub(crate) struct PinningTable {
23    heap: SnapshotDoubleIndexHeap,
24}
25
26impl PinningTable {
27    pub(crate) const fn new() -> Self {
28        Self {
29            heap: SnapshotDoubleIndexHeap::new(),
30        }
31    }
32
33    fn add(&mut self, snapshot_id: SnapshotId) -> PinHandle {
34        let heap_handle = self.heap.add(snapshot_id);
35        PinHandle(heap_handle + 1)
36    }
37
38    fn remove(&mut self, handle: PinHandle) -> bool {
39        if !handle.is_valid() {
40            return false;
41        }
42
43        let heap_handle = handle.0 - 1;
44
45        if heap_handle < usize::MAX {
46            self.heap.remove(heap_handle);
47            true
48        } else {
49            false
50        }
51    }
52
53    fn lowest_pinned(&self) -> Option<SnapshotId> {
54        if self.heap.is_empty() {
55            None
56        } else {
57            Some(self.heap.lowest_or_default(0))
58        }
59    }
60
61    fn pin_count(&self) -> usize {
62        self.heap.len()
63    }
64
65    fn debug_stats(&self) -> SnapshotPinningDebugStats {
66        SnapshotPinningDebugStats {
67            pin_count: self.pin_count(),
68            lowest_pinned_snapshot: self.lowest_pinned(),
69            heap: self.heap.debug_stats(),
70        }
71    }
72}
73
74#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
75pub struct SnapshotPinningDebugStats {
76    pub pin_count: usize,
77    pub lowest_pinned_snapshot: Option<SnapshotId>,
78    pub heap: SnapshotDoubleIndexHeapDebugStats,
79}
80
81/// Pin a snapshot and its invalid set, returning a handle.
82///
83/// This should be called when a snapshot is created to ensure that state records
84/// from the pinned snapshot and all its dependencies remain valid.
85///
86/// # Arguments
87/// * `snapshot_id` - The ID of the snapshot being created
88/// * `invalid` - The set of invalid snapshot IDs for this snapshot
89///
90/// # Returns
91/// A pin handle that should be released when the snapshot is disposed.
92///
93/// # Time Complexity
94/// O(log N) where N is the number of pinned snapshots
95pub fn track_pinning(snapshot_id: SnapshotId, invalid: &SnapshotIdSet) -> PinHandle {
96    let pinned_id = invalid.lowest(snapshot_id);
97
98    crate::thread_state::snapshots(|state| state.pinning.borrow_mut().add(pinned_id))
99}
100
101/// Release a pinned snapshot.
102///
103/// # Arguments
104/// * `handle` - The pin handle returned by `track_pinning`
105///
106/// This must be called while holding the appropriate lock (sync).
107///
108/// # Time Complexity
109/// O(log N) where N is the number of pinned snapshots
110pub fn release_pinning(handle: PinHandle) {
111    if !handle.is_valid() {
112        return;
113    }
114
115    crate::thread_state::snapshots(|state| {
116        state.pinning.borrow_mut().remove(handle);
117    });
118}
119
120/// Get the lowest currently pinned snapshot ID.
121///
122/// This is used to determine which state records can be safely garbage collected.
123/// Any state records from snapshots older than this ID are still potentially in use.
124///
125/// # Time Complexity
126/// O(1)
127pub fn lowest_pinned_snapshot() -> Option<SnapshotId> {
128    crate::thread_state::snapshots(|state| state.pinning.borrow().lowest_pinned())
129}
130
131/// Get the current count of pinned snapshots (for testing).
132/// Get the current count of pinned snapshots (for testing/debugging).
133pub fn pin_count() -> usize {
134    crate::thread_state::snapshots(|state| state.pinning.borrow().pin_count())
135}
136
137pub fn debug_snapshot_pinning_stats() -> SnapshotPinningDebugStats {
138    crate::thread_state::snapshots(|state| state.pinning.borrow().debug_stats())
139}
140
141#[cfg(test)]
142pub fn reset_pinning_table() {
143    crate::thread_state::snapshots(|state| {
144        state.pinning.borrow_mut().heap = SnapshotDoubleIndexHeap::new();
145    });
146}
147
148#[cfg(test)]
149#[path = "tests/snapshot_pinning_tests.rs"]
150mod tests;