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