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                    .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: Arc<dyn StateObject>,
60        writer_id: SnapshotId,
61    },
62    PromoteExisting {
63        object_id: StateObjectId,
64        state: Arc<dyn StateObject>,
65        source_id: SnapshotId,
66        applied: Rc<StateRecord>,
67    },
68    CommitMerged {
69        object_id: StateObjectId,
70        state: Arc<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 `Arc`
85/// is used for cheap cloning within a single thread, not for cross-thread sharing.
86pub struct MutableSnapshot {
87    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    ) -> Arc<Self> {
102        Arc::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    ) -> Arc<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    pub fn snapshot_id(&self) -> SnapshotId {
137        self.state.id.get()
138    }
139
140    pub fn invalid(&self) -> SnapshotIdSet {
141        self.state.invalid.borrow().clone()
142    }
143
144    pub fn read_only(&self) -> bool {
145        false
146    }
147
148    pub(crate) fn set_on_dispose<F>(&self, f: F)
149    where
150        F: FnOnce() + 'static,
151    {
152        self.state.set_on_dispose(f);
153    }
154
155    pub fn root_mutable(self: &Arc<Self>) -> Arc<Self> {
156        self.clone()
157    }
158
159    pub fn enter<T>(self: &Arc<Self>, f: impl FnOnce() -> T) -> T {
160        enter_snapshot_scope(AnySnapshot::Mutable(self.clone()), f)
161    }
162
163    pub fn take_nested_snapshot(
164        self: &Arc<Self>,
165        read_observer: Option<ReadObserver>,
166    ) -> Arc<ReadonlySnapshot> {
167        self.validate_not_disposed();
168        self.validate_not_applied();
169
170        let merged_observer =
171            merge_read_observers(read_observer, self.state.read_observer.borrow().clone());
172
173        let nested = ReadonlySnapshot::new(
174            self.state.id.get(),
175            self.state.invalid.borrow().clone(),
176            merged_observer,
177        );
178
179        self.nested_count.set(self.nested_count.get() + 1);
180
181        let parent_weak = Arc::downgrade(self);
182        nested.set_on_dispose(move || {
183            if let Some(parent) = parent_weak.upgrade() {
184                let cur = parent.nested_count.get();
185                if cur > 0 {
186                    parent.nested_count.set(cur - 1);
187                }
188            }
189        });
190        nested
191    }
192
193    pub fn has_pending_changes(&self) -> bool {
194        !self.state.modified.borrow().is_empty()
195    }
196
197    pub fn pending_children(&self) -> Vec<SnapshotId> {
198        self.state.pending_children()
199    }
200
201    pub fn has_pending_children(&self) -> bool {
202        self.state.has_pending_children()
203    }
204
205    pub fn dispose(&self) {
206        if !self.state.disposed.get() && self.nested_count.get() == 0 {
207            self.state.dispose();
208        }
209    }
210
211    pub fn record_read(&self, state: &dyn StateObject) {
212        self.state.record_read(state);
213    }
214
215    pub fn record_write(&self, state: Arc<dyn StateObject>) {
216        self.validate_not_applied();
217        self.validate_not_disposed();
218        self.state.record_write(state, self.state.id.get());
219    }
220
221    pub fn close(&self) {
222        self.state.disposed.set(true);
223    }
224
225    pub fn is_disposed(&self) -> bool {
226        self.state.disposed.get()
227    }
228
229    pub fn apply(&self) -> SnapshotApplyResult {
230        if self.state.disposed.get() {
231            return SnapshotApplyResult::Failure;
232        }
233
234        if self.applied.get() {
235            return SnapshotApplyResult::Failure;
236        }
237
238        let modified = self.state.modified.borrow();
239        if modified.is_empty() {
240            self.applied.set(true);
241            self.state.dispose();
242            return SnapshotApplyResult::Success;
243        }
244
245        let this_id = self.state.id.get();
246        let mut modified_objects: Vec<(StateObjectId, Arc<dyn StateObject>, SnapshotId)> =
247            Vec::with_capacity(modified.len());
248        for (&obj_id, (obj, writer_id)) in modified.iter() {
249            modified_objects.push((obj_id, obj.clone(), *writer_id));
250        }
251
252        drop(modified);
253
254        let parent_snapshot = GlobalSnapshot::get_or_create();
255        let parent_snapshot_id = parent_snapshot.snapshot_id();
256        let parent_invalid = parent_snapshot.invalid();
257        drop(parent_snapshot);
258
259        let next_invalid = super::runtime::open_snapshots().clear(parent_snapshot_id);
260        let this_invalid_for_optimistic = self.state.invalid.borrow().clone();
261        let optimistic = super::optimistic_merges(
262            parent_snapshot_id,
263            self.base_parent_id,
264            &modified_objects,
265            &next_invalid,
266            &this_invalid_for_optimistic,
267        );
268
269        let mut operations: Vec<ApplyOperation> = Vec::with_capacity(modified_objects.len());
270
271        for (obj_id, state, writer_id) in &modified_objects {
272            let head = state.first_record();
273            let Some(applied) = find_record_by_id(&head, *writer_id) else {
274                return SnapshotApplyResult::Failure;
275            };
276
277            let Some(current) =
278                crate::state::readable_record_for(&head, parent_snapshot_id, &next_invalid)
279                    .or_else(|| state.try_readable_record(parent_snapshot_id, &parent_invalid))
280            else {
281                log::error!(
282                    "MutableSnapshot::apply missing parent readable record (object_id={obj_id:?}, parent_snapshot_id={parent_snapshot_id})"
283                );
284                return SnapshotApplyResult::Failure;
285            };
286            let this_invalid = self.state.invalid.borrow();
287            let (previous_opt, found_base) =
288                find_previous_record(&head, self.base_parent_id, &this_invalid);
289            drop(this_invalid);
290            let Some(previous) = previous_opt else {
291                return SnapshotApplyResult::Failure;
292            };
293
294            if !found_base || previous.snapshot_id() == PREEXISTING_SNAPSHOT_ID {
295                operations.push(ApplyOperation::PromoteChild {
296                    object_id: *obj_id,
297                    state: state.clone(),
298                    writer_id: *writer_id,
299                });
300                continue;
301            }
302
303            if Rc::ptr_eq(&current, &previous) {
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            let merged = if let Some(candidate) = optimistic
313                .as_ref()
314                .and_then(|map| map.get(&(Rc::as_ptr(&current) as usize)))
315                .cloned()
316            {
317                candidate
318            } else {
319                match state.merge_records(
320                    Rc::clone(&previous),
321                    Rc::clone(&current),
322                    Rc::clone(&applied),
323                ) {
324                    Some(record) => record,
325                    None => return SnapshotApplyResult::Failure,
326                }
327            };
328
329            if Rc::ptr_eq(&merged, &applied) {
330                operations.push(ApplyOperation::PromoteChild {
331                    object_id: *obj_id,
332                    state: state.clone(),
333                    writer_id: *writer_id,
334                });
335            } else if Rc::ptr_eq(&merged, &current) {
336                operations.push(ApplyOperation::PromoteExisting {
337                    object_id: *obj_id,
338                    state: state.clone(),
339                    source_id: current.snapshot_id(),
340                    applied: applied.clone(),
341                });
342            } else {
343                operations.push(ApplyOperation::CommitMerged {
344                    object_id: *obj_id,
345                    state: state.clone(),
346                    merged: merged.clone(),
347                    applied: applied.clone(),
348                });
349            }
350        }
351
352        let mut applied_info: Vec<(StateObjectId, Arc<dyn StateObject>, SnapshotId)> =
353            Vec::with_capacity(operations.len());
354
355        for operation in operations {
356            match operation {
357                ApplyOperation::PromoteChild {
358                    object_id,
359                    state,
360                    writer_id,
361                } => {
362                    if state.promote_record(writer_id).is_err() {
363                        return SnapshotApplyResult::Failure;
364                    }
365                    let new_head_id = state.first_record().snapshot_id();
366                    applied_info.push((object_id, state, new_head_id));
367                }
368                ApplyOperation::PromoteExisting {
369                    object_id,
370                    state,
371                    source_id,
372                    applied,
373                } => {
374                    if state.promote_record(source_id).is_err() {
375                        return SnapshotApplyResult::Failure;
376                    }
377                    applied.set_tombstone(true);
378                    applied.clear_value();
379                    let new_head_id = state.first_record().snapshot_id();
380                    applied_info.push((object_id, state, new_head_id));
381                }
382                ApplyOperation::CommitMerged {
383                    object_id,
384                    state,
385                    merged,
386                    applied,
387                } => {
388                    let Ok(new_head_id) = state.commit_merged_record(merged) else {
389                        return SnapshotApplyResult::Failure;
390                    };
391                    applied.set_tombstone(true);
392                    applied.clear_value();
393                    applied_info.push((object_id, state, new_head_id));
394                }
395            }
396        }
397
398        for (obj_id, _, head_id) in &applied_info {
399            super::set_last_write(*obj_id, *head_id);
400        }
401
402        self.applied.set(true);
403        self.state.dispose();
404
405        for (_, state, _) in &applied_info {
406            super::EXTRA_STATE_OBJECTS.with(|cell| {
407                cell.borrow_mut().add_trait_object(state);
408            });
409        }
410
411        let observer_states: Vec<Arc<dyn StateObject>> = applied_info
412            .iter()
413            .map(|(_, state, _)| state.clone())
414            .collect();
415        super::notify_apply_observers(&observer_states, this_id);
416        SnapshotApplyResult::Success
417    }
418
419    pub fn take_nested_mutable_snapshot(
420        self: &Arc<Self>,
421        read_observer: Option<ReadObserver>,
422        write_observer: Option<WriteObserver>,
423    ) -> Arc<NestedMutableSnapshot> {
424        self.validate_not_disposed();
425        self.validate_not_applied();
426
427        allocate_nested_mutable_snapshot(self, Arc::downgrade(self), read_observer, write_observer)
428    }
429
430    pub(crate) fn merge_child_modifications(
431        &self,
432        child_modified: &HashMap<StateObjectId, (Arc<dyn StateObject>, SnapshotId)>,
433    ) -> Result<(), ()> {
434        {
435            let parent_mod = self.state.modified.borrow();
436            for key in child_modified.keys() {
437                if parent_mod.contains_key(key) {
438                    return Err(());
439                }
440            }
441        }
442
443        let mut parent_mod = self.state.modified.borrow_mut();
444        for (key, value) in child_modified {
445            parent_mod.entry(*key).or_insert_with(|| value.clone());
446        }
447        Ok(())
448    }
449}
450
451impl NestedMutableHost for MutableSnapshot {
452    fn snapshot_state(&self) -> &SnapshotState {
453        &self.state
454    }
455
456    fn nested_count(&self) -> &Cell<usize> {
457        &self.nested_count
458    }
459}
460
461#[cfg(test)]
462impl MutableSnapshot {
463    pub(crate) fn debug_modified_objects(
464        &self,
465    ) -> Vec<(StateObjectId, Arc<dyn StateObject>, SnapshotId)> {
466        let modified = self.state.modified.borrow();
467        modified
468            .iter()
469            .map(|(&obj_id, (state, writer_id))| (obj_id, state.clone(), *writer_id))
470            .collect()
471    }
472
473    pub(crate) fn debug_base_parent_id(&self) -> SnapshotId {
474        self.base_parent_id
475    }
476}
477
478#[cfg(test)]
479#[path = "tests/mutable_tests.rs"]
480mod tests;