Skip to main content

cranpose_core/snapshot_v2/
mutable.rs

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