1#![expect(clippy::arc_with_non_send_sync)]
22
23use std::{
24 cell::{Cell, RefCell},
25 hash::{Hash, Hasher},
26 rc::Rc,
27 sync::{Arc, Weak},
28};
29
30use crate::{
31 collections::map::{HashMap, HashSet},
32 snapshot_id_set::{SnapshotId, SnapshotIdSet},
33 snapshot_pinning::{self, PinHandle},
34 snapshot_weak_set::SnapshotWeakSetDebugStats,
35 state::{StateObject, StateRecord},
36};
37
38mod global;
39mod mutable;
40mod nested;
41mod readonly;
42mod runtime;
43mod transparent;
44
45#[cfg(test)]
46#[path = "tests/integration_tests.rs"]
47mod integration_tests;
48
49pub use global::{GlobalSnapshot, advance_global_snapshot};
50pub use mutable::MutableSnapshot;
51pub use nested::{NestedMutableSnapshot, NestedReadonlySnapshot};
52pub use readonly::ReadonlySnapshot;
53#[cfg(test)]
54pub(crate) use runtime::{TestRuntimeGuard, reset_runtime_for_tests};
55pub(crate) use runtime::{allocate_snapshot, close_snapshot, with_runtime};
56pub use transparent::{TransparentObserverMutableSnapshot, TransparentObserverSnapshot};
57
58pub type ReadObserver = Arc<dyn Fn(&dyn StateObject) + 'static>;
60
61pub type WriteObserver = Arc<dyn Fn(&dyn StateObject) + 'static>;
63
64pub type ApplyObserver = Rc<dyn Fn(&[Arc<dyn StateObject>], SnapshotId) + 'static>;
66
67#[derive(Debug, Clone, PartialEq, Eq)]
69pub enum SnapshotApplyResult {
70 Success,
72 Failure,
74}
75
76impl SnapshotApplyResult {
77 pub fn is_success(&self) -> bool {
79 matches!(self, SnapshotApplyResult::Success)
80 }
81
82 pub fn is_failure(&self) -> bool {
84 matches!(self, SnapshotApplyResult::Failure)
85 }
86
87 #[track_caller]
89 pub fn check(&self) {
90 assert!(!self.is_failure(), "Snapshot apply failed");
91 }
92}
93
94pub type StateObjectId = usize;
96
97#[derive(Clone)]
102pub enum AnySnapshot {
103 Readonly(Arc<ReadonlySnapshot>),
104 Mutable(Arc<MutableSnapshot>),
105 NestedReadonly(Arc<NestedReadonlySnapshot>),
106 NestedMutable(Arc<NestedMutableSnapshot>),
107 Global(Arc<GlobalSnapshot>),
108 TransparentMutable(Arc<TransparentObserverMutableSnapshot>),
109 TransparentReadonly(Arc<TransparentObserverSnapshot>),
110}
111
112#[derive(Clone)]
119pub enum AnyMutableSnapshot {
120 Root(Arc<MutableSnapshot>),
121 Nested(Arc<NestedMutableSnapshot>),
122}
123
124impl AnyMutableSnapshot {
125 pub fn snapshot_id(&self) -> SnapshotId {
127 match self {
128 AnyMutableSnapshot::Root(s) => s.snapshot_id(),
129 AnyMutableSnapshot::Nested(s) => s.snapshot_id(),
130 }
131 }
132
133 pub fn invalid(&self) -> SnapshotIdSet {
135 match self {
136 AnyMutableSnapshot::Root(s) => s.invalid(),
137 AnyMutableSnapshot::Nested(s) => s.invalid(),
138 }
139 }
140
141 pub fn enter<T>(&self, f: impl FnOnce() -> T) -> T {
143 match self {
144 AnyMutableSnapshot::Root(s) => s.enter(f),
145 AnyMutableSnapshot::Nested(s) => s.enter(f),
146 }
147 }
148
149 pub fn apply(&self) -> SnapshotApplyResult {
151 match self {
152 AnyMutableSnapshot::Root(s) => s.apply(),
153 AnyMutableSnapshot::Nested(s) => s.apply(),
154 }
155 }
156
157 pub fn dispose(&self) {
159 match self {
160 AnyMutableSnapshot::Root(s) => s.dispose(),
161 AnyMutableSnapshot::Nested(s) => s.dispose(),
162 }
163 }
164}
165
166impl AnySnapshot {
167 pub fn snapshot_id(&self) -> SnapshotId {
169 match self {
170 AnySnapshot::Readonly(s) => s.snapshot_id(),
171 AnySnapshot::Mutable(s) => s.snapshot_id(),
172 AnySnapshot::NestedReadonly(s) => s.snapshot_id(),
173 AnySnapshot::NestedMutable(s) => s.snapshot_id(),
174 AnySnapshot::Global(s) => s.snapshot_id(),
175 AnySnapshot::TransparentMutable(s) => s.snapshot_id(),
176 AnySnapshot::TransparentReadonly(s) => s.snapshot_id(),
177 }
178 }
179
180 fn state(&self) -> &SnapshotState {
182 match self {
183 AnySnapshot::Readonly(s) => &s.state,
184 AnySnapshot::Mutable(s) => &s.state,
185 AnySnapshot::NestedReadonly(s) => &s.state,
186 AnySnapshot::NestedMutable(s) => &s.state,
187 AnySnapshot::Global(s) => &s.state,
188 AnySnapshot::TransparentMutable(s) => &s.state,
189 AnySnapshot::TransparentReadonly(s) => &s.state,
190 }
191 }
192
193 pub(crate) fn with_invalid<R>(&self, f: impl FnOnce(&SnapshotIdSet) -> R) -> R {
194 f(&self.state().invalid.borrow())
195 }
196
197 pub fn invalid(&self) -> SnapshotIdSet {
198 match self {
199 AnySnapshot::Readonly(s) => s.invalid(),
200 AnySnapshot::Mutable(s) => s.invalid(),
201 AnySnapshot::NestedReadonly(s) => s.invalid(),
202 AnySnapshot::NestedMutable(s) => s.invalid(),
203 AnySnapshot::Global(s) => s.invalid(),
204 AnySnapshot::TransparentMutable(s) => s.invalid(),
205 AnySnapshot::TransparentReadonly(s) => s.invalid(),
206 }
207 }
208
209 pub fn is_valid(&self, id: SnapshotId) -> bool {
211 let snapshot_id = self.snapshot_id();
212 id <= snapshot_id && !self.invalid().get(id)
213 }
214
215 pub fn read_only(&self) -> bool {
217 match self {
218 AnySnapshot::Readonly(_) => true,
219 AnySnapshot::Mutable(_) => false,
220 AnySnapshot::NestedReadonly(_) => true,
221 AnySnapshot::NestedMutable(_) => false,
222 AnySnapshot::Global(_) => false,
223 AnySnapshot::TransparentMutable(_) => false,
224 AnySnapshot::TransparentReadonly(_) => true,
225 }
226 }
227
228 pub fn root(&self) -> AnySnapshot {
230 match self {
231 AnySnapshot::Readonly(s) => AnySnapshot::Readonly(s.root_readonly()),
232 AnySnapshot::Mutable(s) => AnySnapshot::Mutable(s.root_mutable()),
233 AnySnapshot::NestedReadonly(s) => AnySnapshot::NestedReadonly(s.root_nested_readonly()),
234 AnySnapshot::NestedMutable(s) => AnySnapshot::Mutable(s.root_mutable()),
235 AnySnapshot::Global(s) => AnySnapshot::Global(s.root_global()),
236 AnySnapshot::TransparentMutable(s) => {
237 AnySnapshot::TransparentMutable(s.root_transparent_mutable())
238 }
239 AnySnapshot::TransparentReadonly(s) => {
240 AnySnapshot::TransparentReadonly(s.root_transparent_readonly())
241 }
242 }
243 }
244
245 pub fn is_same_transparent(&self, other: &Arc<TransparentObserverMutableSnapshot>) -> bool {
247 matches!(self, AnySnapshot::TransparentMutable(snapshot) if Arc::ptr_eq(snapshot, other))
248 }
249
250 pub fn is_same_transparent_mutable(
252 &self,
253 other: &Arc<TransparentObserverMutableSnapshot>,
254 ) -> bool {
255 self.is_same_transparent(other)
256 }
257
258 pub fn is_same_transparent_readonly(&self, other: &Arc<TransparentObserverSnapshot>) -> bool {
260 matches!(self, AnySnapshot::TransparentReadonly(snapshot) if Arc::ptr_eq(snapshot, other))
261 }
262
263 pub fn enter<T>(&self, f: impl FnOnce() -> T) -> T {
265 match self {
266 AnySnapshot::Readonly(s) => s.enter(f),
267 AnySnapshot::Mutable(s) => s.enter(f),
268 AnySnapshot::NestedReadonly(s) => s.enter(f),
269 AnySnapshot::NestedMutable(s) => s.enter(f),
270 AnySnapshot::Global(s) => s.enter(f),
271 AnySnapshot::TransparentMutable(s) => s.enter(f),
272 AnySnapshot::TransparentReadonly(s) => s.enter(f),
273 }
274 }
275
276 pub fn take_nested_snapshot(&self, read_observer: Option<ReadObserver>) -> AnySnapshot {
278 match self {
279 AnySnapshot::Readonly(s) => {
280 AnySnapshot::Readonly(s.take_nested_snapshot(read_observer))
281 }
282 AnySnapshot::Mutable(s) => AnySnapshot::Readonly(s.take_nested_snapshot(read_observer)),
283 AnySnapshot::NestedReadonly(s) => {
284 AnySnapshot::NestedReadonly(s.take_nested_snapshot(read_observer))
285 }
286 AnySnapshot::NestedMutable(s) => {
287 AnySnapshot::Readonly(s.take_nested_snapshot(read_observer))
288 }
289 AnySnapshot::Global(s) => AnySnapshot::Readonly(s.take_nested_snapshot(read_observer)),
290 AnySnapshot::TransparentMutable(s) => {
291 AnySnapshot::Readonly(s.take_nested_snapshot(read_observer))
292 }
293 AnySnapshot::TransparentReadonly(s) => {
294 AnySnapshot::TransparentReadonly(s.take_nested_snapshot(read_observer))
295 }
296 }
297 }
298
299 pub fn has_pending_changes(&self) -> bool {
301 match self {
302 AnySnapshot::Readonly(s) => s.has_pending_changes(),
303 AnySnapshot::Mutable(s) => s.has_pending_changes(),
304 AnySnapshot::NestedReadonly(s) => s.has_pending_changes(),
305 AnySnapshot::NestedMutable(s) => s.has_pending_changes(),
306 AnySnapshot::Global(s) => s.has_pending_changes(),
307 AnySnapshot::TransparentMutable(s) => s.has_pending_changes(),
308 AnySnapshot::TransparentReadonly(s) => s.has_pending_changes(),
309 }
310 }
311
312 pub fn dispose(&self) {
314 match self {
315 AnySnapshot::Readonly(s) => s.dispose(),
316 AnySnapshot::Mutable(s) => s.dispose(),
317 AnySnapshot::NestedReadonly(s) => s.dispose(),
318 AnySnapshot::NestedMutable(s) => s.dispose(),
319 AnySnapshot::Global(s) => s.dispose(),
320 AnySnapshot::TransparentMutable(s) => s.dispose(),
321 AnySnapshot::TransparentReadonly(s) => s.dispose(),
322 }
323 }
324
325 pub fn is_disposed(&self) -> bool {
327 match self {
328 AnySnapshot::Readonly(s) => s.is_disposed(),
329 AnySnapshot::Mutable(s) => s.is_disposed(),
330 AnySnapshot::NestedReadonly(s) => s.is_disposed(),
331 AnySnapshot::NestedMutable(s) => s.is_disposed(),
332 AnySnapshot::Global(s) => s.is_disposed(),
333 AnySnapshot::TransparentMutable(s) => s.is_disposed(),
334 AnySnapshot::TransparentReadonly(s) => s.is_disposed(),
335 }
336 }
337
338 pub fn record_read(&self, state: &dyn StateObject) {
340 match self {
341 AnySnapshot::Readonly(s) => s.record_read(state),
342 AnySnapshot::Mutable(s) => s.record_read(state),
343 AnySnapshot::NestedReadonly(s) => s.record_read(state),
344 AnySnapshot::NestedMutable(s) => s.record_read(state),
345 AnySnapshot::Global(s) => s.record_read(state),
346 AnySnapshot::TransparentMutable(s) => s.record_read(state),
347 AnySnapshot::TransparentReadonly(s) => s.record_read(state),
348 }
349 }
350
351 pub fn record_write(&self, state: Arc<dyn StateObject>) {
353 match self {
354 AnySnapshot::Readonly(s) => s.record_write(state),
355 AnySnapshot::Mutable(s) => s.record_write(state),
356 AnySnapshot::NestedReadonly(s) => s.record_write(state),
357 AnySnapshot::NestedMutable(s) => s.record_write(state),
358 AnySnapshot::Global(s) => s.record_write(state),
359 AnySnapshot::TransparentMutable(s) => s.record_write(state),
360 AnySnapshot::TransparentReadonly(s) => s.record_write(state),
361 }
362 }
363
364 pub fn apply(&self) -> SnapshotApplyResult {
366 match self {
367 AnySnapshot::Mutable(s) => s.apply(),
368 AnySnapshot::NestedMutable(s) => s.apply(),
369 AnySnapshot::Global(s) => s.apply(),
370 AnySnapshot::TransparentMutable(s) => s.apply(),
371 _ => panic!("Cannot apply a read-only snapshot"),
372 }
373 }
374
375 pub fn take_nested_mutable_snapshot(
377 &self,
378 read_observer: Option<ReadObserver>,
379 write_observer: Option<WriteObserver>,
380 ) -> AnySnapshot {
381 match self {
382 AnySnapshot::Mutable(s) => AnySnapshot::NestedMutable(
383 s.take_nested_mutable_snapshot(read_observer, write_observer),
384 ),
385 AnySnapshot::NestedMutable(s) => AnySnapshot::NestedMutable(
386 s.take_nested_mutable_snapshot(read_observer, write_observer),
387 ),
388 AnySnapshot::Global(s) => {
389 AnySnapshot::Mutable(s.take_nested_mutable_snapshot(read_observer, write_observer))
390 }
391 AnySnapshot::TransparentMutable(s) => AnySnapshot::TransparentMutable(
392 s.take_nested_mutable_snapshot(read_observer, write_observer),
393 ),
394 _ => panic!("Cannot take nested mutable snapshot from read-only snapshot"),
395 }
396 }
397}
398
399thread_local! {
400 static CURRENT_SNAPSHOT: RefCell<Option<AnySnapshot>> = const { RefCell::new(None) };
401}
402
403pub fn current_snapshot() -> Option<AnySnapshot> {
405 CURRENT_SNAPSHOT
406 .try_with(|cell| cell.borrow().clone())
407 .unwrap_or(None)
408}
409
410pub(crate) fn current_snapshot_reads_into(observer: &ReadObserver) -> bool {
411 CURRENT_SNAPSHOT
412 .try_with(|cell| match &*cell.borrow() {
413 Some(AnySnapshot::TransparentMutable(snapshot)) => {
414 snapshot.can_reuse() && snapshot.reads_into(observer)
415 }
416 _ => false,
417 })
418 .unwrap_or(false)
419}
420
421pub(crate) fn set_current_snapshot(snapshot: Option<AnySnapshot>) {
422 let _ = CURRENT_SNAPSHOT.try_with(|cell| {
423 *cell.borrow_mut() = snapshot;
424 });
425}
426
427struct CurrentSnapshotGuard {
428 previous: Option<AnySnapshot>,
429}
430
431impl CurrentSnapshotGuard {
432 fn enter(snapshot: AnySnapshot) -> Self {
433 let previous = current_snapshot();
434 set_current_snapshot(Some(snapshot));
435 Self { previous }
436 }
437}
438
439impl Drop for CurrentSnapshotGuard {
440 fn drop(&mut self) {
441 set_current_snapshot(self.previous.take());
442 }
443}
444
445pub(crate) fn enter_snapshot_scope<T>(snapshot: AnySnapshot, f: impl FnOnce() -> T) -> T {
446 let _guard = CurrentSnapshotGuard::enter(snapshot);
447 f()
448}
449
450pub fn take_mutable_snapshot(
459 read_observer: Option<ReadObserver>,
460 write_observer: Option<WriteObserver>,
461) -> AnyMutableSnapshot {
462 match current_snapshot() {
463 Some(AnySnapshot::Mutable(parent)) => AnyMutableSnapshot::Nested(
464 parent.take_nested_mutable_snapshot(read_observer, write_observer),
465 ),
466 Some(AnySnapshot::NestedMutable(parent)) => AnyMutableSnapshot::Nested(
467 parent.take_nested_mutable_snapshot(read_observer, write_observer),
468 ),
469 _ => AnyMutableSnapshot::Root(
470 GlobalSnapshot::get_or_create()
471 .take_nested_mutable_snapshot(read_observer, write_observer),
472 ),
473 }
474}
475
476pub fn take_transparent_observer_mutable_snapshot(
485 read_observer: Option<ReadObserver>,
486 write_observer: Option<WriteObserver>,
487) -> Arc<TransparentObserverMutableSnapshot> {
488 take_transparent_observer_mutable_snapshot_reusing(read_observer, write_observer, None)
489}
490
491pub(crate) fn take_transparent_observer_mutable_snapshot_reusing(
492 read_observer: Option<ReadObserver>,
493 write_observer: Option<WriteObserver>,
494 recycled: Option<Arc<TransparentObserverMutableSnapshot>>,
495) -> Arc<TransparentObserverMutableSnapshot> {
496 let parent = current_snapshot();
497 match parent {
498 Some(AnySnapshot::TransparentMutable(transparent)) if transparent.can_reuse() => {
499 let (parent_read, parent_write) = transparent.observers();
500 if already_observes(&read_observer, &parent_read)
501 && already_observes(&write_observer, &parent_write)
502 {
503 return transparent;
504 }
505 TransparentObserverMutableSnapshot::new_reusing(
506 recycled,
507 transparent.snapshot_id(),
508 transparent.invalid(),
509 merge_read_observers(read_observer, parent_read),
510 merge_write_observers(write_observer, parent_write),
511 Some(Arc::downgrade(&transparent)),
512 )
513 }
514 _ => {
515 let current = current_snapshot()
516 .unwrap_or_else(|| AnySnapshot::Global(GlobalSnapshot::get_or_create()));
517 let id = current.snapshot_id();
518 let invalid = current.invalid();
519 TransparentObserverMutableSnapshot::new_reusing(
520 recycled,
521 id,
522 invalid,
523 read_observer,
524 write_observer,
525 None,
526 )
527 }
528 }
529}
530
531fn already_observes(requested: &Option<ReadObserver>, installed: &Option<ReadObserver>) -> bool {
532 match (requested, installed) {
533 (None, _) => true,
534 (Some(requested), Some(installed)) => Arc::ptr_eq(requested, installed),
535 (Some(_), None) => false,
536 }
537}
538
539pub fn allocate_record_id() -> SnapshotId {
541 runtime::allocate_record_id()
542}
543
544pub(crate) fn peek_next_snapshot_id() -> SnapshotId {
545 runtime::peek_next_snapshot_id()
546}
547
548#[derive(Clone)]
549struct ObserverId(Rc<()>);
550
551impl ObserverId {
552 fn new() -> Self {
553 Self(Rc::new(()))
554 }
555}
556
557impl PartialEq for ObserverId {
558 fn eq(&self, other: &Self) -> bool {
559 Rc::ptr_eq(&self.0, &other.0)
560 }
561}
562
563impl Eq for ObserverId {}
564
565impl Hash for ObserverId {
566 fn hash<H: Hasher>(&self, state: &mut H) {
567 Rc::as_ptr(&self.0).hash(state);
568 }
569}
570
571thread_local! {
572 static APPLY_OBSERVERS: RefCell<HashMap<ObserverId, ApplyObserver>> = RefCell::new(HashMap::default());
573}
574
575thread_local! {
576 static LAST_WRITES: RefCell<HashMap<StateObjectId, SnapshotId>> = RefCell::new(HashMap::default());
577}
578
579thread_local! {
580 static EXTRA_STATE_OBJECTS: RefCell<crate::snapshot_weak_set::SnapshotWeakSet> = RefCell::new(crate::snapshot_weak_set::SnapshotWeakSet::new());
581}
582
583const UNUSED_RECORD_CLEANUP_INTERVAL: SnapshotId = 2;
584const UNUSED_RECORD_CLEANUP_BUSY_INTERVAL: SnapshotId = 1;
585const UNUSED_RECORD_CLEANUP_MIN_SIZE: usize = 64;
586
587thread_local! {
588 static LAST_UNUSED_RECORD_CLEANUP: Cell<SnapshotId> = const { Cell::new(0) };
589}
590
591#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
592pub struct SnapshotV2DebugStats {
593 pub apply_observers_len: usize,
594 pub apply_observers_cap: usize,
595 pub last_writes_len: usize,
596 pub last_writes_cap: usize,
597 pub extra_state_objects_len: usize,
598 pub extra_state_objects_cap: usize,
599 pub last_unused_record_cleanup: SnapshotId,
600}
601
602pub fn debug_snapshot_v2_stats() -> SnapshotV2DebugStats {
603 let (apply_observers_len, apply_observers_cap) = APPLY_OBSERVERS.with(|cell| {
604 let observers = cell.borrow();
605 (observers.len(), observers.capacity())
606 });
607 let (last_writes_len, last_writes_cap) = LAST_WRITES.with(|cell| {
608 let writes = cell.borrow();
609 (writes.len(), writes.capacity())
610 });
611 let SnapshotWeakSetDebugStats {
612 len: extra_state_objects_len,
613 capacity: extra_state_objects_cap,
614 } = EXTRA_STATE_OBJECTS.with(|cell| cell.borrow().debug_stats());
615 let last_unused_record_cleanup = LAST_UNUSED_RECORD_CLEANUP.with(Cell::get);
616
617 SnapshotV2DebugStats {
618 apply_observers_len,
619 apply_observers_cap,
620 last_writes_len,
621 last_writes_cap,
622 extra_state_objects_len,
623 extra_state_objects_cap,
624 last_unused_record_cleanup,
625 }
626}
627
628pub fn register_apply_observer(observer: ApplyObserver) -> ObserverHandle {
632 let id = ObserverId::new();
633 APPLY_OBSERVERS.with(|cell| {
634 cell.borrow_mut().insert(id.clone(), observer);
635 });
636 ObserverHandle {
637 kind: ObserverKind::Apply,
638 id,
639 }
640}
641
642pub struct ObserverHandle {
646 kind: ObserverKind,
647 id: ObserverId,
648}
649
650enum ObserverKind {
651 Apply,
652}
653
654impl Drop for ObserverHandle {
655 fn drop(&mut self) {
656 match self.kind {
657 ObserverKind::Apply => {
658 let _ = APPLY_OBSERVERS.try_with(|cell| {
662 cell.borrow_mut().remove(&self.id);
663 });
664 }
665 }
666 }
667}
668
669pub(crate) fn notify_apply_observers(modified: &[Arc<dyn StateObject>], snapshot_id: SnapshotId) {
670 APPLY_OBSERVERS.with(|cell| {
671 let observers: Vec<ApplyObserver> = cell.borrow().values().cloned().collect();
672 for observer in observers.into_iter() {
673 observer(modified, snapshot_id);
674 }
675 });
676}
677
678pub(crate) fn set_last_write(id: StateObjectId, snapshot_id: SnapshotId) {
679 LAST_WRITES.with(|cell| {
680 cell.borrow_mut().insert(id, snapshot_id);
681 });
682}
683
684#[cfg(test)]
685pub(crate) fn clear_last_writes() {
686 LAST_WRITES.with(|cell| {
687 cell.borrow_mut().clear();
688 });
689}
690
691pub(crate) fn check_and_overwrite_unused_records_locked() {
692 let _retiring = super::state::RetiredValuesScope::enter();
693 EXTRA_STATE_OBJECTS.with(|cell| {
694 cell.borrow_mut()
695 .remove_if(super::state::StateObject::overwrite_unused_records);
696 });
697}
698
699pub(crate) fn maybe_check_and_overwrite_unused_records_locked(current_snapshot_id: SnapshotId) {
700 let should_run = EXTRA_STATE_OBJECTS.with(|cell| {
701 let set = cell.borrow();
702 if set.is_empty() {
703 return false;
704 }
705 let last_cleanup = LAST_UNUSED_RECORD_CLEANUP.with(Cell::get);
706 let interval = if set.len() >= UNUSED_RECORD_CLEANUP_MIN_SIZE {
707 UNUSED_RECORD_CLEANUP_BUSY_INTERVAL
708 } else {
709 UNUSED_RECORD_CLEANUP_INTERVAL
710 };
711 current_snapshot_id.saturating_sub(last_cleanup) >= interval
712 });
713
714 if should_run {
715 LAST_UNUSED_RECORD_CLEANUP.with(|cell| cell.set(current_snapshot_id));
716 check_and_overwrite_unused_records_locked();
717 }
718}
719
720#[cfg(test)]
721pub(crate) fn clear_unused_record_cleanup_for_tests() {
722 LAST_UNUSED_RECORD_CLEANUP.with(|cell| cell.set(0));
723}
724
725pub(crate) fn optimistic_merges(
726 current_snapshot_id: SnapshotId,
727 base_parent_id: SnapshotId,
728 modified_objects: &[(StateObjectId, Arc<dyn StateObject>, SnapshotId)],
729 invalid_snapshots: &SnapshotIdSet,
730 applying_invalid: &SnapshotIdSet,
731) -> Option<HashMap<usize, Rc<StateRecord>>> {
732 if modified_objects.is_empty() {
733 return None;
734 }
735
736 let mut result: Option<HashMap<usize, Rc<StateRecord>>> = None;
737
738 for (_, state, writer_id) in modified_objects {
739 let head = state.first_record();
740
741 let Some(current) =
742 crate::state::readable_record_for(&head, current_snapshot_id, invalid_snapshots)
743 else {
744 continue;
745 };
746
747 let (previous_opt, found_base) =
748 mutable::find_previous_record(&head, base_parent_id, applying_invalid);
749 let previous = previous_opt?;
750
751 if !found_base || previous.snapshot_id() == crate::state::PREEXISTING_SNAPSHOT_ID {
752 continue;
753 }
754
755 if Rc::ptr_eq(¤t, &previous) {
756 continue;
757 }
758
759 let applied = mutable::find_record_by_id(&head, *writer_id)?;
760
761 let merged = state.merge_records(
762 Rc::clone(&previous),
763 Rc::clone(¤t),
764 Rc::clone(&applied),
765 )?;
766
767 result
768 .get_or_insert_with(HashMap::default)
769 .insert(Rc::as_ptr(¤t) as usize, merged);
770 }
771
772 result
773}
774
775#[expect(clippy::arc_with_non_send_sync)]
776fn merge_observers(a: Option<ReadObserver>, b: Option<ReadObserver>) -> Option<ReadObserver> {
777 match (a, b) {
778 (None, None) => None,
779 (Some(a), None) => Some(a),
780 (None, Some(b)) => Some(b),
781 (Some(a), Some(b)) => Some(Arc::new(move |state: &dyn StateObject| {
782 a(state);
783 b(state);
784 })),
785 }
786}
787
788pub fn merge_read_observers(
794 a: Option<ReadObserver>,
795 b: Option<ReadObserver>,
796) -> Option<ReadObserver> {
797 merge_observers(a, b)
798}
799
800pub fn merge_write_observers(
806 a: Option<WriteObserver>,
807 b: Option<WriteObserver>,
808) -> Option<WriteObserver> {
809 merge_observers(a, b)
810}
811
812pub(crate) struct SnapshotState {
813 pub(crate) id: Cell<SnapshotId>,
814 pub(crate) invalid: RefCell<SnapshotIdSet>,
815 pub(crate) pin_handle: Cell<PinHandle>,
816 pub(crate) disposed: Cell<bool>,
817 pub(crate) read_observer: RefCell<Option<ReadObserver>>,
818 pub(crate) write_observer: RefCell<Option<WriteObserver>>,
819 #[expect(clippy::type_complexity)]
820 pub(crate) modified: RefCell<HashMap<StateObjectId, (Arc<dyn StateObject>, SnapshotId)>>,
821 on_dispose: RefCell<Option<Box<dyn FnOnce()>>>,
822 runtime_tracked: bool,
823 pending_children: RefCell<HashSet<SnapshotId>>,
824}
825
826impl SnapshotState {
827 pub(crate) fn new(
828 id: SnapshotId,
829 invalid: SnapshotIdSet,
830 read_observer: Option<ReadObserver>,
831 write_observer: Option<WriteObserver>,
832 runtime_tracked: bool,
833 ) -> Self {
834 Self::new_with_pinning(
835 id,
836 invalid,
837 read_observer,
838 write_observer,
839 runtime_tracked,
840 true,
841 )
842 }
843
844 pub(crate) fn new_with_pinning(
845 id: SnapshotId,
846 invalid: SnapshotIdSet,
847 read_observer: Option<ReadObserver>,
848 write_observer: Option<WriteObserver>,
849 runtime_tracked: bool,
850 should_pin: bool,
851 ) -> Self {
852 let pin_handle = if should_pin {
853 snapshot_pinning::track_pinning(id, &invalid)
854 } else {
855 snapshot_pinning::PinHandle::INVALID
856 };
857 Self {
858 id: Cell::new(id),
859 invalid: RefCell::new(invalid),
860 pin_handle: Cell::new(pin_handle),
861 disposed: Cell::new(false),
862 read_observer: RefCell::new(read_observer),
863 write_observer: RefCell::new(write_observer),
864 modified: RefCell::new(HashMap::default()),
865 on_dispose: RefCell::new(None),
866 runtime_tracked,
867 pending_children: RefCell::new(HashSet::default()),
868 }
869 }
870
871 pub(crate) fn record_read(&self, state: &dyn StateObject) {
872 if let Some(observer) = self.read_observer.borrow().as_ref() {
873 observer(state);
874 }
875 }
876
877 pub(crate) fn record_write(&self, state: Arc<dyn StateObject>, writer_id: SnapshotId) {
878 let state_id = state.object_id().as_usize();
879
880 let mut modified = self.modified.borrow_mut();
881
882 match modified.entry(state_id) {
883 std::collections::hash_map::Entry::Vacant(e) => {
884 if let Some(observer) = self.write_observer.borrow().as_ref() {
885 observer(&*state);
886 }
887 e.insert((state, writer_id));
888 }
889 std::collections::hash_map::Entry::Occupied(mut e) => {
890 e.insert((state, writer_id));
891 }
892 }
893 }
894
895 pub(crate) fn dispose(&self) {
896 if !self.disposed.replace(true) {
897 let pin_handle = self.pin_handle.get();
898 snapshot_pinning::release_pinning(pin_handle);
899 if let Some(cb) = self.on_dispose.borrow_mut().take() {
900 cb();
901 }
902 if self.runtime_tracked {
903 close_snapshot(self.id.get());
904 }
905 }
906 }
907
908 pub(crate) fn add_pending_child(&self, id: SnapshotId) {
909 self.pending_children.borrow_mut().insert(id);
910 }
911
912 pub(crate) fn remove_pending_child(&self, id: SnapshotId) {
913 self.pending_children.borrow_mut().remove(&id);
914 }
915
916 pub(crate) fn has_pending_children(&self) -> bool {
917 !self.pending_children.borrow().is_empty()
918 }
919
920 pub(crate) fn pending_children(&self) -> Vec<SnapshotId> {
921 self.pending_children.borrow().iter().copied().collect()
922 }
923
924 pub(crate) fn set_on_dispose<F>(&self, f: F)
925 where
926 F: FnOnce() + 'static,
927 {
928 *self.on_dispose.borrow_mut() = Some(Box::new(f));
929 }
930}
931
932pub(crate) trait NestedMutableHost {
933 fn snapshot_state(&self) -> &SnapshotState;
934 fn nested_count(&self) -> &Cell<usize>;
935}
936
937pub(crate) fn clear_nested_child_on_dispose<P>(
938 parent: &Arc<P>,
939 child_id: SnapshotId,
940) -> impl FnOnce() + 'static
941where
942 P: NestedMutableHost + 'static,
943{
944 let weak = Arc::downgrade(parent);
945 move || {
946 if let Some(parent) = weak.upgrade() {
947 let nested_count = parent.nested_count();
948 if nested_count.get() > 0 {
949 nested_count.set(nested_count.get().saturating_sub(1));
950 }
951 let state = parent.snapshot_state();
952 let new_invalid = state.invalid.borrow().clone().clear(child_id);
953 state.invalid.replace(new_invalid);
954 state.remove_pending_child(child_id);
955 }
956 }
957}
958
959pub(crate) fn allocate_nested_mutable_snapshot<P>(
960 parent: &Arc<P>,
961 root: Weak<MutableSnapshot>,
962 read_observer: Option<ReadObserver>,
963 write_observer: Option<WriteObserver>,
964) -> Arc<NestedMutableSnapshot>
965where
966 P: NestedMutableHost + 'static,
967{
968 let state = parent.snapshot_state();
969 let merged_read = merge_read_observers(read_observer, state.read_observer.borrow().clone());
970 let merged_write = merge_write_observers(write_observer, state.write_observer.borrow().clone());
971
972 let parent_id = state.id.get();
973 let current_invalid = state.invalid.borrow().clone();
974
975 let (new_id, _runtime_invalid) = allocate_snapshot();
976
977 let parent_invalid_with_child = current_invalid.set(new_id);
978 state.invalid.replace(parent_invalid_with_child);
979
980 let invalid = current_invalid.add_range(parent_id + 1, new_id);
981
982 let nested = NestedMutableSnapshot::new(
983 new_id,
984 invalid,
985 merged_read,
986 merged_write,
987 root,
988 state.id.get(),
989 );
990
991 let nested_count = parent.nested_count();
992 nested_count.set(nested_count.get() + 1);
993 state.add_pending_child(new_id);
994
995 nested.set_on_dispose(clear_nested_child_on_dispose(parent, new_id));
996
997 nested
998}
999
1000#[cfg(test)]
1001#[path = "tests/snapshot_v2_tests.rs"]
1002mod tests;