Skip to main content

cranpose_core/snapshot_v2/
mutable.rs

1//! Mutable snapshot implementation.
2
3use super::*;
4use crate::collections::map::HashMap;
5use crate::state::{StateRecord, PREEXISTING_SNAPSHOT_ID};
6use std::rc::Rc;
7use std::sync::Arc;
8
9pub(super) fn find_record_by_id(
10    head: &Rc<StateRecord>,
11    target: SnapshotId,
12) -> Option<Rc<StateRecord>> {
13    let mut cursor = Some(Rc::clone(head));
14    while let Some(record) = cursor {
15        if !record.is_tombstone() && record.snapshot_id() == target {
16            return Some(record);
17        }
18        cursor = record.next();
19    }
20    None
21}
22
23/// Find the record that was readable when the snapshot was created.
24///
25/// This uses both base_snapshot_id and invalid set to find the record,
26/// matching Kotlin's `readable(first, snapshotId, applyingSnapshot.invalid)`.
27/// Records in the invalid set are filtered out even if their ID <= base_snapshot_id.
28pub(super) fn find_previous_record(
29    head: &Rc<StateRecord>,
30    base_snapshot_id: SnapshotId,
31    invalid: &SnapshotIdSet,
32) -> (Option<Rc<StateRecord>>, bool) {
33    let mut cursor = Some(Rc::clone(head));
34    let mut best: Option<Rc<StateRecord>> = None;
35    let mut fallback: Option<Rc<StateRecord>> = None;
36    let mut found_base = false;
37
38    while let Some(record) = cursor {
39        if !record.is_tombstone() {
40            let id = record.snapshot_id();
41            // A record is valid if id <= base_snapshot_id AND id is NOT in invalid set
42            let is_valid = id <= base_snapshot_id && !invalid.get(id);
43            if is_valid {
44                found_base = true;
45                let replace = best
46                    .as_ref()
47                    .map(|current| current.snapshot_id() < id)
48                    .unwrap_or(true);
49                if replace {
50                    best = Some(record.clone());
51                }
52            }
53            // Fallback captures the first non-tombstone record regardless of validity
54            if fallback.is_none() {
55                fallback = Some(record.clone());
56            }
57        }
58        cursor = record.next();
59    }
60
61    (best.or(fallback), found_base)
62}
63
64enum ApplyOperation {
65    PromoteChild {
66        object_id: StateObjectId,
67        state: Arc<dyn StateObject>,
68        writer_id: SnapshotId,
69    },
70    PromoteExisting {
71        object_id: StateObjectId,
72        state: Arc<dyn StateObject>,
73        source_id: SnapshotId,
74        applied: Rc<StateRecord>,
75    },
76    CommitMerged {
77        object_id: StateObjectId,
78        state: Arc<dyn StateObject>,
79        merged: Rc<StateRecord>,
80        applied: Rc<StateRecord>,
81    },
82}
83
84/// A mutable snapshot that allows isolated state changes.
85///
86/// Changes made in a mutable snapshot are isolated from other snapshots
87/// until `apply()` is called, at which point they become visible atomically.
88/// This is a root mutable snapshot (not nested).
89///
90/// # Thread Safety
91/// Contains `Cell<T>` which is not `Send`/`Sync`. This is safe because snapshots
92/// are stored in thread-local storage and never shared across threads. The `Arc`
93/// is used for cheap cloning within a single thread, not for cross-thread sharing.
94#[allow(clippy::arc_with_non_send_sync)]
95pub struct MutableSnapshot {
96    state: SnapshotState,
97    /// The parent's snapshot id at the time this snapshot was created
98    base_parent_id: SnapshotId,
99    /// Number of active nested snapshots
100    nested_count: Cell<usize>,
101    /// Whether this snapshot has been applied
102    applied: Cell<bool>,
103}
104
105impl MutableSnapshot {
106    pub(crate) fn from_parts(
107        id: SnapshotId,
108        invalid: SnapshotIdSet,
109        read_observer: Option<ReadObserver>,
110        write_observer: Option<WriteObserver>,
111        base_parent_id: SnapshotId,
112        runtime_tracked: bool,
113    ) -> Arc<Self> {
114        Arc::new(Self {
115            state: SnapshotState::new(id, invalid, read_observer, write_observer, runtime_tracked),
116            base_parent_id,
117            nested_count: Cell::new(0),
118            applied: Cell::new(false),
119        })
120    }
121
122    /// Create a new root mutable snapshot.
123    pub fn new(
124        id: SnapshotId,
125        invalid: SnapshotIdSet,
126        read_observer: Option<ReadObserver>,
127        write_observer: Option<WriteObserver>,
128        base_parent_id: SnapshotId,
129    ) -> Arc<Self> {
130        Self::from_parts(
131            id,
132            invalid,
133            read_observer,
134            write_observer,
135            base_parent_id,
136            false,
137        )
138    }
139
140    fn validate_not_applied(&self) {
141        if self.applied.get() {
142            panic!("Snapshot has already been applied");
143        }
144    }
145
146    fn validate_not_disposed(&self) {
147        if self.state.disposed.get() {
148            panic!("Snapshot has been disposed");
149        }
150    }
151
152    pub fn snapshot_id(&self) -> SnapshotId {
153        self.state.id.get()
154    }
155
156    pub fn invalid(&self) -> SnapshotIdSet {
157        self.state.invalid.borrow().clone()
158    }
159
160    pub fn read_only(&self) -> bool {
161        false
162    }
163
164    pub(crate) fn set_on_dispose<F>(&self, f: F)
165    where
166        F: FnOnce() + 'static,
167    {
168        self.state.set_on_dispose(f);
169    }
170
171    pub fn root_mutable(self: &Arc<Self>) -> Arc<Self> {
172        self.clone()
173    }
174
175    pub fn enter<T>(self: &Arc<Self>, f: impl FnOnce() -> T) -> T {
176        enter_snapshot_scope(AnySnapshot::Mutable(self.clone()), f)
177    }
178
179    pub fn take_nested_snapshot(
180        self: &Arc<Self>,
181        read_observer: Option<ReadObserver>,
182    ) -> Arc<ReadonlySnapshot> {
183        self.validate_not_disposed();
184        self.validate_not_applied();
185
186        let merged_observer =
187            merge_read_observers(read_observer, self.state.read_observer.borrow().clone());
188
189        // Create a nested read-only snapshot
190        let nested = ReadonlySnapshot::new(
191            self.state.id.get(),
192            self.state.invalid.borrow().clone(),
193            merged_observer,
194        );
195
196        self.nested_count.set(self.nested_count.get() + 1);
197
198        // When the nested snapshot is disposed, decrement this parent's nested_count
199        let parent_weak = Arc::downgrade(self);
200        nested.set_on_dispose(move || {
201            if let Some(parent) = parent_weak.upgrade() {
202                let cur = parent.nested_count.get();
203                if cur > 0 {
204                    parent.nested_count.set(cur - 1);
205                }
206            }
207        });
208        nested
209    }
210
211    pub fn has_pending_changes(&self) -> bool {
212        !self.state.modified.borrow().is_empty()
213    }
214
215    pub fn pending_children(&self) -> Vec<SnapshotId> {
216        self.state.pending_children()
217    }
218
219    pub fn has_pending_children(&self) -> bool {
220        self.state.has_pending_children()
221    }
222
223    pub fn dispose(&self) {
224        if !self.state.disposed.get() && self.nested_count.get() == 0 {
225            self.state.dispose();
226        }
227    }
228
229    pub fn record_read(&self, state: &dyn StateObject) {
230        self.state.record_read(state);
231    }
232
233    pub fn record_write(&self, state: Arc<dyn StateObject>) {
234        self.validate_not_applied();
235        self.validate_not_disposed();
236        self.state.record_write(state, self.state.id.get());
237    }
238
239    pub fn close(&self) {
240        self.state.disposed.set(true);
241    }
242
243    pub fn is_disposed(&self) -> bool {
244        self.state.disposed.get()
245    }
246
247    pub fn apply(&self) -> SnapshotApplyResult {
248        // Check disposed state first - return Failure instead of panicking
249        if self.state.disposed.get() {
250            return SnapshotApplyResult::Failure;
251        }
252
253        if self.applied.get() {
254            return SnapshotApplyResult::Failure;
255        }
256
257        let modified = self.state.modified.borrow();
258        if modified.is_empty() {
259            // No changes to apply
260            self.applied.set(true);
261            self.state.dispose();
262            return SnapshotApplyResult::Success;
263        }
264
265        let this_id = self.state.id.get();
266        let mut modified_objects: Vec<(StateObjectId, Arc<dyn StateObject>, SnapshotId)> =
267            Vec::with_capacity(modified.len());
268        for (&obj_id, (obj, writer_id)) in modified.iter() {
269            modified_objects.push((obj_id, obj.clone(), *writer_id));
270        }
271
272        drop(modified);
273
274        let parent_snapshot = GlobalSnapshot::get_or_create();
275        let parent_snapshot_id = parent_snapshot.snapshot_id();
276        let parent_invalid = parent_snapshot.invalid();
277        drop(parent_snapshot);
278
279        let next_invalid = super::runtime::open_snapshots().clear(parent_snapshot_id);
280        let this_invalid_for_optimistic = self.state.invalid.borrow().clone();
281        let optimistic = super::optimistic_merges(
282            parent_snapshot_id,
283            self.base_parent_id,
284            &modified_objects,
285            &next_invalid,
286            &this_invalid_for_optimistic,
287        );
288
289        let mut operations: Vec<ApplyOperation> = Vec::with_capacity(modified_objects.len());
290
291        for (obj_id, state, writer_id) in &modified_objects {
292            let head = state.first_record();
293            let applied = match find_record_by_id(&head, *writer_id) {
294                Some(record) => record,
295                None => return SnapshotApplyResult::Failure,
296            };
297
298            let Some(current) =
299                crate::state::readable_record_for(&head, parent_snapshot_id, &next_invalid)
300                    .or_else(|| state.try_readable_record(parent_snapshot_id, &parent_invalid))
301            else {
302                log::error!(
303                    "MutableSnapshot::apply missing parent readable record (object_id={:?}, parent_snapshot_id={})",
304                    obj_id,
305                    parent_snapshot_id
306                );
307                return SnapshotApplyResult::Failure;
308            };
309            // Use this snapshot's invalid set to find previous (matching Kotlin's
310            // `readable(first, snapshotId, applyingSnapshot.invalid)`)
311            let this_invalid = self.state.invalid.borrow();
312            let (previous_opt, found_base) =
313                find_previous_record(&head, self.base_parent_id, &this_invalid);
314            drop(this_invalid);
315            let Some(previous) = previous_opt else {
316                return SnapshotApplyResult::Failure;
317            };
318
319            if !found_base || previous.snapshot_id() == PREEXISTING_SNAPSHOT_ID {
320                operations.push(ApplyOperation::PromoteChild {
321                    object_id: *obj_id,
322                    state: state.clone(),
323                    writer_id: *writer_id,
324                });
325                continue;
326            }
327
328            if Rc::ptr_eq(&current, &previous) {
329                operations.push(ApplyOperation::PromoteChild {
330                    object_id: *obj_id,
331                    state: state.clone(),
332                    writer_id: *writer_id,
333                });
334                continue;
335            }
336
337            let merged = if let Some(candidate) = optimistic
338                .as_ref()
339                .and_then(|map| map.get(&(Rc::as_ptr(&current) as usize)))
340                .cloned()
341            {
342                candidate
343            } else {
344                match state.merge_records(
345                    Rc::clone(&previous),
346                    Rc::clone(&current),
347                    Rc::clone(&applied),
348                ) {
349                    Some(record) => record,
350                    None => return SnapshotApplyResult::Failure,
351                }
352            };
353
354            if Rc::ptr_eq(&merged, &applied) {
355                operations.push(ApplyOperation::PromoteChild {
356                    object_id: *obj_id,
357                    state: state.clone(),
358                    writer_id: *writer_id,
359                });
360            } else if Rc::ptr_eq(&merged, &current) {
361                operations.push(ApplyOperation::PromoteExisting {
362                    object_id: *obj_id,
363                    state: state.clone(),
364                    source_id: current.snapshot_id(),
365                    applied: applied.clone(),
366                });
367            } else {
368                operations.push(ApplyOperation::CommitMerged {
369                    object_id: *obj_id,
370                    state: state.clone(),
371                    merged: merged.clone(),
372                    applied: applied.clone(),
373                });
374            }
375        }
376
377        let mut applied_info: Vec<(StateObjectId, Arc<dyn StateObject>, SnapshotId)> =
378            Vec::with_capacity(operations.len());
379
380        for operation in operations {
381            match operation {
382                ApplyOperation::PromoteChild {
383                    object_id,
384                    state,
385                    writer_id,
386                } => {
387                    if state.promote_record(writer_id).is_err() {
388                        return SnapshotApplyResult::Failure;
389                    }
390                    let new_head_id = state.first_record().snapshot_id();
391                    applied_info.push((object_id, state, new_head_id));
392                }
393                ApplyOperation::PromoteExisting {
394                    object_id,
395                    state,
396                    source_id,
397                    applied,
398                } => {
399                    if state.promote_record(source_id).is_err() {
400                        return SnapshotApplyResult::Failure;
401                    }
402                    applied.set_tombstone(true);
403                    applied.clear_value();
404                    let new_head_id = state.first_record().snapshot_id();
405                    applied_info.push((object_id, state, new_head_id));
406                }
407                ApplyOperation::CommitMerged {
408                    object_id,
409                    state,
410                    merged,
411                    applied,
412                } => {
413                    let Ok(new_head_id) = state.commit_merged_record(merged) else {
414                        return SnapshotApplyResult::Failure;
415                    };
416                    applied.set_tombstone(true);
417                    applied.clear_value();
418                    applied_info.push((object_id, state, new_head_id));
419                }
420            }
421        }
422
423        for (obj_id, _, head_id) in &applied_info {
424            super::set_last_write(*obj_id, *head_id);
425        }
426
427        self.applied.set(true);
428        self.state.dispose();
429
430        // Track modified states for future cleanup instead of cleaning immediately.
431        // This defers the O(record_chain) work to check_and_overwrite_unused_records_locked,
432        // which runs periodically (e.g., on global snapshot advance) rather than on every apply.
433        // This prevents performance regression during rapid scrolling while still preventing leaks.
434        for (_, state, _) in &applied_info {
435            super::EXTRA_STATE_OBJECTS.with(|cell| {
436                cell.borrow_mut().add_trait_object(state);
437            });
438        }
439
440        let observer_states: Vec<Arc<dyn StateObject>> = applied_info
441            .iter()
442            .map(|(_, state, _)| state.clone())
443            .collect();
444        super::notify_apply_observers(&observer_states, this_id);
445        SnapshotApplyResult::Success
446    }
447
448    pub fn take_nested_mutable_snapshot(
449        self: &Arc<Self>,
450        read_observer: Option<ReadObserver>,
451        write_observer: Option<WriteObserver>,
452    ) -> Arc<NestedMutableSnapshot> {
453        self.validate_not_disposed();
454        self.validate_not_applied();
455
456        let merged_read =
457            merge_read_observers(read_observer, self.state.read_observer.borrow().clone());
458        let merged_write =
459            merge_write_observers(write_observer, self.state.write_observer.borrow().clone());
460
461        // Get parent's current state BEFORE allocating child
462        let parent_id = self.state.id.get();
463        let current_invalid = self.state.invalid.borrow().clone();
464
465        // Allocate the new child snapshot ID
466        let (new_id, _runtime_invalid) = allocate_snapshot();
467
468        // Update parent's invalid to include the child
469        let parent_invalid_with_child = current_invalid.set(new_id);
470        self.state.invalid.replace(parent_invalid_with_child);
471
472        // Child's invalid = parent's invalid + range(parent_id + 1, new_id)
473        // This does NOT include parent_id, so child can read parent's records
474        // (matching Kotlin's currentInvalid.addRange(snapshotId + 1, newId))
475        let invalid = current_invalid.add_range(parent_id + 1, new_id);
476
477        let self_weak = Arc::downgrade(self);
478        let nested = NestedMutableSnapshot::new(
479            new_id,
480            invalid,
481            merged_read,
482            merged_write,
483            self_weak,
484            self.state.id.get(), // base_parent_id for child is this snapshot's id
485        );
486
487        self.nested_count.set(self.nested_count.get() + 1);
488        self.state.add_pending_child(new_id);
489
490        let parent_weak = Arc::downgrade(self);
491        nested.set_on_dispose({
492            let child_id = new_id;
493            move || {
494                if let Some(parent) = parent_weak.upgrade() {
495                    if parent.nested_count.get() > 0 {
496                        parent
497                            .nested_count
498                            .set(parent.nested_count.get().saturating_sub(1));
499                    }
500                    let mut invalid = parent.state.invalid.borrow_mut();
501                    let new_set = invalid.clone().clear(child_id);
502                    *invalid = new_set;
503                    parent.state.remove_pending_child(child_id);
504                }
505            }
506        });
507
508        nested
509    }
510
511    /// Merge a child's modified set into this snapshot's modified set.
512    ///
513    /// Returns Ok(()) on success, or Err(()) if a conflict is detected
514    /// (i.e., this snapshot already has a modification for the same object).
515    pub(crate) fn merge_child_modifications(
516        &self,
517        child_modified: &HashMap<StateObjectId, (Arc<dyn StateObject>, SnapshotId)>,
518    ) -> Result<(), ()> {
519        // Check for conflicts
520        {
521            let parent_mod = self.state.modified.borrow();
522            for key in child_modified.keys() {
523                if parent_mod.contains_key(key) {
524                    return Err(());
525                }
526            }
527        }
528
529        // Merge entries
530        let mut parent_mod = self.state.modified.borrow_mut();
531        for (key, value) in child_modified.iter() {
532            parent_mod.entry(*key).or_insert_with(|| value.clone());
533        }
534        Ok(())
535    }
536}
537
538#[cfg(test)]
539impl MutableSnapshot {
540    pub(crate) fn debug_modified_objects(
541        &self,
542    ) -> Vec<(StateObjectId, Arc<dyn StateObject>, SnapshotId)> {
543        let modified = self.state.modified.borrow();
544        modified
545            .iter()
546            .map(|(&obj_id, (state, writer_id))| (obj_id, state.clone(), *writer_id))
547            .collect()
548    }
549
550    pub(crate) fn debug_base_parent_id(&self) -> SnapshotId {
551        self.base_parent_id
552    }
553}
554
555#[cfg(test)]
556mod tests {
557    use super::*;
558    use crate::snapshot_v2::runtime::TestRuntimeGuard;
559    use crate::state::{NeverEqual, SnapshotMutableState, StateObject};
560    use std::sync::Arc;
561
562    fn reset_runtime() -> TestRuntimeGuard {
563        reset_runtime_for_tests()
564    }
565
566    fn new_state(initial: i32) -> Arc<SnapshotMutableState<i32>> {
567        SnapshotMutableState::new_in_arc(initial, Arc::new(NeverEqual))
568    }
569
570    // Mock StateObject for testing
571    struct MockStateObject;
572
573    fn mock_state_record() -> Rc<crate::state::StateRecord> {
574        crate::state::StateRecord::new(crate::state::PREEXISTING_SNAPSHOT_ID, (), None)
575    }
576
577    impl StateObject for MockStateObject {
578        fn object_id(&self) -> crate::state::ObjectId {
579            crate::state::ObjectId(0)
580        }
581
582        fn first_record(&self) -> Rc<crate::state::StateRecord> {
583            mock_state_record()
584        }
585
586        fn try_readable_record(
587            &self,
588            snapshot_id: crate::snapshot_id_set::SnapshotId,
589            invalid: &SnapshotIdSet,
590        ) -> Option<Rc<crate::state::StateRecord>> {
591            Some(self.readable_record(snapshot_id, invalid))
592        }
593
594        fn readable_record(
595            &self,
596            _snapshot_id: crate::snapshot_id_set::SnapshotId,
597            _invalid: &SnapshotIdSet,
598        ) -> Rc<crate::state::StateRecord> {
599            mock_state_record()
600        }
601
602        fn prepend_state_record(&self, _record: Rc<crate::state::StateRecord>) {}
603
604        fn promote_record(
605            &self,
606            _child_id: crate::snapshot_id_set::SnapshotId,
607        ) -> Result<(), &'static str> {
608            Ok(())
609        }
610
611        fn as_any(&self) -> &dyn std::any::Any {
612            self
613        }
614    }
615
616    struct MissingParentReadableStateObject {
617        record: Rc<crate::state::StateRecord>,
618    }
619
620    impl MissingParentReadableStateObject {
621        fn new(writer_id: SnapshotId) -> Self {
622            Self {
623                record: crate::state::StateRecord::new(writer_id, (), None),
624            }
625        }
626    }
627
628    impl StateObject for MissingParentReadableStateObject {
629        fn object_id(&self) -> crate::state::ObjectId {
630            crate::state::ObjectId(10_001)
631        }
632
633        fn first_record(&self) -> Rc<crate::state::StateRecord> {
634            Rc::clone(&self.record)
635        }
636
637        fn try_readable_record(
638            &self,
639            _: SnapshotId,
640            _: &SnapshotIdSet,
641        ) -> Option<Rc<crate::state::StateRecord>> {
642            None
643        }
644
645        fn readable_record(
646            &self,
647            _: SnapshotId,
648            _: &SnapshotIdSet,
649        ) -> Rc<crate::state::StateRecord> {
650            panic!("apply must use a fallible parent readable-record lookup")
651        }
652
653        fn prepend_state_record(&self, _record: Rc<crate::state::StateRecord>) {}
654
655        fn promote_record(
656            &self,
657            _child_id: crate::snapshot_id_set::SnapshotId,
658        ) -> Result<(), &'static str> {
659            Ok(())
660        }
661
662        fn as_any(&self) -> &dyn std::any::Any {
663            self
664        }
665    }
666
667    #[test]
668    fn test_mutable_snapshot_creation() {
669        let _guard = reset_runtime();
670        let snapshot = MutableSnapshot::new(1, SnapshotIdSet::new(), None, None, 0);
671        assert_eq!(snapshot.snapshot_id(), 1);
672        assert!(!snapshot.read_only());
673        assert!(!snapshot.is_disposed());
674        assert!(!snapshot.applied.get());
675    }
676
677    #[test]
678    fn test_mutable_snapshot_no_pending_changes_initially() {
679        let _guard = reset_runtime();
680        let snapshot = MutableSnapshot::new(1, SnapshotIdSet::new(), None, None, 0);
681        assert!(!snapshot.has_pending_changes());
682    }
683
684    #[test]
685    fn test_mutable_snapshot_enter() {
686        let _guard = reset_runtime();
687        let snapshot = MutableSnapshot::new(1, SnapshotIdSet::new(), None, None, 0);
688
689        set_current_snapshot(None);
690        assert!(current_snapshot().is_none());
691
692        snapshot.enter(|| {
693            let current = current_snapshot();
694            assert!(current.is_some());
695            assert_eq!(current.unwrap().snapshot_id(), 1);
696        });
697
698        assert!(current_snapshot().is_none());
699    }
700
701    #[test]
702    fn test_mutable_snapshot_read_observer() {
703        let _guard = reset_runtime();
704        use std::sync::{Arc as StdArc, Mutex};
705
706        let read_count = StdArc::new(Mutex::new(0));
707        let read_count_clone = read_count.clone();
708
709        let observer = Arc::new(move |_: &dyn StateObject| {
710            *read_count_clone.lock().unwrap() += 1;
711        });
712
713        let snapshot = MutableSnapshot::new(1, SnapshotIdSet::new(), Some(observer), None, 0);
714        let mock_state = MockStateObject;
715
716        snapshot.record_read(&mock_state);
717        snapshot.record_read(&mock_state);
718
719        assert_eq!(*read_count.lock().unwrap(), 2);
720    }
721
722    #[test]
723    fn test_mutable_snapshot_write_observer() {
724        let _guard = reset_runtime();
725        use std::sync::{Arc as StdArc, Mutex};
726
727        let write_count = StdArc::new(Mutex::new(0));
728        let write_count_clone = write_count.clone();
729
730        let observer = Arc::new(move |_: &dyn StateObject| {
731            *write_count_clone.lock().unwrap() += 1;
732        });
733
734        let snapshot = MutableSnapshot::new(1, SnapshotIdSet::new(), None, Some(observer), 0);
735        let mock_state = Arc::new(MockStateObject);
736
737        snapshot.record_write(mock_state.clone());
738        snapshot.record_write(mock_state.clone()); // Second write should not call observer
739
740        assert_eq!(*write_count.lock().unwrap(), 1);
741    }
742
743    #[test]
744    fn test_mutable_snapshot_apply_empty() {
745        let _guard = reset_runtime();
746        let snapshot = MutableSnapshot::new(1, SnapshotIdSet::new(), None, None, 0);
747        let result = snapshot.apply();
748        assert!(result.is_success());
749        assert!(snapshot.applied.get());
750    }
751
752    #[test]
753    fn mutable_apply_returns_failure_when_parent_readable_record_is_missing() {
754        let _guard = reset_runtime();
755        let snapshot = MutableSnapshot::new(7, SnapshotIdSet::new(), None, None, 1);
756        let state = Arc::new(MissingParentReadableStateObject::new(
757            snapshot.snapshot_id(),
758        ));
759
760        snapshot.record_write(state);
761
762        let result = snapshot.apply();
763
764        assert!(result.is_failure());
765    }
766
767    #[test]
768    fn test_mutable_snapshot_apply_twice_fails() {
769        let _guard = reset_runtime();
770        let snapshot = MutableSnapshot::new(1, SnapshotIdSet::new(), None, None, 0);
771        snapshot.apply().check();
772
773        let result = snapshot.apply();
774        assert!(result.is_failure());
775    }
776
777    #[test]
778    fn test_mutable_snapshot_nested_readonly() {
779        let _guard = reset_runtime();
780        let parent = MutableSnapshot::new(1, SnapshotIdSet::new(), None, None, 0);
781        let nested = parent.take_nested_snapshot(None);
782
783        assert_eq!(nested.snapshot_id(), 1);
784        assert!(nested.read_only());
785        assert_eq!(parent.nested_count.get(), 1);
786    }
787
788    #[test]
789    fn test_mutable_snapshot_nested_mutable() {
790        let _guard = reset_runtime();
791        let parent = MutableSnapshot::new(1, SnapshotIdSet::new(), None, None, 0);
792        let nested = parent.take_nested_mutable_snapshot(None, None);
793
794        assert!(nested.snapshot_id() > parent.snapshot_id());
795        assert!(!nested.read_only());
796        assert_eq!(parent.nested_count.get(), 1);
797    }
798
799    #[test]
800    fn test_mutable_snapshot_nested_mutable_dispose_clears_invalid() {
801        let _guard = reset_runtime();
802        let parent = MutableSnapshot::new(1, SnapshotIdSet::new(), None, None, 0);
803        let nested = parent.take_nested_mutable_snapshot(None, None);
804
805        let child_id = nested.snapshot_id();
806        assert!(parent.state.invalid.borrow().get(child_id));
807
808        nested.dispose();
809
810        assert_eq!(parent.nested_count.get(), 0);
811        assert!(!parent.state.invalid.borrow().get(child_id));
812    }
813
814    #[test]
815    fn test_mutable_snapshot_nested_dispose() {
816        let _guard = reset_runtime();
817        let parent = MutableSnapshot::new(1, SnapshotIdSet::new(), None, None, 0);
818        let nested = parent.take_nested_snapshot(None);
819
820        assert_eq!(parent.nested_count.get(), 1);
821
822        nested.dispose();
823        assert_eq!(parent.nested_count.get(), 0);
824    }
825
826    #[test]
827    #[should_panic(expected = "Snapshot has already been applied")]
828    fn test_mutable_snapshot_write_after_apply_panics() {
829        let _guard = reset_runtime();
830        let snapshot = MutableSnapshot::new(1, SnapshotIdSet::new(), None, None, 0);
831        snapshot.apply().check();
832
833        let mock_state = Arc::new(MockStateObject);
834        snapshot.record_write(mock_state);
835    }
836
837    #[test]
838    #[should_panic(expected = "Snapshot has been disposed")]
839    fn test_mutable_snapshot_write_after_dispose_panics() {
840        let _guard = reset_runtime();
841        let snapshot = MutableSnapshot::new(1, SnapshotIdSet::new(), None, None, 0);
842        snapshot.dispose();
843
844        let mock_state = Arc::new(MockStateObject);
845        snapshot.record_write(mock_state);
846    }
847
848    #[test]
849    fn test_mutable_snapshot_dispose() {
850        let _guard = reset_runtime();
851        let snapshot = MutableSnapshot::new(1, SnapshotIdSet::new(), None, None, 0);
852        assert!(!snapshot.is_disposed());
853
854        snapshot.dispose();
855        assert!(snapshot.is_disposed());
856    }
857
858    #[test]
859    fn test_mutable_snapshot_apply_observer() {
860        let _guard = reset_runtime();
861        use std::sync::{Arc as StdArc, Mutex};
862
863        let applied_count = StdArc::new(Mutex::new(0));
864        let applied_count_clone = applied_count.clone();
865
866        let observer = Rc::new(
867            move |_modified: &[Arc<dyn StateObject>], _snapshot_id: SnapshotId| {
868                *applied_count_clone.lock().unwrap() += 1;
869            },
870        );
871
872        let _handle = register_apply_observer(observer);
873
874        let snapshot = MutableSnapshot::new(1, SnapshotIdSet::new(), None, None, 0);
875        let state = new_state(0);
876
877        snapshot.enter(|| state.set(10));
878        snapshot.apply().check();
879
880        assert_eq!(*applied_count.lock().unwrap(), 1);
881    }
882
883    #[test]
884    fn test_mutable_conflict_detection_same_object() {
885        let _guard = reset_runtime();
886        let global = GlobalSnapshot::get_or_create();
887        let state = new_state(0);
888
889        let s1 = global.take_nested_mutable_snapshot(None, None);
890        s1.enter(|| state.set(1));
891
892        let s2 = global.take_nested_mutable_snapshot(None, None);
893        s2.enter(|| state.set(2));
894
895        assert!(s1.apply().is_success());
896        assert!(s2.apply().is_failure());
897    }
898
899    #[test]
900    fn test_mutable_no_conflict_different_objects() {
901        let _guard = reset_runtime();
902        let global = GlobalSnapshot::get_or_create();
903        let state1 = new_state(0);
904        let state2 = new_state(0);
905
906        let s1 = global.take_nested_mutable_snapshot(None, None);
907        s1.enter(|| state1.set(10));
908
909        let s2 = global.take_nested_mutable_snapshot(None, None);
910        s2.enter(|| state2.set(20));
911
912        assert!(s1.apply().is_success());
913        assert!(s2.apply().is_success());
914    }
915}