1use std::collections::{HashMap, HashSet};
10use std::sync::atomic::{AtomicU64, Ordering};
11use std::sync::Arc;
12
13use super::IsolationLevel;
14use crate::storage::entity_id::EntityId;
15
16pub(crate) const AUTOCOMMIT_POOL_BATCH: u64 = 16;
23
24pub type Xid = u64;
26
27pub const XID_NONE: Xid = 0;
30
31#[derive(Debug, Clone)]
37pub struct Snapshot {
38 pub xid: Xid,
41 pub in_progress: HashSet<Xid>,
45}
46
47impl Snapshot {
48 pub fn sees(&self, xmin: Xid, xmax: Xid) -> bool {
57 super::visibility::is_visible(xmin, xmax, self.xid, &self.in_progress, &HashSet::new())
58 }
59}
60
61#[derive(Debug, Clone)]
64pub struct TxnContext {
65 pub xid: Xid,
66 pub isolation: IsolationLevel,
67 pub snapshot: Snapshot,
70 pub savepoints: Vec<(String, Xid)>,
79 pub released_sub_xids: Vec<Xid>,
85}
86
87impl TxnContext {
88 pub fn writer_xid(&self) -> Xid {
92 self.savepoints.last().map(|(_, x)| *x).unwrap_or(self.xid)
93 }
94}
95
96pub struct SnapshotManager {
116 next_xid: AtomicU64,
117 visibility_revision: AtomicU64,
118 state: parking_lot::RwLock<ManagerState>,
119 autocommit_pool: parking_lot::Mutex<AutocommitPool>,
129}
130
131pub type LogicalRowKey = (String, EntityId);
132
133#[derive(Default)]
134struct SerializableTxnState {
135 read_set: HashSet<LogicalRowKey>,
136 vault_reads: HashSet<(String, Option<String>, Option<String>)>,
137 incoming_rw: HashSet<Xid>,
138 outgoing_rw: HashSet<Xid>,
139 committed: bool,
140}
141
142#[derive(Default)]
143struct AutocommitPool {
144 next: Xid,
145 end: Xid,
146}
147
148#[derive(Default)]
149struct ManagerState {
150 active: HashSet<Xid>,
152 snapshot_horizons: HashMap<Xid, Xid>,
155 statement_horizons: HashMap<Xid, usize>,
158 aborted: HashSet<Xid>,
161 untracked_visibility_revision: u64,
164 pinned: HashMap<Xid, u32>,
170 serializable: HashMap<Xid, SerializableTxnState>,
171}
172
173pub struct SnapshotRetentionGuard {
175 manager: Arc<SnapshotManager>,
176 horizon: Xid,
177}
178
179impl Drop for SnapshotRetentionGuard {
180 fn drop(&mut self) {
181 let mut state = self.manager.state.write();
182 let count = state
183 .statement_horizons
184 .get_mut(&self.horizon)
185 .expect("a live snapshot retention guard has a registered horizon");
186 if *count == 1 {
187 state.statement_horizons.remove(&self.horizon);
188 } else {
189 *count -= 1;
190 }
191 }
192}
193
194impl SnapshotManager {
195 pub fn new() -> Self {
196 Self {
197 next_xid: AtomicU64::new(1),
199 visibility_revision: AtomicU64::new(0),
200 state: parking_lot::RwLock::new(ManagerState::default()),
201 autocommit_pool: parking_lot::Mutex::new(AutocommitPool::default()),
203 }
204 }
205
206 pub fn begin(&self) -> Xid {
224 let mut state = self.state.write();
225 let mut pool = self.autocommit_pool.lock();
229 pool.next = pool.end;
230 let xid = self.next_xid.fetch_add(1, Ordering::Relaxed);
231 state.active.insert(xid);
232 self.visibility_revision.fetch_add(1, Ordering::Release);
233 xid
234 }
235
236 pub fn fresh_read_snapshot(&self) -> Snapshot {
251 let state = self.state.read();
252 self.fresh_read_snapshot_locked(&state)
253 }
254
255 fn fresh_read_snapshot_locked(&self, state: &ManagerState) -> Snapshot {
256 let pool = self.autocommit_pool.lock();
257 let xid = self.next_xid.load(Ordering::Relaxed).saturating_sub(1);
260 let mut in_progress: HashSet<Xid> = state.active.iter().copied().collect();
261 in_progress.extend(pool.next..pool.end);
264 Snapshot { xid, in_progress }
265 }
266
267 pub fn fresh_retained_snapshot(self: &Arc<Self>) -> (Snapshot, SnapshotRetentionGuard) {
271 let mut state = self.state.write();
272 let snapshot = self.fresh_read_snapshot_locked(&state);
273 let guard = self.retain_snapshot_locked(&snapshot, &mut state);
274 (snapshot, guard)
275 }
276
277 pub fn retain_snapshot(
280 self: &Arc<Self>,
281 snapshot: Snapshot,
282 ) -> (Snapshot, SnapshotRetentionGuard) {
283 let mut state = self.state.write();
284 let guard = self.retain_snapshot_locked(&snapshot, &mut state);
285 (snapshot, guard)
286 }
287
288 fn retain_snapshot_locked(
289 self: &Arc<Self>,
290 snapshot: &Snapshot,
291 state: &mut ManagerState,
292 ) -> SnapshotRetentionGuard {
293 let horizon = snapshot
296 .in_progress
297 .iter()
298 .copied()
299 .fold(snapshot.xid.saturating_add(1), Xid::min);
300 *state.statement_horizons.entry(horizon).or_default() += 1;
301 SnapshotRetentionGuard {
302 manager: Arc::clone(self),
303 horizon,
304 }
305 }
306
307 pub fn snapshot(&self, xid: Xid) -> Snapshot {
311 let mut state = self.state.write();
312 let pool = self.autocommit_pool.lock();
314 let mut in_progress: HashSet<Xid> =
315 state.active.iter().copied().filter(|&x| x != xid).collect();
316 in_progress.extend(pool.next..pool.end);
317 if state.active.contains(&xid) {
318 let horizon = in_progress.iter().copied().fold(xid, Xid::min);
319 state
320 .snapshot_horizons
321 .entry(xid)
322 .and_modify(|previous| *previous = (*previous).min(horizon))
323 .or_insert(horizon);
324 }
325 Snapshot { xid, in_progress }
326 }
327
328 pub fn commit(&self, xid: Xid) {
331 let mut state = self.state.write();
332 let was_active = state.active.remove(&xid);
333 state.snapshot_horizons.remove(&xid);
334 let was_aborted = state.aborted.remove(&xid);
337 if was_active || was_aborted {
338 self.visibility_revision.fetch_add(1, Ordering::Release);
339 }
340 if let Some(ssi) = state.serializable.get_mut(&xid) {
341 ssi.committed = true;
342 }
343 Self::prune_serializable_state(&mut state);
344 }
345
346 pub fn allocate_committed_xid(&self) -> Xid {
371 let mut pool = self.autocommit_pool.lock();
372 if pool.next >= pool.end {
373 let start = self
378 .next_xid
379 .fetch_add(AUTOCOMMIT_POOL_BATCH, Ordering::Relaxed);
380 pool.next = start;
381 pool.end = start + AUTOCOMMIT_POOL_BATCH;
382 }
383 let xid = pool.next;
384 pool.next += 1;
385 xid
386 }
387
388 pub fn rollback(&self, xid: Xid) {
392 let mut state = self.state.write();
393 state.snapshot_horizons.remove(&xid);
394 let was_active = state.active.remove(&xid);
395 let newly_aborted = state.aborted.insert(xid);
396 if newly_aborted && !was_active {
397 state.untracked_visibility_revision = state
398 .untracked_visibility_revision
399 .checked_add(1)
400 .expect("visibility invalidation counter must not overflow");
401 }
402 if was_active || newly_aborted {
403 self.visibility_revision.fetch_add(1, Ordering::Release);
404 }
405 state.serializable.remove(&xid);
406 for ssi in state.serializable.values_mut() {
407 ssi.incoming_rw.remove(&xid);
408 ssi.outgoing_rw.remove(&xid);
409 }
410 Self::prune_serializable_state(&mut state);
411 }
412
413 pub fn begin_serializable(&self, xid: Xid) {
414 self.state.write().serializable.entry(xid).or_default();
415 }
416
417 pub fn record_serializable_read(&self, reader: Xid, collection: &str, logical_id: EntityId) {
418 let mut state = self.state.write();
419 if let Some(ssi) = state.serializable.get_mut(&reader) {
420 ssi.read_set.insert((collection.to_string(), logical_id));
421 }
422 }
423
424 pub fn record_serializable_vault_read(
425 &self,
426 reader: Xid,
427 collection: &str,
428 tenant: Option<&str>,
429 key: Option<&str>,
430 ) {
431 if let Some(ssi) = self.state.write().serializable.get_mut(&reader) {
432 ssi.vault_reads.insert((
433 collection.into(),
434 tenant.map(str::to_owned),
435 key.map(str::to_owned),
436 ));
437 }
438 }
439
440 pub fn serializable_vault_commit_would_be_dangerous(
441 &self,
442 writer: Xid,
443 write_set: &HashSet<(String, Option<String>, String)>,
444 ) -> bool {
445 let mut state = self.state.write();
446 if !state.serializable.contains_key(&writer) {
447 return false;
448 }
449 let readers = state
450 .serializable
451 .iter()
452 .filter_map(|(&reader, ssi)| {
453 (reader != writer
454 && ssi.vault_reads.iter().any(|(collection, tenant, key)| {
455 write_set
456 .iter()
457 .any(|(written_collection, written_tenant, written_key)| {
458 collection == written_collection
459 && tenant == written_tenant
460 && key.as_ref().is_none_or(|key| key == written_key)
461 })
462 }))
463 .then_some(reader)
464 })
465 .collect();
466 Self::serializable_readers_would_be_dangerous(&mut state, writer, readers)
467 }
468
469 pub fn serializable_commit_would_be_dangerous(
470 &self,
471 writer: Xid,
472 write_set: &HashSet<LogicalRowKey>,
473 ) -> bool {
474 if write_set.is_empty() {
475 return false;
476 }
477
478 let mut state = self.state.write();
479 if !state.serializable.contains_key(&writer) {
480 return false;
481 }
482
483 let readers: Vec<Xid> = state
484 .serializable
485 .iter()
486 .filter_map(|(&reader, ssi)| {
487 if reader != writer && !ssi.read_set.is_disjoint(write_set) {
488 Some(reader)
489 } else {
490 None
491 }
492 })
493 .collect();
494
495 Self::serializable_readers_would_be_dangerous(&mut state, writer, readers)
496 }
497
498 fn serializable_readers_would_be_dangerous(
499 state: &mut ManagerState,
500 writer: Xid,
501 readers: Vec<Xid>,
502 ) -> bool {
503 for reader in readers {
504 if let Some(reader_state) = state.serializable.get_mut(&reader) {
505 reader_state.outgoing_rw.insert(writer);
506 }
507 if let Some(writer_state) = state.serializable.get_mut(&writer) {
508 writer_state.incoming_rw.insert(reader);
509 }
510 }
511
512 let Some(writer_state) = state.serializable.get(&writer) else {
513 return false;
514 };
515 let has_incoming = !writer_state.incoming_rw.is_empty();
516 let has_outgoing_to_committed = writer_state
517 .outgoing_rw
518 .iter()
519 .any(|xid| state.serializable.get(xid).is_some_and(|ssi| ssi.committed));
520 has_incoming && has_outgoing_to_committed
521 }
522
523 fn prune_serializable_state(state: &mut ManagerState) {
524 if state.active.is_empty() {
525 state.serializable.clear();
526 }
527 }
528
529 pub fn is_aborted(&self, xid: Xid) -> bool {
532 self.state.read().aborted.contains(&xid)
533 }
534
535 pub fn checkpoint_excluded_state(&self) -> (u64, HashSet<Xid>) {
538 let (revision, excluded, _) = self.checkpoint_projection_state();
539 (revision, excluded)
540 }
541
542 pub fn checkpoint_projection_state(&self) -> (u64, HashSet<Xid>, u64) {
543 let state = self.state.read();
544 let mut excluded = state.aborted.clone();
545 excluded.extend(state.active.iter().copied());
546 (
547 self.visibility_revision.load(Ordering::Acquire),
548 excluded,
549 state.untracked_visibility_revision,
550 )
551 }
552
553 pub fn is_active(&self, xid: Xid) -> bool {
555 self.state.read().active.contains(&xid)
556 }
557
558 pub fn row_reserves_unique_key(&self, xmin: Xid, xmax: Xid, own_xids: &HashSet<Xid>) -> bool {
562 let state = self.state.read();
563 if state.aborted.contains(&xmin) {
564 return false;
565 }
566 if state.active.contains(&xmin) && !own_xids.contains(&xmin) {
567 return true;
568 }
569 if xmax == 0 || state.aborted.contains(&xmax) {
570 return true;
571 }
572 !own_xids.contains(&xmax) && state.active.contains(&xmax)
573 }
574
575 pub fn oldest_active_xid(&self) -> Option<Xid> {
578 self.state.read().active.iter().copied().min()
579 }
580
581 pub fn oldest_required_xid(&self) -> Option<Xid> {
584 let state = self.state.read();
585 state
586 .active
587 .iter()
588 .chain(state.snapshot_horizons.values())
589 .chain(state.statement_horizons.keys())
590 .chain(state.pinned.keys())
591 .copied()
592 .min()
593 }
594
595 pub fn oldest_pinned_xid(&self) -> Option<Xid> {
599 self.state.read().pinned.keys().copied().min()
600 }
601
602 pub fn peek_next_xid(&self) -> Xid {
605 self.next_xid.load(Ordering::Relaxed)
606 }
607
608 pub fn observe_committed_xid(&self, xid: Xid) {
611 if xid == XID_NONE {
612 return;
613 }
614 let mut pool = self.autocommit_pool.lock();
619 let target = xid.saturating_add(1);
620 pool.next = pool.next.max(target.min(pool.end));
621 let mut current = self.next_xid.load(Ordering::Relaxed);
622 while current < target {
623 match self.next_xid.compare_exchange(
624 current,
625 target,
626 Ordering::Relaxed,
627 Ordering::Relaxed,
628 ) {
629 Ok(_) => return,
630 Err(actual) => current = actual,
631 }
632 }
633 }
634
635 pub fn prune_aborted(&self, below: Xid) {
641 let mut state = self.state.write();
642 let ManagerState {
643 aborted, pinned, ..
644 } = &mut *state;
645 let previous = aborted.len();
646 aborted.retain(|&x| x >= below || pinned.contains_key(&x));
647 if previous != aborted.len() {
648 self.visibility_revision.fetch_add(1, Ordering::Release);
649 }
650 }
651
652 pub fn pin(&self, xid: Xid) {
655 if xid == XID_NONE {
656 return;
657 }
658 let mut state = self.state.write();
659 *state.pinned.entry(xid).or_insert(0) += 1;
660 }
661
662 pub fn unpin(&self, xid: Xid) {
665 if xid == XID_NONE {
666 return;
667 }
668 let mut state = self.state.write();
669 if let Some(count) = state.pinned.get_mut(&xid) {
670 if *count <= 1 {
671 state.pinned.remove(&xid);
672 } else {
673 *count -= 1;
674 }
675 }
676 }
677
678 pub fn is_pinned(&self, xid: Xid) -> bool {
680 self.state.read().pinned.contains_key(&xid)
681 }
682
683 pub fn pin_count(&self, xid: Xid) -> u32 {
685 self.state.read().pinned.get(&xid).copied().unwrap_or(0)
686 }
687}
688
689impl Default for SnapshotManager {
690 fn default() -> Self {
691 Self::new()
692 }
693}
694
695#[cfg(test)]
696mod tests {
697 use super::*;
698
699 #[test]
700 fn xids_are_monotonic() {
701 let m = SnapshotManager::new();
702 let a = m.begin();
703 let b = m.begin();
704 let c = m.begin();
705 assert!(a < b && b < c);
706 }
707
708 #[test]
709 fn snapshot_excludes_concurrent_writers() {
710 let m = SnapshotManager::new();
711 let writer = m.begin();
712 let reader = m.begin();
713 let snap = m.snapshot(reader);
714 assert!(snap.in_progress.contains(&writer));
716 assert!(!snap.sees(writer, XID_NONE));
718 }
719
720 #[test]
721 fn fresh_read_snapshot_sees_latest_committed_hides_active() {
722 let m = SnapshotManager::new();
723 let committed = m.begin();
727 m.commit(committed);
728 let active = m.begin();
729
730 let snap = m.fresh_read_snapshot();
731 assert!(snap.sees(committed, XID_NONE));
732 assert!(!snap.sees(active, XID_NONE));
733 assert!(snap.in_progress.contains(&active));
734 }
735
736 #[test]
737 fn fresh_read_snapshot_refreshes_between_calls() {
738 let m = SnapshotManager::new();
739 let later = m.begin();
742 let first = m.fresh_read_snapshot();
743 assert!(!first.sees(later, XID_NONE));
744 m.commit(later);
745 let second = m.fresh_read_snapshot();
746 assert!(second.sees(later, XID_NONE));
747 }
748
749 #[test]
750 fn committed_rows_become_visible() {
751 let m = SnapshotManager::new();
752 let writer = m.begin();
753 m.commit(writer);
754 let reader = m.begin();
755 let snap = m.snapshot(reader);
756 assert!(snap.sees(writer, XID_NONE));
758 }
759
760 #[test]
761 fn rolled_back_writers_stay_hidden() {
762 let m = SnapshotManager::new();
763 let writer = m.begin();
764 m.rollback(writer);
765 assert!(m.is_aborted(writer));
766 }
768
769 #[test]
770 fn pre_mvcc_rows_always_visible() {
771 let m = SnapshotManager::new();
772 let reader = m.begin();
773 let snap = m.snapshot(reader);
774 assert!(snap.sees(XID_NONE, XID_NONE));
775 }
776
777 #[test]
778 fn deletion_xmax_respected() {
779 let m = SnapshotManager::new();
780 let creator = m.begin();
781 m.commit(creator);
782 let deleter = m.begin();
783 m.commit(deleter);
784 let reader = m.begin();
785 let snap = m.snapshot(reader);
786 assert!(!snap.sees(creator, deleter));
788 }
789
790 #[test]
791 fn pin_blocks_prune_of_aborted_xid() {
792 let m = SnapshotManager::new();
793 let writer = m.begin();
794 m.rollback(writer);
795 assert!(m.is_aborted(writer));
796 m.pin(writer);
797 m.prune_aborted(writer + 1);
799 assert!(m.is_aborted(writer));
800 m.unpin(writer);
801 m.prune_aborted(writer + 1);
802 assert!(!m.is_aborted(writer));
803 }
804
805 #[test]
806 fn pin_is_reference_counted() {
807 let m = SnapshotManager::new();
808 let x = m.begin();
809 m.pin(x);
810 m.pin(x);
811 assert_eq!(m.pin_count(x), 2);
812 m.unpin(x);
813 assert_eq!(m.pin_count(x), 1);
814 assert!(m.is_pinned(x));
815 m.unpin(x);
816 assert_eq!(m.pin_count(x), 0);
817 assert!(!m.is_pinned(x));
818 m.unpin(x);
820 assert_eq!(m.pin_count(x), 0);
821 }
822
823 #[test]
824 fn pin_xid_none_is_noop() {
825 let m = SnapshotManager::new();
826 m.pin(XID_NONE);
827 assert!(!m.is_pinned(XID_NONE));
828 assert_eq!(m.pin_count(XID_NONE), 0);
829 }
830
831 #[test]
832 fn allocate_committed_xid_is_monotonic_and_unique() {
833 let m = SnapshotManager::new();
834 let mut seen = HashSet::new();
835 let mut last = 0u64;
836 for _ in 0..50 {
838 let x = m.allocate_committed_xid();
839 assert!(x > last, "xids must be strictly increasing: {x} > {last}");
840 assert!(seen.insert(x), "duplicate xid handed out: {x}");
841 last = x;
842 }
843 }
844
845 #[test]
846 fn mixed_allocations_preserve_transaction_order_and_reclaim_horizon() {
847 let manager = Arc::new(SnapshotManager::new());
848 let old = manager.allocate_committed_xid();
849 let deletion = manager.begin();
850 manager.commit(deletion);
851 let next_insert = manager.allocate_committed_xid();
852 assert!(
853 next_insert > deletion,
854 "a later insert cannot reuse an older reserved xid"
855 );
856 let (snapshot, _retention) = manager.fresh_retained_snapshot();
857 assert!(!snapshot.sees(old, deletion));
858 assert!(snapshot.sees(next_insert, XID_NONE));
859 assert!(
860 manager.oldest_required_xid().expect("retained statement") > deletion,
861 "unused allocator slots cannot keep already visible deletes resident"
862 );
863 }
864
865 #[test]
866 fn allocate_committed_xid_skips_active_set() {
867 let m = SnapshotManager::new();
868 let _x = m.allocate_committed_xid();
869 assert_eq!(m.oldest_active_xid(), None);
873 }
874
875 #[test]
876 fn allocate_committed_xid_visible_to_subsequent_snapshots() {
877 let m = SnapshotManager::new();
878 let writer = m.allocate_committed_xid();
879 let reader = m.begin();
880 let snap = m.snapshot(reader);
881 assert!(!snap.in_progress.contains(&writer));
885 assert!(!m.is_aborted(writer));
886 assert!(snap.sees(writer, XID_NONE));
887 }
888
889 #[test]
890 fn allocate_committed_xid_does_not_block_concurrent_begin() {
891 let m = SnapshotManager::new();
895 let tx = m.begin();
896 let auto1 = m.allocate_committed_xid();
897 let auto2 = m.allocate_committed_xid();
898 m.commit(tx);
899 assert!(tx < auto1 && auto1 < auto2);
900 assert_eq!(m.oldest_active_xid(), None);
902 }
903
904 #[test]
905 fn oldest_active_is_min_live_xid() {
906 let m = SnapshotManager::new();
907 let a = m.begin();
908 let b = m.begin();
909 assert_eq!(m.oldest_active_xid(), Some(a));
910 m.commit(a);
911 assert_eq!(m.oldest_active_xid(), Some(b));
912 m.commit(b);
913 assert_eq!(m.oldest_active_xid(), None);
914 }
915 #[test]
916 fn read_snapshot_excludes_first_future_writer_and_deleter() {
917 let manager = SnapshotManager::new();
918 let old = manager.begin();
919 manager.commit(old);
920 let snapshot = manager.fresh_read_snapshot();
921 let future = manager.begin();
922 manager.commit(future);
923 assert!(!snapshot.sees(future, XID_NONE));
924 assert!(snapshot.sees(old, future));
925 let fresh = manager.fresh_read_snapshot();
926 assert!(fresh.sees(future, XID_NONE));
927 assert!(!fresh.sees(old, future));
928 }
929
930 #[test]
931 fn snapshots_hide_autocommit_writers_allocated_after_begin() {
932 let manager = SnapshotManager::new();
933 let old = manager.allocate_committed_xid();
934 let reader = manager.begin();
935 let transaction_snapshot = manager.snapshot(reader);
936 let statement_snapshot = manager.fresh_read_snapshot();
937 let future = manager.allocate_committed_xid();
938 assert!(future > reader, "begin retires the older reserved range");
939 for snapshot in [transaction_snapshot, statement_snapshot] {
940 assert!(snapshot.sees(old, XID_NONE));
941 assert!(!snapshot.sees(future, XID_NONE));
942 assert!(snapshot.sees(old, future));
943 }
944 assert!(manager.fresh_read_snapshot().sees(future, XID_NONE));
945 manager.rollback(reader);
946 }
947
948 #[test]
949 fn retiring_unused_slots_preserves_an_older_readers_history() {
950 let manager = Arc::new(SnapshotManager::new());
951 let old = manager.allocate_committed_xid();
952 let (snapshot, retention) = manager.fresh_retained_snapshot();
953 let deletion = manager.begin();
954 manager.commit(deletion);
955 let next_insert = manager.allocate_committed_xid();
956 let (_current, _current_retention) = manager.fresh_retained_snapshot();
957 assert!(snapshot.sees(old, deletion));
958 assert!(!snapshot.sees(next_insert, XID_NONE));
959 assert!(manager.oldest_required_xid().expect("old reader retained") <= deletion);
960 drop(retention);
961 assert!(
962 manager
963 .oldest_required_xid()
964 .expect("current reader retained")
965 > deletion
966 );
967 }
968
969 #[test]
970 fn replay_retires_reserved_xids_without_changing_captured_snapshots() {
971 let manager = SnapshotManager::new();
972 let first = manager.allocate_committed_xid();
973 let captured = manager.fresh_read_snapshot();
974 let replayed = first + 2;
975 manager.observe_committed_xid(replayed);
976 assert!(!captured.sees(replayed, XID_NONE));
977 assert!(manager.fresh_read_snapshot().sees(replayed, XID_NONE));
978 assert!(manager.allocate_committed_xid() > replayed);
979
980 let beyond_pool = manager.peek_next_xid() + 10;
981 let captured = manager.fresh_read_snapshot();
982 manager.observe_committed_xid(beyond_pool);
983 assert!(!captured.sees(beyond_pool, XID_NONE));
984 assert!(manager.fresh_read_snapshot().sees(beyond_pool, XID_NONE));
985 assert!(manager.allocate_committed_xid() > beyond_pool);
986 }
987}