Skip to main content

cranpose_core/snapshot_v2/
global.rs

1use super::*;
2
3/// The global mutable snapshot.
4///
5/// This is a special singleton snapshot that represents the global state.
6/// All non-nested snapshots implicitly depend on the global snapshot.
7///
8/// # Thread Safety
9/// Contains `Cell<T>` which is not `Send`/`Sync`. This is safe because snapshots
10/// are stored in thread-local storage and never shared across threads. The `Arc`
11/// is used for cheap cloning within a single thread, not for cross-thread sharing.
12#[allow(clippy::arc_with_non_send_sync)]
13pub struct GlobalSnapshot {
14    state: SnapshotState,
15    nested_count: Cell<usize>,
16}
17
18impl GlobalSnapshot {
19    /// Create a new global snapshot.
20    ///
21    /// The global snapshot does NOT pin because it always represents the current state
22    /// and reads the latest records. Pinning would prevent garbage collection.
23    pub fn new(id: SnapshotId, invalid: SnapshotIdSet) -> Arc<Self> {
24        Arc::new(Self {
25            state: SnapshotState::new_with_pinning(id, invalid, None, None, false, false),
26            nested_count: Cell::new(0),
27        })
28    }
29
30    /// Get or create the global snapshot instance.
31    pub fn get_or_create() -> Arc<Self> {
32        GLOBAL_SNAPSHOT.with(|cell| {
33            let mut snapshot = cell.borrow_mut();
34            if let Some(global) = snapshot.as_ref() {
35                return Arc::clone(global);
36            }
37
38            let id = with_runtime(|runtime| runtime.global_snapshot_id());
39            let invalid = super::runtime::open_snapshots();
40            let global = GlobalSnapshot::new(id, invalid);
41            *snapshot = Some(Arc::clone(&global));
42            global
43        })
44    }
45
46    /// Advance the global snapshot to a new ID.
47    pub fn advance(&self, new_id: SnapshotId) {
48        let invalid = super::runtime::advance_global_snapshot(new_id);
49        self.state.id.set(new_id);
50        self.state.invalid.replace(invalid);
51    }
52}
53
54thread_local! {
55    static GLOBAL_SNAPSHOT: RefCell<Option<Arc<GlobalSnapshot>>> = const { RefCell::new(None) };
56}
57
58#[cfg(test)]
59pub(crate) fn clear_global_snapshot_for_tests() {
60    GLOBAL_SNAPSHOT.with(|cell| {
61        *cell.borrow_mut() = None;
62    });
63}
64
65impl GlobalSnapshot {
66    pub fn snapshot_id(&self) -> SnapshotId {
67        self.state.id.get()
68    }
69
70    pub fn invalid(&self) -> SnapshotIdSet {
71        self.state.invalid.borrow().clone()
72    }
73
74    pub fn read_only(&self) -> bool {
75        false
76    }
77
78    pub fn root_global(&self) -> Arc<Self> {
79        GlobalSnapshot::get_or_create()
80    }
81
82    pub fn enter<T>(&self, f: impl FnOnce() -> T) -> T {
83        enter_snapshot_scope(AnySnapshot::Global(self.root_global()), f)
84    }
85
86    pub fn take_nested_snapshot(
87        &self,
88        read_observer: Option<ReadObserver>,
89    ) -> Arc<ReadonlySnapshot> {
90        ReadonlySnapshot::new(
91            self.state.id.get(),
92            self.state.invalid.borrow().clone(),
93            read_observer,
94        )
95    }
96
97    pub fn has_pending_changes(&self) -> bool {
98        !self.state.modified.borrow().is_empty()
99    }
100
101    pub fn pending_children(&self) -> Vec<SnapshotId> {
102        self.state.pending_children()
103    }
104
105    pub fn has_pending_children(&self) -> bool {
106        self.state.has_pending_children()
107    }
108
109    pub fn dispose(&self) {}
110
111    pub fn record_read(&self, state: &dyn StateObject) {
112        self.state.record_read(state);
113    }
114
115    pub fn record_write(&self, state: Arc<dyn StateObject>) {
116        self.state.record_write(state, self.state.id.get());
117    }
118
119    pub fn close(&self) {}
120
121    pub fn is_disposed(&self) -> bool {
122        false
123    }
124
125    pub fn apply(&self) -> SnapshotApplyResult {
126        SnapshotApplyResult::Success
127    }
128
129    pub fn take_nested_mutable_snapshot(
130        &self,
131        read_observer: Option<ReadObserver>,
132        write_observer: Option<WriteObserver>,
133    ) -> Arc<MutableSnapshot> {
134        let base_parent_id = self.state.id.get();
135
136        let (new_id, child_invalid, new_global_invalid) =
137            super::runtime::with_runtime(|runtime| runtime.take_new_snapshot_advancing_global());
138
139        let new_global_id = super::runtime::with_runtime(|runtime| runtime.global_snapshot_id());
140        self.state.id.set(new_global_id);
141        self.state.invalid.replace(new_global_invalid);
142
143        let child = MutableSnapshot::from_parts(
144            new_id,
145            child_invalid,
146            read_observer,
147            write_observer,
148            base_parent_id,
149            true,
150        );
151
152        self.nested_count.set(self.nested_count.get() + 1);
153        self.state.add_pending_child(new_id);
154
155        child.set_on_dispose(clear_nested_child_on_dispose(&self.root_global(), new_id));
156
157        child
158    }
159}
160
161impl NestedMutableHost for GlobalSnapshot {
162    fn snapshot_state(&self) -> &SnapshotState {
163        &self.state
164    }
165
166    fn nested_count(&self) -> &Cell<usize> {
167        &self.nested_count
168    }
169}
170
171/// Advance the global snapshot to a new ID.
172pub fn advance_global_snapshot(new_id: SnapshotId) {
173    let global = GlobalSnapshot::get_or_create();
174    global.advance(new_id);
175    super::maybe_check_and_overwrite_unused_records_locked(new_id);
176}
177
178#[cfg(test)]
179pub fn global_snapshot_id() -> SnapshotId {
180    let global = GlobalSnapshot::get_or_create();
181    global.snapshot_id()
182}
183
184#[cfg(test)]
185mod tests {
186    use super::*;
187    use crate::snapshot_v2::runtime::TestRuntimeGuard;
188
189    fn reset_runtime() -> TestRuntimeGuard {
190        let guard = reset_runtime_for_tests();
191        GLOBAL_SNAPSHOT.with(|cell| {
192            *cell.borrow_mut() = None;
193        });
194        guard
195    }
196
197    #[test]
198    fn test_global_snapshot_creation() {
199        let _guard = reset_runtime();
200        let snapshot = GlobalSnapshot::new(1, SnapshotIdSet::new());
201        assert_eq!(snapshot.snapshot_id(), 1);
202        assert!(!snapshot.read_only());
203        assert!(!snapshot.is_disposed());
204    }
205
206    #[test]
207    fn test_global_snapshot_get_or_create() {
208        let _guard = reset_runtime();
209
210        let snapshot1 = GlobalSnapshot::get_or_create();
211        let snapshot2 = GlobalSnapshot::get_or_create();
212
213        assert_eq!(snapshot1.snapshot_id(), snapshot2.snapshot_id());
214    }
215
216    #[test]
217    fn test_global_snapshot_advance() {
218        let _guard = reset_runtime();
219        let snapshot = GlobalSnapshot::new(1, SnapshotIdSet::new());
220        assert_eq!(snapshot.snapshot_id(), 1);
221
222        snapshot.state.id.set(5);
223        assert_eq!(snapshot.snapshot_id(), 5);
224
225        snapshot.state.id.set(10);
226        assert_eq!(snapshot.snapshot_id(), 10);
227    }
228
229    #[test]
230    fn test_global_snapshot_never_disposed() {
231        let _guard = reset_runtime();
232        let snapshot = GlobalSnapshot::new(1, SnapshotIdSet::new());
233        assert!(!snapshot.is_disposed());
234
235        snapshot.dispose();
236        assert!(!snapshot.is_disposed());
237    }
238
239    #[test]
240    fn test_global_snapshot_apply_always_succeeds() {
241        let _guard = reset_runtime();
242        let snapshot = GlobalSnapshot::new(1, SnapshotIdSet::new());
243        let result = snapshot.apply();
244        assert!(result.is_success());
245    }
246
247    #[test]
248    fn test_global_snapshot_nested() {
249        let _guard = reset_runtime();
250        let global = GlobalSnapshot::new(1, SnapshotIdSet::new());
251        let nested = global.take_nested_snapshot(None);
252
253        assert_eq!(nested.snapshot_id(), 1);
254        assert!(nested.read_only());
255        assert_eq!(global.nested_count.get(), 0);
256    }
257
258    #[test]
259    fn test_global_snapshot_nested_mutable() {
260        let _guard = reset_runtime();
261        let global = GlobalSnapshot::new(1, SnapshotIdSet::new());
262        let nested = global.take_nested_mutable_snapshot(None, None);
263
264        assert!(nested.snapshot_id() < global.snapshot_id());
265        assert!(!nested.read_only());
266    }
267
268    #[test]
269    fn test_global_snapshot_nested_mutable_dispose_clears_invalid() {
270        let _guard = reset_runtime();
271        let global = GlobalSnapshot::get_or_create();
272        let nested = global.take_nested_mutable_snapshot(None, None);
273        let child_id = nested.snapshot_id();
274
275        assert!(global.state.invalid.borrow().get(child_id));
276        assert_eq!(global.nested_count.get(), 1);
277
278        nested.dispose();
279
280        assert_eq!(global.nested_count.get(), 0);
281        assert!(!global.state.invalid.borrow().get(child_id));
282    }
283
284    #[test]
285    fn test_advance_global_snapshot_function() {
286        let _guard = reset_runtime();
287
288        let initial_id = global_snapshot_id();
289
290        advance_global_snapshot(initial_id + 10);
291        assert_eq!(global_snapshot_id(), initial_id + 10);
292
293        advance_global_snapshot(initial_id + 20);
294        assert_eq!(global_snapshot_id(), initial_id + 20);
295    }
296
297    #[test]
298    fn test_global_snapshot_has_no_pending_changes_initially() {
299        let _guard = reset_runtime();
300        let snapshot = GlobalSnapshot::new(1, SnapshotIdSet::new());
301        assert!(!snapshot.has_pending_changes());
302    }
303
304    #[test]
305    fn test_global_snapshot_enter() {
306        let _guard = reset_runtime();
307        let snapshot = GlobalSnapshot::new(1, SnapshotIdSet::new());
308
309        set_current_snapshot(None);
310        snapshot.enter(|| {
311            let current = current_snapshot();
312            assert!(current.is_some());
313        });
314    }
315}