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 merged_read =
264            merge_read_observers(read_observer, self.state.read_observer.borrow().clone());
265        let merged_write =
266            merge_write_observers(write_observer, self.state.write_observer.borrow().clone());
267
268        let parent_id = self.state.id.get();
269        let current_invalid = self.state.invalid.borrow().clone();
270
271        let (new_id, _runtime_invalid) = allocate_snapshot();
272
273        let parent_invalid_with_child = current_invalid.set(new_id);
274        self.state.invalid.replace(parent_invalid_with_child);
275
276        let invalid = current_invalid.add_range(parent_id + 1, new_id);
277
278        let self_weak = Arc::downgrade(&self.root_mutable());
279
280        let nested = NestedMutableSnapshot::new(
281            new_id,
282            invalid,
283            merged_read,
284            merged_write,
285            self_weak,
286            self.state.id.get(),
287        );
288
289        self.nested_count.set(self.nested_count.get() + 1);
290        self.state.add_pending_child(new_id);
291
292        let parent_self_weak = Arc::downgrade(self);
293        nested.set_on_dispose({
294            let child_id = new_id;
295            move || {
296                if let Some(parent) = parent_self_weak.upgrade() {
297                    if parent.nested_count.get() > 0 {
298                        parent
299                            .nested_count
300                            .set(parent.nested_count.get().saturating_sub(1));
301                    }
302                    let mut invalid = parent.state.invalid.borrow_mut();
303                    let new_set = invalid.clone().clear(child_id);
304                    *invalid = new_set;
305                    parent.state.remove_pending_child(child_id);
306                }
307            }
308        });
309
310        nested
311    }
312}
313
314#[cfg(test)]
315mod tests {
316    use std::rc::Rc;
317
318    use super::*;
319    use crate::snapshot_v2::runtime::TestRuntimeGuard;
320
321    fn reset_runtime() -> TestRuntimeGuard {
322        reset_runtime_for_tests()
323    }
324
325    fn mock_state_record() -> Rc<crate::state::StateRecord> {
326        crate::state::StateRecord::new(crate::state::PREEXISTING_SNAPSHOT_ID, (), None)
327    }
328
329    #[test]
330    fn test_nested_readonly_snapshot() {
331        let _guard = reset_runtime();
332        let parent = NestedReadonlySnapshot::new(1, SnapshotIdSet::new(), None, Weak::new());
333        let parent_weak = Arc::downgrade(&parent);
334
335        let nested = NestedReadonlySnapshot::new(1, SnapshotIdSet::new(), None, parent_weak);
336
337        assert_eq!(nested.snapshot_id(), 1);
338        assert!(nested.read_only());
339        assert!(!nested.is_disposed());
340    }
341
342    #[test]
343    fn test_nested_readonly_snapshot_root() {
344        let _guard = reset_runtime();
345        let parent = NestedReadonlySnapshot::new(1, SnapshotIdSet::new(), None, Weak::new());
346        let parent_weak = Arc::downgrade(&parent);
347
348        let nested = NestedReadonlySnapshot::new(1, SnapshotIdSet::new(), None, parent_weak);
349
350        let root = nested.root_nested_readonly();
351        assert_eq!(root.snapshot_id(), 1);
352    }
353
354    #[test]
355    fn test_nested_readonly_dispose() {
356        let _guard = reset_runtime();
357        let parent = NestedReadonlySnapshot::new(1, SnapshotIdSet::new(), None, Weak::new());
358        let parent_weak = Arc::downgrade(&parent);
359
360        let nested = NestedReadonlySnapshot::new(1, SnapshotIdSet::new(), None, parent_weak);
361
362        nested.dispose();
363        assert!(nested.is_disposed());
364    }
365
366    #[test]
367    fn test_nested_mutable_snapshot() {
368        let _guard = reset_runtime();
369        let parent = MutableSnapshot::new(1, SnapshotIdSet::new(), None, None, 0);
370        let parent_weak = Arc::downgrade(&parent);
371
372        let nested =
373            NestedMutableSnapshot::new(2, SnapshotIdSet::new().set(1), None, None, parent_weak, 1);
374
375        assert_eq!(nested.snapshot_id(), 2);
376        assert!(!nested.read_only());
377        assert!(!nested.is_disposed());
378    }
379
380    #[test]
381    fn test_nested_mutable_apply() {
382        let _guard = reset_runtime();
383        let parent = MutableSnapshot::new(1, SnapshotIdSet::new(), None, None, 0);
384        let parent_weak = Arc::downgrade(&parent);
385
386        let nested =
387            NestedMutableSnapshot::new(2, SnapshotIdSet::new().set(1), None, None, parent_weak, 1);
388
389        let result = nested.apply();
390        assert!(result.is_success());
391        assert!(nested.applied.get());
392    }
393
394    #[test]
395    fn test_nested_merge_sets_parent_pending_changes() {
396        let _guard = reset_runtime();
397        struct TestObj {
398            id: crate::state::ObjectId,
399        }
400        impl StateObject for TestObj {
401            fn object_id(&self) -> crate::state::ObjectId {
402                self.id
403            }
404            fn first_record(&self) -> Rc<crate::state::StateRecord> {
405                mock_state_record()
406            }
407            fn try_readable_record(
408                &self,
409                snapshot_id: crate::snapshot_id_set::SnapshotId,
410                invalid: &SnapshotIdSet,
411            ) -> Option<Rc<crate::state::StateRecord>> {
412                Some(self.readable_record(snapshot_id, invalid))
413            }
414            fn readable_record(
415                &self,
416                _snapshot_id: crate::snapshot_id_set::SnapshotId,
417                _invalid: &SnapshotIdSet,
418            ) -> Rc<crate::state::StateRecord> {
419                mock_state_record()
420            }
421            fn prepend_state_record(&self, _record: Rc<crate::state::StateRecord>) {}
422            fn promote_record(
423                &self,
424                _child_id: crate::snapshot_id_set::SnapshotId,
425            ) -> Result<(), &'static str> {
426                Ok(())
427            }
428
429            fn as_any(&self) -> &dyn std::any::Any {
430                self
431            }
432        }
433
434        let parent = MutableSnapshot::new(1, SnapshotIdSet::new(), None, None, 0);
435        let child = parent.take_nested_mutable_snapshot(None, None);
436
437        let obj = Arc::new(TestObj {
438            id: crate::state::ObjectId(100),
439        });
440        child.record_write(obj);
441        assert!(!parent.has_pending_changes());
442        child.apply().check();
443        assert!(parent.has_pending_changes());
444    }
445
446    #[test]
447    fn test_nested_conflict_with_parent_same_object() {
448        let _guard = reset_runtime();
449        struct TestObj {
450            id: crate::state::ObjectId,
451        }
452        impl StateObject for TestObj {
453            fn object_id(&self) -> crate::state::ObjectId {
454                self.id
455            }
456            fn first_record(&self) -> Rc<crate::state::StateRecord> {
457                mock_state_record()
458            }
459            fn try_readable_record(
460                &self,
461                snapshot_id: crate::snapshot_id_set::SnapshotId,
462                invalid: &SnapshotIdSet,
463            ) -> Option<Rc<crate::state::StateRecord>> {
464                Some(self.readable_record(snapshot_id, invalid))
465            }
466            fn readable_record(
467                &self,
468                _snapshot_id: crate::snapshot_id_set::SnapshotId,
469                _invalid: &SnapshotIdSet,
470            ) -> Rc<crate::state::StateRecord> {
471                mock_state_record()
472            }
473            fn prepend_state_record(&self, _record: Rc<crate::state::StateRecord>) {}
474            fn promote_record(
475                &self,
476                _child_id: crate::snapshot_id_set::SnapshotId,
477            ) -> Result<(), &'static str> {
478                Ok(())
479            }
480
481            fn as_any(&self) -> &dyn std::any::Any {
482                self
483            }
484        }
485
486        let parent = MutableSnapshot::new(1, SnapshotIdSet::new(), None, None, 0);
487        let child = parent.take_nested_mutable_snapshot(None, None);
488
489        let obj = Arc::new(TestObj {
490            id: crate::state::ObjectId(200),
491        });
492        parent.record_write(obj.clone());
493        child.record_write(obj.clone());
494
495        let result = child.apply();
496        assert!(result.is_failure());
497    }
498
499    #[test]
500    fn test_nested_mutable_apply_twice_fails() {
501        let _guard = reset_runtime();
502        let parent = MutableSnapshot::new(1, SnapshotIdSet::new(), None, None, 0);
503        let parent_weak = Arc::downgrade(&parent);
504
505        let nested =
506            NestedMutableSnapshot::new(2, SnapshotIdSet::new().set(1), None, None, parent_weak, 1);
507
508        nested.apply().check();
509        let result = nested.apply();
510        assert!(result.is_failure());
511    }
512
513    #[test]
514    fn test_nested_mutable_dispose() {
515        let _guard = reset_runtime();
516        let parent = MutableSnapshot::new(1, SnapshotIdSet::new(), None, None, 0);
517        let parent_weak = Arc::downgrade(&parent);
518
519        let nested =
520            NestedMutableSnapshot::new(2, SnapshotIdSet::new().set(1), None, None, parent_weak, 1);
521
522        nested.dispose();
523        assert!(nested.is_disposed());
524    }
525}