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.clone(),
54                Weak::new(),
55            )
56        }
57    }
58
59    pub fn enter<T>(self: &Arc<Self>, f: impl FnOnce() -> T) -> T {
60        let previous = current_snapshot();
61        set_current_snapshot(Some(AnySnapshot::NestedReadonly(self.clone())));
62        let result = f();
63        set_current_snapshot(previous);
64        result
65    }
66
67    pub fn take_nested_snapshot(
68        &self,
69        read_observer: Option<ReadObserver>,
70    ) -> Arc<NestedReadonlySnapshot> {
71        let merged_observer = merge_read_observers(read_observer, self.state.read_observer.clone());
72
73        NestedReadonlySnapshot::new(
74            self.state.id.get(),
75            self.state.invalid.borrow().clone(),
76            merged_observer,
77            self.parent.clone(),
78        )
79    }
80
81    pub fn has_pending_changes(&self) -> bool {
82        false
83    }
84
85    pub fn dispose(&self) {
86        if !self.state.disposed.get() {
87            self.state.dispose();
88        }
89    }
90
91    pub fn record_read(&self, state: &dyn StateObject) {
92        self.state.record_read(state);
93    }
94
95    pub fn record_write(&self, _state: Arc<dyn StateObject>) {
96        panic!("Cannot write to a read-only snapshot");
97    }
98
99    pub fn close(&self) {
100        self.state.disposed.set(true);
101    }
102
103    pub fn is_disposed(&self) -> bool {
104        self.state.disposed.get()
105    }
106}
107
108/// A nested mutable snapshot.
109///
110/// This is a mutable snapshot that has a parent. Changes made in this
111/// snapshot are applied to the parent when `apply()` is called, not
112/// to the global snapshot.
113///
114/// # Thread Safety
115/// Contains `Cell<T>` and `RefCell<T>` which are not `Send`/`Sync`. This is safe because
116/// snapshots are stored in thread-local storage and never shared across threads. The `Arc`
117/// is used for cheap cloning within a single thread, not for cross-thread sharing.
118#[allow(clippy::arc_with_non_send_sync)]
119pub struct NestedMutableSnapshot {
120    state: SnapshotState,
121    parent: Weak<MutableSnapshot>,
122    nested_count: Cell<usize>,
123    applied: Cell<bool>,
124    /// Parent's snapshot id when this nested snapshot was created
125    base_parent_id: SnapshotId,
126}
127
128impl NestedMutableSnapshot {
129    pub fn new(
130        id: SnapshotId,
131        invalid: SnapshotIdSet,
132        read_observer: Option<ReadObserver>,
133        write_observer: Option<WriteObserver>,
134        parent: Weak<MutableSnapshot>,
135        base_parent_id: SnapshotId,
136    ) -> Arc<Self> {
137        Arc::new(Self {
138            state: SnapshotState::new(id, invalid, read_observer, write_observer, true),
139            parent,
140            nested_count: Cell::new(0),
141            applied: Cell::new(false),
142            base_parent_id,
143        })
144    }
145
146    pub fn snapshot_id(&self) -> SnapshotId {
147        self.state.id.get()
148    }
149
150    pub fn invalid(&self) -> SnapshotIdSet {
151        self.state.invalid.borrow().clone()
152    }
153
154    pub fn read_only(&self) -> bool {
155        false
156    }
157
158    pub(crate) fn set_on_dispose<F>(&self, f: F)
159    where
160        F: FnOnce() + 'static,
161    {
162        self.state.set_on_dispose(f);
163    }
164
165    pub fn root_mutable(&self) -> Arc<MutableSnapshot> {
166        if let Some(parent) = self.parent.upgrade() {
167            parent.root_mutable()
168        } else {
169            // Parent is gone, return a fallback mutable snapshot
170            MutableSnapshot::new(
171                self.state.id.get(),
172                self.state.invalid.borrow().clone(),
173                self.state.read_observer.clone(),
174                self.state.write_observer.clone(),
175                self.base_parent_id,
176            )
177        }
178    }
179
180    pub fn enter<T>(self: &Arc<Self>, f: impl FnOnce() -> T) -> T {
181        let previous = current_snapshot();
182        set_current_snapshot(Some(AnySnapshot::NestedMutable(self.clone())));
183        let result = f();
184        set_current_snapshot(previous);
185        result
186    }
187
188    pub fn take_nested_snapshot(
189        &self,
190        read_observer: Option<ReadObserver>,
191    ) -> Arc<ReadonlySnapshot> {
192        let merged_observer = merge_read_observers(read_observer, self.state.read_observer.clone());
193
194        ReadonlySnapshot::new(
195            self.state.id.get(),
196            self.state.invalid.borrow().clone(),
197            merged_observer,
198        )
199    }
200
201    pub fn has_pending_changes(&self) -> bool {
202        !self.state.modified.borrow().is_empty()
203    }
204
205    pub fn pending_children(&self) -> Vec<SnapshotId> {
206        self.state.pending_children()
207    }
208
209    pub fn has_pending_children(&self) -> bool {
210        self.state.has_pending_children()
211    }
212
213    pub fn parent_mutable(&self) -> Option<Arc<MutableSnapshot>> {
214        self.parent.upgrade()
215    }
216
217    pub fn dispose(&self) {
218        if !self.state.disposed.get() && self.nested_count.get() == 0 {
219            self.state.dispose();
220        }
221    }
222
223    pub fn record_read(&self, state: &dyn StateObject) {
224        self.state.record_read(state);
225    }
226
227    pub fn record_write(&self, state: Arc<dyn StateObject>) {
228        if self.applied.get() {
229            panic!("Cannot write to an applied snapshot");
230        }
231        if self.state.disposed.get() {
232            panic!("Cannot write to a disposed snapshot");
233        }
234        self.state.record_write(state, self.state.id.get());
235    }
236
237    pub fn close(&self) {
238        self.state.disposed.set(true);
239    }
240
241    pub fn is_disposed(&self) -> bool {
242        self.state.disposed.get()
243    }
244
245    pub fn apply(&self) -> SnapshotApplyResult {
246        if self.state.disposed.get() {
247            return SnapshotApplyResult::Failure;
248        }
249
250        if self.applied.get() {
251            return SnapshotApplyResult::Failure;
252        }
253
254        // Apply changes to parent instead of global snapshot
255        if let Some(parent) = self.parent.upgrade() {
256            // Merge to parent (Phase 2.2) with simple conflict detection.
257            let child_modified = self.state.modified.borrow();
258            if child_modified.is_empty() {
259                self.applied.set(true);
260                self.state.dispose();
261                return SnapshotApplyResult::Success;
262            }
263            // Ask parent to merge child's modifications; it will detect conflicts.
264            if parent.merge_child_modifications(&child_modified).is_err() {
265                return SnapshotApplyResult::Failure;
266            }
267
268            self.applied.set(true);
269            self.state.dispose();
270            SnapshotApplyResult::Success
271        } else {
272            SnapshotApplyResult::Failure
273        }
274    }
275
276    pub fn take_nested_mutable_snapshot(
277        self: &Arc<Self>,
278        read_observer: Option<ReadObserver>,
279        write_observer: Option<WriteObserver>,
280    ) -> Arc<NestedMutableSnapshot> {
281        let merged_read = merge_read_observers(read_observer, self.state.read_observer.clone());
282        let merged_write = merge_write_observers(write_observer, self.state.write_observer.clone());
283
284        // Get parent's current state BEFORE allocating child
285        let parent_id = self.state.id.get();
286        let current_invalid = self.state.invalid.borrow().clone();
287
288        // Allocate the new child snapshot ID
289        let (new_id, _runtime_invalid) = allocate_snapshot();
290
291        // Update parent's invalid to include the child
292        let parent_invalid_with_child = current_invalid.set(new_id);
293        self.state.invalid.replace(parent_invalid_with_child);
294
295        // Child's invalid = parent's invalid + range(parent_id + 1, new_id)
296        // This does NOT include parent_id, so child can read parent's records
297        let invalid = current_invalid.add_range(parent_id + 1, new_id);
298
299        let self_weak = Arc::downgrade(&self.root_mutable());
300
301        let nested = NestedMutableSnapshot::new(
302            new_id,
303            invalid,
304            merged_read,
305            merged_write,
306            self_weak,
307            self.state.id.get(), // base_parent_id = this snapshot's id at creation time
308        );
309
310        self.nested_count.set(self.nested_count.get() + 1);
311        self.state.add_pending_child(new_id);
312
313        let parent_self_weak = Arc::downgrade(self);
314        nested.set_on_dispose({
315            let child_id = new_id;
316            move || {
317                if let Some(parent) = parent_self_weak.upgrade() {
318                    if parent.nested_count.get() > 0 {
319                        parent
320                            .nested_count
321                            .set(parent.nested_count.get().saturating_sub(1));
322                    }
323                    let mut invalid = parent.state.invalid.borrow_mut();
324                    let new_set = invalid.clone().clear(child_id);
325                    *invalid = new_set;
326                    parent.state.remove_pending_child(child_id);
327                }
328            }
329        });
330
331        nested
332    }
333}
334
335#[cfg(test)]
336mod tests {
337    use super::*;
338    use crate::snapshot_v2::runtime::TestRuntimeGuard;
339    use std::rc::Rc;
340
341    fn reset_runtime() -> TestRuntimeGuard {
342        reset_runtime_for_tests()
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                unimplemented!("not used in v2 tests")
423            }
424            fn readable_record(
425                &self,
426                _snapshot_id: crate::snapshot_id_set::SnapshotId,
427                _invalid: &SnapshotIdSet,
428            ) -> Rc<crate::state::StateRecord> {
429                unimplemented!("not used in v2 tests")
430            }
431            fn prepend_state_record(&self, _record: Rc<crate::state::StateRecord>) {
432                unimplemented!("not used in v2 tests")
433            }
434            fn promote_record(
435                &self,
436                _child_id: crate::snapshot_id_set::SnapshotId,
437            ) -> Result<(), &'static str> {
438                unimplemented!("not used in v2 tests")
439            }
440
441            fn as_any(&self) -> &dyn std::any::Any {
442                self
443            }
444        }
445
446        let parent = MutableSnapshot::new(1, SnapshotIdSet::new(), None, None, 0);
447        let child = parent.take_nested_mutable_snapshot(None, None);
448
449        let obj = Arc::new(TestObj {
450            id: crate::state::ObjectId(100),
451        });
452        child.record_write(obj);
453        assert!(!parent.has_pending_changes());
454        child.apply().check();
455        assert!(parent.has_pending_changes());
456    }
457
458    #[test]
459    fn test_nested_conflict_with_parent_same_object() {
460        let _guard = reset_runtime();
461        // Parent and child both modify same object; child apply should fail
462        struct TestObj {
463            id: crate::state::ObjectId,
464        }
465        impl StateObject for TestObj {
466            fn object_id(&self) -> crate::state::ObjectId {
467                self.id
468            }
469            fn first_record(&self) -> Rc<crate::state::StateRecord> {
470                unimplemented!("not used in v2 tests")
471            }
472            fn readable_record(
473                &self,
474                _snapshot_id: crate::snapshot_id_set::SnapshotId,
475                _invalid: &SnapshotIdSet,
476            ) -> Rc<crate::state::StateRecord> {
477                unimplemented!("not used in v2 tests")
478            }
479            fn prepend_state_record(&self, _record: Rc<crate::state::StateRecord>) {
480                unimplemented!("not used in v2 tests")
481            }
482            fn promote_record(
483                &self,
484                _child_id: crate::snapshot_id_set::SnapshotId,
485            ) -> Result<(), &'static str> {
486                unimplemented!("not used in v2 tests")
487            }
488
489            fn as_any(&self) -> &dyn std::any::Any {
490                self
491            }
492        }
493
494        let parent = MutableSnapshot::new(1, SnapshotIdSet::new(), None, None, 0);
495        let child = parent.take_nested_mutable_snapshot(None, None);
496
497        let obj = Arc::new(TestObj {
498            id: crate::state::ObjectId(200),
499        });
500        parent.record_write(obj.clone());
501        child.record_write(obj.clone());
502
503        let result = child.apply();
504        assert!(result.is_failure());
505    }
506
507    #[test]
508    fn test_nested_mutable_apply_twice_fails() {
509        let _guard = reset_runtime();
510        let parent = MutableSnapshot::new(1, SnapshotIdSet::new(), None, None, 0);
511        let parent_weak = Arc::downgrade(&parent);
512
513        let nested =
514            NestedMutableSnapshot::new(2, SnapshotIdSet::new().set(1), None, None, parent_weak, 1);
515
516        nested.apply().check();
517        let result = nested.apply();
518        assert!(result.is_failure());
519    }
520
521    #[test]
522    fn test_nested_mutable_dispose() {
523        let _guard = reset_runtime();
524        let parent = MutableSnapshot::new(1, SnapshotIdSet::new(), None, None, 0);
525        let parent_weak = Arc::downgrade(&parent);
526
527        let nested =
528            NestedMutableSnapshot::new(2, SnapshotIdSet::new().set(1), None, None, parent_weak, 1);
529
530        nested.dispose();
531        assert!(nested.is_disposed());
532    }
533}