Skip to main content

cranpose_core/snapshot_v2/
mutable.rs

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