Skip to main content

cranpose_core/snapshot_v2/
nested.rs

1use super::*;
2
3/// A nested read-only snapshot.
4///
5/// This is a read-only snapshot that has a parent snapshot. It inherits
6/// the parent's invalid set and can be disposed independently.
7///
8/// # Thread Safety
9/// Contains `Cell<T>` and `RefCell<T>` which are not `Send`/`Sync`. This is safe because
10/// snapshots 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 NestedReadonlySnapshot {
14    state: SnapshotState,
15    parent: Weak<NestedReadonlySnapshot>,
16}
17
18impl NestedReadonlySnapshot {
19    pub fn new(
20        id: SnapshotId,
21        invalid: SnapshotIdSet,
22        read_observer: Option<ReadObserver>,
23        parent: Weak<NestedReadonlySnapshot>,
24    ) -> Arc<Self> {
25        Arc::new(Self {
26            state: SnapshotState::new(id, invalid, read_observer, None, false),
27            parent,
28        })
29    }
30
31    pub fn snapshot_id(&self) -> SnapshotId {
32        self.state.id.get()
33    }
34
35    pub fn invalid(&self) -> SnapshotIdSet {
36        self.state.invalid.borrow().clone()
37    }
38
39    pub fn read_only(&self) -> bool {
40        true
41    }
42
43    pub fn root_nested_readonly(&self) -> Arc<NestedReadonlySnapshot> {
44        if let Some(parent) = self.parent.upgrade() {
45            parent.root_nested_readonly()
46        } else {
47            NestedReadonlySnapshot::new(
48                self.state.id.get(),
49                self.state.invalid.borrow().clone(),
50                self.state.read_observer.borrow().clone(),
51                Weak::new(),
52            )
53        }
54    }
55
56    pub fn enter<T>(self: &Arc<Self>, f: impl FnOnce() -> T) -> T {
57        enter_snapshot_scope(AnySnapshot::NestedReadonly(self.clone()), f)
58    }
59
60    pub fn take_nested_snapshot(
61        &self,
62        read_observer: Option<ReadObserver>,
63    ) -> Arc<NestedReadonlySnapshot> {
64        let merged_observer =
65            merge_read_observers(read_observer, self.state.read_observer.borrow().clone());
66
67        NestedReadonlySnapshot::new(
68            self.state.id.get(),
69            self.state.invalid.borrow().clone(),
70            merged_observer,
71            self.parent.clone(),
72        )
73    }
74
75    pub fn has_pending_changes(&self) -> bool {
76        false
77    }
78
79    pub fn dispose(&self) {
80        if !self.state.disposed.get() {
81            self.state.dispose();
82        }
83    }
84
85    pub fn record_read(&self, state: &dyn StateObject) {
86        self.state.record_read(state);
87    }
88
89    pub fn record_write(&self, _state: Arc<dyn StateObject>) {
90        panic!("Cannot write to a read-only snapshot");
91    }
92
93    pub fn close(&self) {
94        self.state.disposed.set(true);
95    }
96
97    pub fn is_disposed(&self) -> bool {
98        self.state.disposed.get()
99    }
100}
101
102/// A nested mutable snapshot.
103///
104/// This is a mutable snapshot that has a parent. Changes made in this
105/// snapshot are applied to the parent when `apply()` is called, not
106/// to the global snapshot.
107///
108/// # Thread Safety
109/// Contains `Cell<T>` and `RefCell<T>` which are not `Send`/`Sync`. This is safe because
110/// snapshots are stored in thread-local storage and never shared across threads. The `Arc`
111/// is used for cheap cloning within a single thread, not for cross-thread sharing.
112#[allow(clippy::arc_with_non_send_sync)]
113pub struct NestedMutableSnapshot {
114    state: SnapshotState,
115    parent: Weak<MutableSnapshot>,
116    nested_count: Cell<usize>,
117    applied: Cell<bool>,
118    base_parent_id: SnapshotId,
119}
120
121impl NestedMutableSnapshot {
122    pub fn new(
123        id: SnapshotId,
124        invalid: SnapshotIdSet,
125        read_observer: Option<ReadObserver>,
126        write_observer: Option<WriteObserver>,
127        parent: Weak<MutableSnapshot>,
128        base_parent_id: SnapshotId,
129    ) -> Arc<Self> {
130        Arc::new(Self {
131            state: SnapshotState::new(id, invalid, read_observer, write_observer, true),
132            parent,
133            nested_count: Cell::new(0),
134            applied: Cell::new(false),
135            base_parent_id,
136        })
137    }
138
139    pub fn snapshot_id(&self) -> SnapshotId {
140        self.state.id.get()
141    }
142
143    pub fn invalid(&self) -> SnapshotIdSet {
144        self.state.invalid.borrow().clone()
145    }
146
147    pub fn read_only(&self) -> bool {
148        false
149    }
150
151    pub(crate) fn set_on_dispose<F>(&self, f: F)
152    where
153        F: FnOnce() + 'static,
154    {
155        self.state.set_on_dispose(f);
156    }
157
158    pub fn root_mutable(&self) -> Arc<MutableSnapshot> {
159        if let Some(parent) = self.parent.upgrade() {
160            parent.root_mutable()
161        } else {
162            MutableSnapshot::new(
163                self.state.id.get(),
164                self.state.invalid.borrow().clone(),
165                self.state.read_observer.borrow().clone(),
166                self.state.write_observer.borrow().clone(),
167                self.base_parent_id,
168            )
169        }
170    }
171
172    pub fn enter<T>(self: &Arc<Self>, f: impl FnOnce() -> T) -> T {
173        enter_snapshot_scope(AnySnapshot::NestedMutable(self.clone()), f)
174    }
175
176    pub fn take_nested_snapshot(
177        &self,
178        read_observer: Option<ReadObserver>,
179    ) -> Arc<ReadonlySnapshot> {
180        let merged_observer =
181            merge_read_observers(read_observer, self.state.read_observer.borrow().clone());
182
183        ReadonlySnapshot::new(
184            self.state.id.get(),
185            self.state.invalid.borrow().clone(),
186            merged_observer,
187        )
188    }
189
190    pub fn has_pending_changes(&self) -> bool {
191        !self.state.modified.borrow().is_empty()
192    }
193
194    pub fn pending_children(&self) -> Vec<SnapshotId> {
195        self.state.pending_children()
196    }
197
198    pub fn has_pending_children(&self) -> bool {
199        self.state.has_pending_children()
200    }
201
202    pub fn dispose(&self) {
203        if !self.state.disposed.get() && self.nested_count.get() == 0 {
204            self.state.dispose();
205        }
206    }
207
208    pub fn record_read(&self, state: &dyn StateObject) {
209        self.state.record_read(state);
210    }
211
212    pub fn record_write(&self, state: Arc<dyn StateObject>) {
213        if self.applied.get() {
214            panic!("Cannot write to an applied snapshot");
215        }
216        if self.state.disposed.get() {
217            panic!("Cannot write to a disposed snapshot");
218        }
219        self.state.record_write(state, self.state.id.get());
220    }
221
222    pub fn close(&self) {
223        self.state.disposed.set(true);
224    }
225
226    pub fn is_disposed(&self) -> bool {
227        self.state.disposed.get()
228    }
229
230    pub fn apply(&self) -> SnapshotApplyResult {
231        if self.state.disposed.get() {
232            return SnapshotApplyResult::Failure;
233        }
234
235        if self.applied.get() {
236            return SnapshotApplyResult::Failure;
237        }
238
239        if let Some(parent) = self.parent.upgrade() {
240            let child_modified = self.state.modified.borrow();
241            if child_modified.is_empty() {
242                self.applied.set(true);
243                self.state.dispose();
244                return SnapshotApplyResult::Success;
245            }
246            if parent.merge_child_modifications(&child_modified).is_err() {
247                return SnapshotApplyResult::Failure;
248            }
249
250            self.applied.set(true);
251            self.state.dispose();
252            SnapshotApplyResult::Success
253        } else {
254            SnapshotApplyResult::Failure
255        }
256    }
257
258    pub fn take_nested_mutable_snapshot(
259        self: &Arc<Self>,
260        read_observer: Option<ReadObserver>,
261        write_observer: Option<WriteObserver>,
262    ) -> Arc<NestedMutableSnapshot> {
263        let root = Arc::downgrade(&self.root_mutable());
264        allocate_nested_mutable_snapshot(self, root, read_observer, write_observer)
265    }
266}
267
268impl NestedMutableHost for NestedMutableSnapshot {
269    fn snapshot_state(&self) -> &SnapshotState {
270        &self.state
271    }
272
273    fn nested_count(&self) -> &Cell<usize> {
274        &self.nested_count
275    }
276}
277
278#[cfg(test)]
279mod tests {
280    use std::rc::Rc;
281
282    use super::*;
283    use crate::snapshot_v2::runtime::TestRuntimeGuard;
284
285    fn reset_runtime() -> TestRuntimeGuard {
286        reset_runtime_for_tests()
287    }
288
289    fn mock_state_record() -> Rc<crate::state::StateRecord> {
290        crate::state::StateRecord::new(crate::state::PREEXISTING_SNAPSHOT_ID, (), None)
291    }
292
293    #[test]
294    fn test_nested_readonly_snapshot() {
295        let _guard = reset_runtime();
296        let parent = NestedReadonlySnapshot::new(1, SnapshotIdSet::new(), None, Weak::new());
297        let parent_weak = Arc::downgrade(&parent);
298
299        let nested = NestedReadonlySnapshot::new(1, SnapshotIdSet::new(), None, parent_weak);
300
301        assert_eq!(nested.snapshot_id(), 1);
302        assert!(nested.read_only());
303        assert!(!nested.is_disposed());
304    }
305
306    #[test]
307    fn test_nested_readonly_snapshot_root() {
308        let _guard = reset_runtime();
309        let parent = NestedReadonlySnapshot::new(1, SnapshotIdSet::new(), None, Weak::new());
310        let parent_weak = Arc::downgrade(&parent);
311
312        let nested = NestedReadonlySnapshot::new(1, SnapshotIdSet::new(), None, parent_weak);
313
314        let root = nested.root_nested_readonly();
315        assert_eq!(root.snapshot_id(), 1);
316    }
317
318    #[test]
319    fn test_nested_readonly_dispose() {
320        let _guard = reset_runtime();
321        let parent = NestedReadonlySnapshot::new(1, SnapshotIdSet::new(), None, Weak::new());
322        let parent_weak = Arc::downgrade(&parent);
323
324        let nested = NestedReadonlySnapshot::new(1, SnapshotIdSet::new(), None, parent_weak);
325
326        nested.dispose();
327        assert!(nested.is_disposed());
328    }
329
330    #[test]
331    fn test_nested_mutable_snapshot() {
332        let _guard = reset_runtime();
333        let parent = MutableSnapshot::new(1, SnapshotIdSet::new(), None, None, 0);
334        let parent_weak = Arc::downgrade(&parent);
335
336        let nested =
337            NestedMutableSnapshot::new(2, SnapshotIdSet::new().set(1), None, None, parent_weak, 1);
338
339        assert_eq!(nested.snapshot_id(), 2);
340        assert!(!nested.read_only());
341        assert!(!nested.is_disposed());
342    }
343
344    #[test]
345    fn test_nested_mutable_apply() {
346        let _guard = reset_runtime();
347        let parent = MutableSnapshot::new(1, SnapshotIdSet::new(), None, None, 0);
348        let parent_weak = Arc::downgrade(&parent);
349
350        let nested =
351            NestedMutableSnapshot::new(2, SnapshotIdSet::new().set(1), None, None, parent_weak, 1);
352
353        let result = nested.apply();
354        assert!(result.is_success());
355        assert!(nested.applied.get());
356    }
357
358    #[test]
359    fn test_nested_merge_sets_parent_pending_changes() {
360        let _guard = reset_runtime();
361        struct TestObj {
362            id: crate::state::ObjectId,
363        }
364        impl StateObject for TestObj {
365            fn object_id(&self) -> crate::state::ObjectId {
366                self.id
367            }
368            fn first_record(&self) -> Rc<crate::state::StateRecord> {
369                mock_state_record()
370            }
371            fn try_readable_record(
372                &self,
373                snapshot_id: crate::snapshot_id_set::SnapshotId,
374                invalid: &SnapshotIdSet,
375            ) -> Option<Rc<crate::state::StateRecord>> {
376                Some(self.readable_record(snapshot_id, invalid))
377            }
378            fn readable_record(
379                &self,
380                _snapshot_id: crate::snapshot_id_set::SnapshotId,
381                _invalid: &SnapshotIdSet,
382            ) -> Rc<crate::state::StateRecord> {
383                mock_state_record()
384            }
385            fn prepend_state_record(&self, _record: Rc<crate::state::StateRecord>) {}
386            fn promote_record(
387                &self,
388                _child_id: crate::snapshot_id_set::SnapshotId,
389            ) -> Result<(), &'static str> {
390                Ok(())
391            }
392
393            fn as_any(&self) -> &dyn std::any::Any {
394                self
395            }
396        }
397
398        let parent = MutableSnapshot::new(1, SnapshotIdSet::new(), None, None, 0);
399        let child = parent.take_nested_mutable_snapshot(None, None);
400
401        let obj = Arc::new(TestObj {
402            id: crate::state::ObjectId(100),
403        });
404        child.record_write(obj);
405        assert!(!parent.has_pending_changes());
406        child.apply().check();
407        assert!(parent.has_pending_changes());
408    }
409
410    #[test]
411    fn test_nested_conflict_with_parent_same_object() {
412        let _guard = reset_runtime();
413        struct TestObj {
414            id: crate::state::ObjectId,
415        }
416        impl StateObject for TestObj {
417            fn object_id(&self) -> crate::state::ObjectId {
418                self.id
419            }
420            fn first_record(&self) -> Rc<crate::state::StateRecord> {
421                mock_state_record()
422            }
423            fn try_readable_record(
424                &self,
425                snapshot_id: crate::snapshot_id_set::SnapshotId,
426                invalid: &SnapshotIdSet,
427            ) -> Option<Rc<crate::state::StateRecord>> {
428                Some(self.readable_record(snapshot_id, invalid))
429            }
430            fn readable_record(
431                &self,
432                _snapshot_id: crate::snapshot_id_set::SnapshotId,
433                _invalid: &SnapshotIdSet,
434            ) -> Rc<crate::state::StateRecord> {
435                mock_state_record()
436            }
437            fn prepend_state_record(&self, _record: Rc<crate::state::StateRecord>) {}
438            fn promote_record(
439                &self,
440                _child_id: crate::snapshot_id_set::SnapshotId,
441            ) -> Result<(), &'static str> {
442                Ok(())
443            }
444
445            fn as_any(&self) -> &dyn std::any::Any {
446                self
447            }
448        }
449
450        let parent = MutableSnapshot::new(1, SnapshotIdSet::new(), None, None, 0);
451        let child = parent.take_nested_mutable_snapshot(None, None);
452
453        let obj = Arc::new(TestObj {
454            id: crate::state::ObjectId(200),
455        });
456        parent.record_write(obj.clone());
457        child.record_write(obj.clone());
458
459        let result = child.apply();
460        assert!(result.is_failure());
461    }
462
463    #[test]
464    fn test_nested_mutable_apply_twice_fails() {
465        let _guard = reset_runtime();
466        let parent = MutableSnapshot::new(1, SnapshotIdSet::new(), None, None, 0);
467        let parent_weak = Arc::downgrade(&parent);
468
469        let nested =
470            NestedMutableSnapshot::new(2, SnapshotIdSet::new().set(1), None, None, parent_weak, 1);
471
472        nested.apply().check();
473        let result = nested.apply();
474        assert!(result.is_failure());
475    }
476
477    #[test]
478    fn test_nested_mutable_dispose() {
479        let _guard = reset_runtime();
480        let parent = MutableSnapshot::new(1, SnapshotIdSet::new(), None, None, 0);
481        let parent_weak = Arc::downgrade(&parent);
482
483        let nested =
484            NestedMutableSnapshot::new(2, SnapshotIdSet::new().set(1), None, None, parent_weak, 1);
485
486        nested.dispose();
487        assert!(nested.is_disposed());
488    }
489}