Skip to main content

cranpose_core/snapshot_v2/
mutable.rs

1use std::rc::Rc;
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                    .is_none_or(|current| current.snapshot_id() < id);
42                if replace {
43                    best = Some(record.clone());
44                }
45            }
46            if fallback.is_none() {
47                fallback = Some(record.clone());
48            }
49        }
50        cursor = record.next();
51    }
52
53    (best.or(fallback), found_base)
54}
55
56enum ApplyOperation {
57    PromoteChild {
58        object_id: StateObjectId,
59        state: Rc<dyn StateObject>,
60        writer_id: SnapshotId,
61    },
62    PromoteExisting {
63        object_id: StateObjectId,
64        state: Rc<dyn StateObject>,
65        source_id: SnapshotId,
66        applied: Rc<StateRecord>,
67    },
68    CommitMerged {
69        object_id: StateObjectId,
70        state: Rc<dyn StateObject>,
71        merged: Rc<StateRecord>,
72        applied: Rc<StateRecord>,
73    },
74}
75
76/// A mutable snapshot that allows isolated state changes.
77///
78/// Changes made in a mutable snapshot are isolated from other snapshots
79/// until `apply()` is called, at which point they become visible atomically.
80/// This is a root mutable snapshot (not nested).
81///
82/// # Thread Safety
83/// Contains `Cell<T>` which is not `Send`/`Sync`. This is safe because snapshots
84/// are stored in thread-local storage and never shared across threads. The `Rc`
85/// is used for cheap cloning within a single thread, not for cross-thread sharing.
86pub struct MutableSnapshot {
87    pub(super) state: SnapshotState,
88    base_parent_id: SnapshotId,
89    nested_count: Cell<usize>,
90    applied: Cell<bool>,
91}
92
93impl MutableSnapshot {
94    pub(crate) fn from_parts(
95        id: SnapshotId,
96        invalid: SnapshotIdSet,
97        read_observer: Option<ReadObserver>,
98        write_observer: Option<WriteObserver>,
99        base_parent_id: SnapshotId,
100        runtime_tracked: bool,
101    ) -> Rc<Self> {
102        Rc::new(Self {
103            state: SnapshotState::new(id, invalid, read_observer, write_observer, runtime_tracked),
104            base_parent_id,
105            nested_count: Cell::new(0),
106            applied: Cell::new(false),
107        })
108    }
109
110    /// Create a new root mutable snapshot.
111    pub fn new(
112        id: SnapshotId,
113        invalid: SnapshotIdSet,
114        read_observer: Option<ReadObserver>,
115        write_observer: Option<WriteObserver>,
116        base_parent_id: SnapshotId,
117    ) -> Rc<Self> {
118        Self::from_parts(
119            id,
120            invalid,
121            read_observer,
122            write_observer,
123            base_parent_id,
124            false,
125        )
126    }
127
128    fn validate_not_applied(&self) {
129        assert!(!self.applied.get(), "Snapshot has already been applied");
130    }
131
132    fn validate_not_disposed(&self) {
133        assert!(!self.state.disposed.get(), "Snapshot has been disposed");
134    }
135
136    /// Returns the id of this snapshot. Ids grow with every snapshot taken.
137    pub fn snapshot_id(&self) -> SnapshotId {
138        self.state.id.get()
139    }
140
141    /// Returns the ids of snapshots whose writes this snapshot does not see.
142    pub fn invalid(&self) -> SnapshotIdSet {
143        self.state.invalid.borrow().clone()
144    }
145
146    /// Returns `false`: this snapshot accepts writes.
147    pub fn read_only(&self) -> bool {
148        false
149    }
150
151    pub(crate) fn set_on_dispose<F>(&self, f: F)
152    where
153        F: FnOnce() + 'static,
154    {
155        self.state.set_on_dispose(f);
156    }
157
158    /// Returns this snapshot, which is its own root.
159    pub fn root_mutable(self: &Rc<Self>) -> Rc<Self> {
160        self.clone()
161    }
162
163    /// Runs `f` with this snapshot as the current snapshot of the thread and returns its result.
164    /// The previous current snapshot is restored when `f` returns.
165    pub fn enter<T>(self: &Rc<Self>, f: impl FnOnce() -> T) -> T {
166        enter_snapshot_scope(AnySnapshot::Mutable(self.clone()), f)
167    }
168
169    /// Takes a read-only snapshot of this snapshot's view of state. Reads inside the new snapshot
170    /// call both `read_observer` and this snapshot's own read observer. Panics if this snapshot was
171    /// applied or disposed.
172    pub fn take_nested_snapshot(
173        self: &Rc<Self>,
174        read_observer: Option<ReadObserver>,
175    ) -> Rc<ReadonlySnapshot> {
176        self.validate_not_disposed();
177        self.validate_not_applied();
178
179        let merged_observer =
180            merge_read_observers(read_observer, self.state.read_observer.borrow().clone());
181
182        let nested = ReadonlySnapshot::new(
183            self.state.id.get(),
184            self.state.invalid.borrow().clone(),
185            merged_observer,
186        );
187
188        self.nested_count.set(self.nested_count.get() + 1);
189
190        let parent_weak = Rc::downgrade(self);
191        nested.set_on_dispose(move || {
192            if let Some(parent) = parent_weak.upgrade() {
193                let cur = parent.nested_count.get();
194                if cur > 0 {
195                    parent.nested_count.set(cur - 1);
196                }
197            }
198        });
199        nested
200    }
201
202    /// Returns `true` when this snapshot recorded writes that were not applied yet.
203    pub fn has_pending_changes(&self) -> bool {
204        !self.state.modified.borrow().is_empty()
205    }
206
207    /// Returns the ids of nested mutable snapshots taken from this snapshot and not yet disposed.
208    pub fn pending_children(&self) -> Vec<SnapshotId> {
209        self.state.pending_children()
210    }
211
212    /// Returns `true` when a nested mutable snapshot taken from this snapshot is still open.
213    pub fn has_pending_children(&self) -> bool {
214        self.state.has_pending_children()
215    }
216
217    /// Discards the writes that were not applied and releases the snapshot. Does nothing while
218    /// nested snapshots taken from it are still open.
219    pub fn dispose(&self) {
220        if !self.state.disposed.get() && self.nested_count.get() == 0 {
221            self.state.dispose();
222        }
223    }
224
225    /// Calls the read observer, if any, with `state`.
226    pub fn record_read(&self, state: &dyn StateObject) {
227        self.state.record_read(state);
228    }
229
230    /// Records `state` as written in this snapshot. The write observer, if any, is called the first
231    /// time a state object is recorded. Panics if the snapshot was applied or disposed.
232    pub fn record_write(&self, state: Rc<dyn StateObject>) {
233        self.validate_not_applied();
234        self.validate_not_disposed();
235        self.state.record_write(state, self.state.id.get());
236    }
237
238    /// Returns `true` once the snapshot was disposed.
239    pub fn is_disposed(&self) -> bool {
240        self.state.disposed.get()
241    }
242
243    /// Makes the writes of this snapshot visible to the global state. Returns `Failure` if the
244    /// snapshot was already applied or disposed, or if a state object it wrote changed in the
245    /// global state in a way that cannot be merged. After a successful apply with writes, the apply
246    /// observers are called.
247    pub fn apply(&self) -> SnapshotApplyResult {
248        // Values the apply displaces drop once it has landed.
249        let _retiring = crate::state::RetiredValuesScope::enter();
250        if self.state.disposed.get() {
251            return SnapshotApplyResult::Failure;
252        }
253
254        if self.applied.get() {
255            return SnapshotApplyResult::Failure;
256        }
257
258        let modified = self.state.modified.borrow();
259        if modified.is_empty() {
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, Rc<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 Some(applied) = find_record_by_id(&head, *writer_id) else {
294                return SnapshotApplyResult::Failure;
295            };
296
297            let Some(current) =
298                crate::state::readable_record_for(&head, parent_snapshot_id, &next_invalid)
299                    .or_else(|| state.try_readable_record(parent_snapshot_id, &parent_invalid))
300            else {
301                log::error!(
302                    "MutableSnapshot::apply missing parent readable record (object_id={obj_id:?}, parent_snapshot_id={parent_snapshot_id})"
303                );
304                return SnapshotApplyResult::Failure;
305            };
306            let this_invalid = self.state.invalid.borrow();
307            let (previous_opt, found_base) =
308                find_previous_record(&head, self.base_parent_id, &this_invalid);
309            drop(this_invalid);
310            let Some(previous) = previous_opt else {
311                return SnapshotApplyResult::Failure;
312            };
313
314            if !found_base || previous.snapshot_id() == PREEXISTING_SNAPSHOT_ID {
315                operations.push(ApplyOperation::PromoteChild {
316                    object_id: *obj_id,
317                    state: state.clone(),
318                    writer_id: *writer_id,
319                });
320                continue;
321            }
322
323            if Rc::ptr_eq(&current, &previous) {
324                operations.push(ApplyOperation::PromoteChild {
325                    object_id: *obj_id,
326                    state: state.clone(),
327                    writer_id: *writer_id,
328                });
329                continue;
330            }
331
332            let merged = if let Some(candidate) = optimistic
333                .as_ref()
334                .and_then(|map| map.get(&(Rc::as_ptr(&current) as usize)))
335                .cloned()
336            {
337                candidate
338            } else {
339                match state.merge_records(
340                    Rc::clone(&previous),
341                    Rc::clone(&current),
342                    Rc::clone(&applied),
343                ) {
344                    Some(record) => record,
345                    None => return SnapshotApplyResult::Failure,
346                }
347            };
348
349            if Rc::ptr_eq(&merged, &applied) {
350                operations.push(ApplyOperation::PromoteChild {
351                    object_id: *obj_id,
352                    state: state.clone(),
353                    writer_id: *writer_id,
354                });
355            } else if Rc::ptr_eq(&merged, &current) {
356                operations.push(ApplyOperation::PromoteExisting {
357                    object_id: *obj_id,
358                    state: state.clone(),
359                    source_id: current.snapshot_id(),
360                    applied: applied.clone(),
361                });
362            } else {
363                operations.push(ApplyOperation::CommitMerged {
364                    object_id: *obj_id,
365                    state: state.clone(),
366                    merged: merged.clone(),
367                    applied: applied.clone(),
368                });
369            }
370        }
371
372        let mut applied_info: Vec<(StateObjectId, Rc<dyn StateObject>, SnapshotId)> =
373            Vec::with_capacity(operations.len());
374
375        for operation in operations {
376            match operation {
377                ApplyOperation::PromoteChild {
378                    object_id,
379                    state,
380                    writer_id,
381                } => {
382                    if state.promote_record(writer_id).is_err() {
383                        return SnapshotApplyResult::Failure;
384                    }
385                    let new_head_id = state.first_record().snapshot_id();
386                    applied_info.push((object_id, state, new_head_id));
387                }
388                ApplyOperation::PromoteExisting {
389                    object_id,
390                    state,
391                    source_id,
392                    applied,
393                } => {
394                    if state.promote_record(source_id).is_err() {
395                        return SnapshotApplyResult::Failure;
396                    }
397                    applied.tombstone_retiring_value();
398                    let new_head_id = state.first_record().snapshot_id();
399                    applied_info.push((object_id, state, new_head_id));
400                }
401                ApplyOperation::CommitMerged {
402                    object_id,
403                    state,
404                    merged,
405                    applied,
406                } => {
407                    let Ok(new_head_id) = state.commit_merged_record(merged) else {
408                        return SnapshotApplyResult::Failure;
409                    };
410                    applied.tombstone_retiring_value();
411                    applied_info.push((object_id, state, new_head_id));
412                }
413            }
414        }
415
416        for (obj_id, _, head_id) in &applied_info {
417            super::set_last_write(*obj_id, *head_id);
418        }
419
420        self.applied.set(true);
421        self.state.dispose();
422
423        crate::thread_state::snapshots(|snapshots| {
424            let mut extra_state_objects = snapshots.extra_state_objects.borrow_mut();
425            for (_, state, _) in &applied_info {
426                extra_state_objects.add_trait_object(state);
427            }
428        });
429
430        let observer_states: Vec<Rc<dyn StateObject>> = applied_info
431            .iter()
432            .map(|(_, state, _)| state.clone())
433            .collect();
434        super::notify_apply_observers(&observer_states, this_id);
435        SnapshotApplyResult::Success
436    }
437
438    /// Takes a mutable snapshot nested in this one. Its `apply` moves the writes into this
439    /// snapshot. Panics if this snapshot was applied or disposed.
440    pub fn take_nested_mutable_snapshot(
441        self: &Rc<Self>,
442        read_observer: Option<ReadObserver>,
443        write_observer: Option<WriteObserver>,
444    ) -> Rc<NestedMutableSnapshot> {
445        self.validate_not_disposed();
446        self.validate_not_applied();
447
448        allocate_nested_mutable_snapshot(self, Rc::downgrade(self), read_observer, write_observer)
449    }
450
451    pub(crate) fn merge_child_modifications(
452        &self,
453        child_modified: &HashMap<StateObjectId, (Rc<dyn StateObject>, SnapshotId)>,
454    ) -> Result<(), ()> {
455        {
456            let parent_mod = self.state.modified.borrow();
457            for key in child_modified.keys() {
458                if parent_mod.contains_key(key) {
459                    return Err(());
460                }
461            }
462        }
463
464        let mut parent_mod = self.state.modified.borrow_mut();
465        for (key, value) in child_modified {
466            parent_mod.entry(*key).or_insert_with(|| value.clone());
467        }
468        Ok(())
469    }
470}
471
472impl NestedMutableHost for MutableSnapshot {
473    fn snapshot_state(&self) -> &SnapshotState {
474        &self.state
475    }
476
477    fn nested_count(&self) -> &Cell<usize> {
478        &self.nested_count
479    }
480}
481
482#[cfg(test)]
483impl MutableSnapshot {
484    pub(crate) fn debug_modified_objects(
485        &self,
486    ) -> Vec<(StateObjectId, Rc<dyn StateObject>, SnapshotId)> {
487        let modified = self.state.modified.borrow();
488        modified
489            .iter()
490            .map(|(&obj_id, (state, writer_id))| (obj_id, state.clone(), *writer_id))
491            .collect()
492    }
493
494    pub(crate) fn debug_base_parent_id(&self) -> SnapshotId {
495        self.base_parent_id
496    }
497}
498
499#[cfg(test)]
500#[path = "tests/mutable_tests.rs"]
501mod tests;