Skip to main content

cranpose_core/snapshot_v2/
nested.rs

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