Skip to main content

loro_internal/oplog/
loro_dag.rs

1use crate::change::{Change, Lamport};
2use crate::dag::{Dag, DagNode};
3use crate::id::{Counter, ID};
4use crate::span::{HasId, HasLamport};
5use crate::sync::Mutex;
6use crate::version::{shrink_frontiers, Frontiers, ImVersionVector, VersionVector};
7use loro_common::{HasCounter, HasCounterSpan, HasIdSpan, HasLamportSpan, PeerID};
8use once_cell::sync::OnceCell;
9use rle::{HasIndex, HasLength, Mergable, Sliceable};
10use rustc_hash::FxHashSet;
11use smallvec::SmallVec;
12use std::cmp::Ordering;
13use std::collections::{BTreeMap, BTreeSet, BinaryHeap};
14use std::fmt::Display;
15use std::ops::{ControlFlow, Deref};
16use std::sync::atomic::{AtomicBool, Ordering as AtomicOrdering};
17use std::sync::Arc;
18use tracing::instrument;
19
20use super::change_store::BatchDecodeInfo;
21use super::ChangeStore;
22
23/// [AppDag] maintains the causal graph of the app.
24/// It's faster to answer questions like what the meet of two versions is
25#[derive(Debug)]
26pub struct AppDag {
27    change_store: ChangeStore,
28    /// It only contains nodes that are already parsed.
29    ///
30    /// - All the unparsed op ids must be included in `unparsed_vv`.
31    /// - All the parsed and unparsed op ids must be included in `vv`.
32    map: Mutex<BTreeMap<ID, AppDagNode>>,
33    /// The latest known frontiers
34    frontiers: Frontiers,
35    /// The latest known version vector
36    vv: VersionVector,
37    /// The earliest known frontiers
38    shallow_since_frontiers: Frontiers,
39    /// The deps of the shallow frontiers
40    shallow_root_frontiers_deps: Frontiers,
41    /// The vv of shallow_frontiers_deps
42    shallow_since_vv: ImVersionVector,
43    /// Ops included in the version vector but not parsed yet
44    ///
45    /// # Invariants
46    ///
47    /// - `vv` >= `unparsed_vv`
48    unparsed_vv: Mutex<VersionVector>,
49    /// It's a set of points which are deps of some parsed ops.
50    /// But the ops in this set are not parsed yet. When they are parsed,
51    /// we need to make sure it breaks at the given point.
52    unhandled_dep_points: Mutex<BTreeSet<ID>>,
53    pending_txn_node: Option<AppDagNode>,
54    import_rollback_has_journal: AtomicBool,
55    import_rollback: Mutex<Option<AppDagRollback>>,
56}
57
58#[derive(Debug)]
59pub(crate) struct AppDagRollback {
60    frontiers: Frontiers,
61    vv: VersionVector,
62    unparsed_vv: VersionVector,
63    shallow_since_frontiers: Frontiers,
64    shallow_root_frontiers_deps: Frontiers,
65    shallow_since_vv: ImVersionVector,
66    pending_txn_node: Option<AppDagNode>,
67    map_entries_before_mutation: BTreeMap<ID, Option<AppDagNode>>,
68    unhandled_dep_point_log: Vec<UnhandledDepPointLog>,
69}
70
71#[derive(Debug)]
72enum UnhandledDepPointLog {
73    Added(ID),
74    Removed(ID),
75}
76
77#[derive(Debug, Clone)]
78pub struct AppDagNode {
79    inner: Arc<AppDagNodeInner>,
80}
81
82impl Deref for AppDagNode {
83    type Target = AppDagNodeInner;
84
85    fn deref(&self) -> &Self::Target {
86        &self.inner
87    }
88}
89
90impl AppDagNode {
91    pub fn new(inner: AppDagNodeInner) -> Self {
92        Self {
93            inner: Arc::new(inner),
94        }
95    }
96}
97
98#[derive(Debug, Clone)]
99pub struct AppDagNodeInner {
100    pub(crate) peer: PeerID,
101    pub(crate) cnt: Counter,
102    pub(crate) lamport: Lamport,
103    pub(crate) deps: Frontiers,
104    pub(crate) vv: OnceCell<ImVersionVector>,
105    /// A flag indicating whether any other nodes from a different peer depend on this node.
106    /// The calculation of frontiers is based on the property that a node does not depend
107    /// on the middle of other nodes.
108    pub(crate) has_succ: bool,
109    pub(crate) len: usize,
110}
111
112impl From<AppDagNodeInner> for AppDagNode {
113    fn from(inner: AppDagNodeInner) -> Self {
114        AppDagNode {
115            inner: Arc::new(inner),
116        }
117    }
118}
119
120impl AppDag {
121    pub(super) fn new(change_store: ChangeStore) -> Self {
122        Self {
123            change_store,
124            map: Mutex::new(BTreeMap::new()),
125            frontiers: Frontiers::default(),
126            vv: VersionVector::default(),
127            unparsed_vv: Mutex::new(VersionVector::default()),
128            unhandled_dep_points: Mutex::new(BTreeSet::new()),
129            shallow_since_frontiers: Default::default(),
130            shallow_root_frontiers_deps: Default::default(),
131            shallow_since_vv: Default::default(),
132            pending_txn_node: None,
133            import_rollback_has_journal: AtomicBool::new(false),
134            import_rollback: Mutex::new(None),
135        }
136    }
137
138    pub fn frontiers(&self) -> &Frontiers {
139        &self.frontiers
140    }
141
142    pub fn vv(&self) -> &VersionVector {
143        &self.vv
144    }
145
146    pub fn shallow_since_vv(&self) -> &ImVersionVector {
147        &self.shallow_since_vv
148    }
149
150    pub fn shallow_since_frontiers(&self) -> &Frontiers {
151        &self.shallow_since_frontiers
152    }
153
154    pub(crate) fn begin_import_rollback(&mut self) {
155        let old_vv_is_empty = self.vv.is_empty();
156        let mut rollback = self.import_rollback.lock();
157        debug_assert!(rollback.is_none());
158        *rollback = Some(AppDagRollback {
159            frontiers: self.frontiers.clone(),
160            vv: self.vv.clone(),
161            unparsed_vv: self.unparsed_vv.lock().clone(),
162            shallow_since_frontiers: self.shallow_since_frontiers.clone(),
163            shallow_root_frontiers_deps: self.shallow_root_frontiers_deps.clone(),
164            shallow_since_vv: self.shallow_since_vv.clone(),
165            pending_txn_node: self.pending_txn_node.clone(),
166            map_entries_before_mutation: BTreeMap::new(),
167            unhandled_dep_point_log: Vec::new(),
168        });
169        self.import_rollback_has_journal
170            .store(!old_vv_is_empty, AtomicOrdering::Relaxed);
171    }
172
173    pub(crate) fn commit_import_rollback(&mut self) {
174        self.import_rollback_has_journal
175            .store(false, AtomicOrdering::Relaxed);
176        *self.import_rollback.lock() = None;
177    }
178
179    pub(crate) fn rollback_import(&mut self) {
180        self.import_rollback_has_journal
181            .store(false, AtomicOrdering::Relaxed);
182        let Some(checkpoint) = self.import_rollback.lock().take() else {
183            return;
184        };
185
186        let imported_spans = self.vv.sub_iter(&checkpoint.vv).collect::<Vec<_>>();
187        let mut map = self.map.lock();
188        for span in imported_spans {
189            let start = ID::new(span.peer, span.counter.start);
190            let end = ID::new(span.peer, span.counter.end);
191            let keys = map.range(start..end).map(|(id, _)| *id).collect::<Vec<_>>();
192            for key in keys {
193                map.remove(&key);
194            }
195        }
196
197        for (id, node) in checkpoint.map_entries_before_mutation {
198            if let Some(node) = node {
199                map.insert(id, node);
200            } else {
201                map.remove(&id);
202            }
203        }
204        drop(map);
205
206        self.frontiers = checkpoint.frontiers;
207        self.vv = checkpoint.vv.clone();
208        self.shallow_since_frontiers = checkpoint.shallow_since_frontiers;
209        self.shallow_root_frontiers_deps = checkpoint.shallow_root_frontiers_deps;
210        self.shallow_since_vv = checkpoint.shallow_since_vv;
211        *self.unparsed_vv.lock() = checkpoint.unparsed_vv;
212
213        let mut unhandled_dep_points = self.unhandled_dep_points.lock();
214        if checkpoint.vv.is_empty() {
215            unhandled_dep_points.clear();
216        }
217        for item in checkpoint.unhandled_dep_point_log.into_iter().rev() {
218            match item {
219                UnhandledDepPointLog::Added(id) => {
220                    unhandled_dep_points.remove(&id);
221                }
222                UnhandledDepPointLog::Removed(id) => {
223                    unhandled_dep_points.insert(id);
224                }
225            }
226        }
227        drop(unhandled_dep_points);
228
229        self.pending_txn_node = checkpoint.pending_txn_node;
230    }
231
232    fn record_map_entry_before_mutation(&self, map: &BTreeMap<ID, AppDagNode>, id: ID) {
233        if !self
234            .import_rollback_has_journal
235            .load(AtomicOrdering::Relaxed)
236        {
237            return;
238        }
239
240        self.record_map_entry_before_replacement(id, map.get(&id).cloned());
241    }
242
243    fn record_map_entry_before_replacement(&self, id: ID, old: Option<AppDagNode>) {
244        if !self
245            .import_rollback_has_journal
246            .load(AtomicOrdering::Relaxed)
247        {
248            return;
249        }
250
251        let mut rollback = self.import_rollback.lock();
252        let Some(rollback) = rollback.as_mut() else {
253            return;
254        };
255
256        let old_end = rollback.vv.get(&id.peer).copied().unwrap_or(0);
257        if id.counter >= old_end {
258            return;
259        }
260
261        rollback
262            .map_entries_before_mutation
263            .entry(id)
264            .or_insert(old);
265    }
266
267    fn record_unhandled_dep_point_added(&self, id: ID) {
268        if !self
269            .import_rollback_has_journal
270            .load(AtomicOrdering::Relaxed)
271        {
272            return;
273        }
274
275        if let Some(rollback) = self.import_rollback.lock().as_mut() {
276            rollback
277                .unhandled_dep_point_log
278                .push(UnhandledDepPointLog::Added(id));
279        }
280    }
281
282    fn record_unhandled_dep_point_removed(&self, id: ID) {
283        if !self
284            .import_rollback_has_journal
285            .load(AtomicOrdering::Relaxed)
286        {
287            return;
288        }
289
290        if let Some(rollback) = self.import_rollback.lock().as_mut() {
291            rollback
292                .unhandled_dep_point_log
293                .push(UnhandledDepPointLog::Removed(id));
294        }
295    }
296
297    pub fn is_empty(&self) -> bool {
298        self.vv.is_empty()
299    }
300
301    #[tracing::instrument(skip_all, name = "handle_new_change")]
302    pub(super) fn handle_new_change(
303        &mut self,
304        change: &Change,
305        from_local: bool,
306        rollback_old_vv: Option<&VersionVector>,
307    ) {
308        let len = change.content_len();
309        self.update_version_on_new_change(change, from_local);
310        #[cfg(debug_assertions)]
311        {
312            let unhandled_dep_points = self.unhandled_dep_points.lock();
313            let c = unhandled_dep_points
314                .range(change.id_start()..change.id_end())
315                .count();
316            assert!(c == 0);
317        }
318
319        let mut inserted = false;
320        if change.deps_on_self() {
321            let record_old_node_before_merge = rollback_old_vv
322                .and_then(|vv| vv.get(&change.id.peer).copied())
323                .filter(|old_end| *old_end == change.id.counter);
324            // We may not need to push new element to dag because it only depends on itself
325            inserted =
326                self.with_last_mut_of_peer(change.id.peer, record_old_node_before_merge, |last| {
327                    let (_, last) = last.unwrap();
328                    if last.has_succ {
329                        // Don't merge the node if there are other nodes depending on it
330                        return false;
331                    }
332
333                    assert_eq!(last.peer, change.id.peer, "peer id is not the same");
334                    assert_eq!(
335                        last.cnt + last.len as Counter,
336                        change.id.counter,
337                        "counter is not continuous"
338                    );
339                    assert_eq!(
340                        last.lamport + last.len as Lamport,
341                        change.lamport,
342                        "lamport is not continuous"
343                    );
344                    let last = Arc::make_mut(&mut last.inner);
345                    last.len = (change.id.counter - last.cnt) as usize + len;
346                    last.has_succ = false;
347                    true
348                });
349        }
350
351        if !inserted {
352            let node: AppDagNode = AppDagNodeInner {
353                vv: OnceCell::new(),
354                peer: change.id.peer,
355                cnt: change.id.counter,
356                lamport: change.lamport,
357                deps: change.deps.clone(),
358                has_succ: false,
359                len,
360            }
361            .into();
362
363            let mut map = self.map.lock();
364            map.insert(node.id_start(), node);
365            self.handle_deps_break_points(change.deps.iter(), change.id.peer, Some(&mut map));
366        }
367    }
368
369    fn try_with_node_mut<R>(
370        &self,
371        map: &mut BTreeMap<ID, AppDagNode>,
372        id: ID,
373        f: impl FnOnce(Option<(ID, &mut AppDagNode)>) -> R,
374    ) -> R {
375        let x = map.range_mut(..=id).next_back();
376        if let Some((node_id, node)) = x {
377            if node.contains_id(id) {
378                f(Some((*node_id, node)))
379            } else {
380                f(None)
381            }
382        } else {
383            f(None)
384        }
385    }
386
387    /// If the lamport of change can be calculated, return Ok, otherwise, Err
388    pub(crate) fn calc_unknown_lamport_change(&self, change: &mut Change) -> Result<(), ()> {
389        for dep in change.deps.iter() {
390            match self.get_lamport(&dep) {
391                Some(lamport) => {
392                    change.lamport = change.lamport.max(lamport + 1);
393                }
394                None => return Err(()),
395            }
396        }
397        Ok(())
398    }
399
400    pub(crate) fn find_deps_of_id(&self, id: ID) -> Frontiers {
401        let Some(node) = self.get(id) else {
402            return Frontiers::default();
403        };
404
405        let offset = id.counter - node.cnt;
406        if offset == 0 {
407            node.deps.clone()
408        } else {
409            ID::new(id.peer, node.cnt + offset - 1).into()
410        }
411    }
412
413    pub(crate) fn with_last_mut_of_peer<R>(
414        &mut self,
415        peer: PeerID,
416        record_if_before: Option<Counter>,
417        f: impl FnOnce(Option<(ID, &mut AppDagNode)>) -> R,
418    ) -> R {
419        self.lazy_load_last_of_peer(peer);
420        let mut binding = self.map.lock();
421        let last = binding
422            .range_mut(..=ID::new(peer, Counter::MAX))
423            .next_back()
424            .map(|(id, v)| {
425                if record_if_before.is_some_and(|old_end| id.counter < old_end) {
426                    self.record_map_entry_before_replacement(*id, Some(v.clone()));
427                }
428                (*id, v)
429            });
430        f(last)
431    }
432
433    fn update_version_on_new_change(&mut self, change: &Change, from_local: bool) {
434        if from_local {
435            assert!(self.pending_txn_node.take().is_some());
436            assert_eq!(
437                self.vv.get(&change.id.peer).copied().unwrap_or(0),
438                change.ctr_end()
439            );
440        } else {
441            let id_last = change.id_last();
442            self.frontiers
443                .update_frontiers_on_new_change(id_last, &change.deps);
444            assert!(self.pending_txn_node.is_none());
445            assert_eq!(
446                self.vv.get(&change.id.peer).copied().unwrap_or(0),
447                change.id.counter
448            );
449            self.vv.extend_to_include_last_id(id_last);
450        }
451    }
452
453    pub(super) fn lazy_load_last_of_peer(&mut self, peer: u64) {
454        let unparsed_vv = self.unparsed_vv.lock();
455        if !unparsed_vv.contains_key(&peer) || self.vv[&peer] >= unparsed_vv[&peer] {
456            return;
457        }
458
459        let Some(nodes) = self.change_store.get_last_dag_nodes_for_peer(peer) else {
460            panic!("unparsed vv don't match with change store. Peer:{peer} is not in change store")
461        };
462
463        self.lazy_load_nodes_internal(nodes, peer, None);
464    }
465
466    fn lazy_load_nodes_internal(
467        &self,
468        nodes: Vec<AppDagNode>,
469        peer: u64,
470        map_input: Option<&mut BTreeMap<ID, AppDagNode>>,
471    ) {
472        assert!(!nodes.is_empty());
473        let mut map_guard = None;
474        let map = map_input.unwrap_or_else(|| {
475            map_guard = Some(self.map.lock());
476            map_guard.as_mut().unwrap()
477        });
478        let new_dag_start_counter_for_the_peer = nodes[0].cnt;
479        let nodes_cnt_end = nodes.last().unwrap().ctr_end();
480        let mut unparsed_vv = self.unparsed_vv.lock();
481        let end_counter = unparsed_vv[&peer];
482        assert!(end_counter <= nodes_cnt_end);
483        let mut deps_on_others = Vec::new();
484        let mut break_point_set = self.unhandled_dep_points.lock();
485        for mut node in nodes {
486            if node.cnt >= end_counter {
487                // skip already parsed nodes
488                break;
489            }
490
491            if node.cnt + node.len as Counter > end_counter {
492                node = node.slice(0, (end_counter - node.cnt) as usize);
493                // This is unlikely to happen
494            }
495
496            for dep in node.deps.iter() {
497                if dep.peer != peer {
498                    deps_on_others.push(dep);
499                }
500            }
501
502            // PERF: we can try to merge the node with the previous node
503            let break_point_ends: Vec<_> = break_point_set
504                .range(node.id_start()..node.id_end())
505                .map(|id| (id.counter - node.cnt) as usize + 1)
506                .collect();
507            if break_point_ends.is_empty() {
508                self.record_map_entry_before_mutation(map, node.id_start());
509                map.insert(node.id_start(), node);
510            } else {
511                let mut slice_start = 0;
512                for slice_end in break_point_ends.iter().copied() {
513                    let mut slice_node = node.slice(slice_start, slice_end);
514                    let inner = Arc::make_mut(&mut slice_node.inner);
515                    inner.has_succ = true;
516                    self.record_map_entry_before_mutation(map, slice_node.id_start());
517                    map.insert(slice_node.id_start(), slice_node);
518                    slice_start = slice_end;
519                }
520
521                let last_break_point = break_point_ends.last().copied().unwrap();
522                if last_break_point != node.len {
523                    let slice_node = node.slice(last_break_point, node.len);
524                    self.record_map_entry_before_mutation(map, slice_node.id_start());
525                    map.insert(slice_node.id_start(), slice_node);
526                }
527
528                for break_point in break_point_ends.into_iter() {
529                    let id = node.id_start().inc(break_point as Counter - 1);
530                    if break_point_set.remove(&id) {
531                        self.record_unhandled_dep_point_removed(id);
532                    }
533                }
534            }
535        }
536
537        if new_dag_start_counter_for_the_peer == 0 {
538            unparsed_vv.remove(&peer);
539        } else {
540            unparsed_vv.insert(peer, new_dag_start_counter_for_the_peer);
541        }
542        drop(unparsed_vv);
543        drop(break_point_set);
544        self.handle_deps_break_points(deps_on_others.iter().copied(), peer, Some(map));
545    }
546
547    fn handle_deps_break_points(
548        &self,
549        ids: impl IntoIterator<Item = ID>,
550        skip_peer: PeerID,
551        map: Option<&mut BTreeMap<ID, AppDagNode>>,
552    ) {
553        let mut map_guard = None;
554        let map = map.unwrap_or_else(|| {
555            map_guard = Some(self.map.lock());
556            map_guard.as_mut().unwrap()
557        });
558        for id in ids {
559            if id.peer == skip_peer {
560                continue;
561            }
562
563            let mut handled = false;
564            let ans = self.try_with_node_mut(map, id, |target| {
565                // We don't need to break the dag node if it's not loaded yet
566                let (target_id, target) = target?;
567                if target.ctr_last() == id.counter {
568                    self.record_map_entry_before_replacement(target_id, Some(target.clone()));
569                    let target = Arc::make_mut(&mut target.inner);
570                    handled = true;
571                    target.has_succ = true;
572                    None
573                } else {
574                    // We need to split the target node into two part
575                    // so that we can ensure the new change depends on the
576                    // last id of a dag node.
577
578                    let new_node =
579                        target.slice(id.counter as usize - target.cnt as usize + 1, target.len);
580                    self.record_map_entry_before_replacement(target_id, Some(target.clone()));
581                    let target = Arc::make_mut(&mut target.inner);
582                    target.len -= new_node.len;
583                    Some(new_node)
584                }
585            });
586
587            if let Some(new_node) = ans {
588                self.record_map_entry_before_mutation(map, new_node.id_start());
589                map.insert(new_node.id_start(), new_node);
590            } else if !handled {
591                let mut unhandled_dep_points = self.unhandled_dep_points.lock();
592                if unhandled_dep_points.insert(id) {
593                    self.record_unhandled_dep_point_added(id);
594                }
595            }
596        }
597    }
598
599    fn ensure_lazy_load_node(&self, id: ID) {
600        if self.shallow_since_vv.includes_id(id) {
601            return;
602        }
603
604        loop {
605            // We need to load all the dag nodes that has the same peer and greater counter than the given `id`
606            // Because we only record the end counter of the unparsed version on `unparsed_vv`
607            let unparsed_end = {
608                let unparsed_vv = self.unparsed_vv.lock();
609                unparsed_vv.get(&id.peer).copied().unwrap_or(0)
610            };
611            if unparsed_end <= id.counter {
612                return;
613            }
614
615            let last_unparsed_id = ID::new(id.peer, unparsed_end - 1);
616            let Some(nodes) = self
617                .change_store
618                .get_dag_nodes_that_contains(last_unparsed_id)
619            else {
620                panic!("unparsed vv don't match with change store. Id:{id} is not in change store")
621            };
622
623            self.lazy_load_nodes_internal(nodes, id.peer, None);
624        }
625    }
626
627    pub fn total_parsed_dag_node(&self) -> usize {
628        self.map.lock().len()
629    }
630
631    pub(crate) fn set_version_by_fast_snapshot_import(&mut self, v: BatchDecodeInfo) {
632        assert!(self.vv.is_empty());
633        *self.unparsed_vv.lock() = v.vv.clone();
634        self.vv = v.vv;
635        self.frontiers = v.frontiers;
636        if let Some((vv, f)) = v.start_version {
637            if !f.is_empty() {
638                assert!(f.len() == 1);
639                let id = f.as_single().unwrap();
640                let node = self.get(id).unwrap();
641                assert!(node.cnt == id.counter);
642                self.shallow_root_frontiers_deps = node.deps.clone();
643            }
644            self.shallow_since_frontiers = f;
645            self.shallow_since_vv = ImVersionVector::from_vv(&vv);
646        }
647    }
648
649    /// This method is slow and should only be used for debugging and testing.
650    ///
651    /// It will check the following properties:
652    ///
653    /// 1. Counter is continuous
654    /// 2. A node always depends of the last ids of other nodes
655    /// 3. Lamport is correctly calculated
656    /// 4. VV for each node is correctly calculated
657    /// 5. Frontiers are correctly calculated
658    #[instrument(skip(self))]
659    pub fn check_dag_correctness(&self) {
660        {
661            // parse all nodes
662            let unparsed_vv = self.unparsed_vv.lock().clone();
663            for (peer, cnt) in unparsed_vv.iter() {
664                if *cnt == 0 {
665                    continue;
666                }
667
668                let mut end_cnt = *cnt;
669                let init_counter = self.shallow_since_vv.get(peer).copied().unwrap_or(0);
670                while end_cnt > init_counter {
671                    let cnt = end_cnt - 1;
672                    self.ensure_lazy_load_node(ID::new(*peer, cnt));
673                    end_cnt = self.unparsed_vv.lock().get(peer).copied().unwrap_or(0);
674                }
675            }
676
677            self.unparsed_vv.lock().clear();
678        }
679        {
680            // check property 1: Counter is continuous
681            let map = self.map.lock();
682            let mut last_end_id = ID::new(0, 0);
683            for (&id, node) in map.iter() {
684                let init_counter = self.shallow_since_vv.get(&id.peer).copied().unwrap_or(0);
685                if id.peer == last_end_id.peer {
686                    assert!(id.counter == last_end_id.counter);
687                } else {
688                    assert_eq!(id.counter, init_counter);
689                }
690
691                last_end_id = id.inc(node.len as Counter);
692            }
693        }
694        {
695            // check property 2: A node always depends of the last ids of other nodes
696            let map = self.map.lock();
697            check_always_dep_on_last_id(&map);
698        }
699        {
700            // check property 3: Lamport is correctly calculated
701            let map = self.map.lock();
702            'outer: for (_, node) in map.iter() {
703                let mut this_lamport = 0;
704                for dep in node.deps.iter() {
705                    if self.shallow_since_vv.includes_id(dep) {
706                        continue 'outer;
707                    }
708
709                    let (_, dep_node) = map.range(..=dep).next_back().unwrap();
710                    this_lamport = this_lamport.max(dep_node.lamport_end());
711                }
712
713                assert_eq!(this_lamport, node.lamport);
714            }
715        }
716        {
717            // check property 4: VV for each node is correctly calculated
718            let map = self.map.lock().clone();
719            'outer: for (_, node) in map.iter() {
720                let actual_vv = self.ensure_vv_for(node);
721                let mut expected_vv = ImVersionVector::default();
722                for dep in node.deps.iter() {
723                    if self.shallow_since_vv.includes_id(dep) {
724                        continue 'outer;
725                    }
726
727                    let (_, dep_node) = map.range(..=dep).next_back().unwrap();
728                    self.ensure_vv_for(dep_node);
729                    expected_vv.extend_to_include_vv(dep_node.vv.get().unwrap().iter());
730                    expected_vv.extend_to_include_last_id(dep);
731                }
732
733                assert_eq!(actual_vv, expected_vv);
734            }
735        }
736        {
737            // check property 5: Frontiers are correctly calculated
738            let mut maybe_frontiers = FxHashSet::default();
739            let map = self.map.lock();
740            for (_, node) in map.iter() {
741                maybe_frontiers.insert(node.id_last());
742            }
743
744            for (_, node) in map.iter() {
745                for dep in node.deps.iter() {
746                    maybe_frontiers.remove(&dep);
747                }
748            }
749
750            let frontiers = self.frontiers.iter().collect::<FxHashSet<_>>();
751            assert_eq!(maybe_frontiers, frontiers);
752        }
753    }
754
755    pub(crate) fn can_export_shallow_snapshot_on(&self, deps: &Frontiers) -> bool {
756        for id in deps.iter() {
757            if !self.vv.includes_id(id) {
758                return false;
759            }
760        }
761
762        if self.is_before_shallow_root(deps) {
763            return false;
764        }
765
766        true
767    }
768
769    pub(crate) fn is_before_shallow_root(&self, deps: &Frontiers) -> bool {
770        // trace!("Is on shallow history? deps={:?}", deps);
771        // trace!("self.shallow_since_vv {:?}", &self.shallow_since_vv);
772        // trace!("self.shallow_frontiers {:?}", &self.shallow_since_frontiers);
773
774        if self.shallow_since_vv.is_empty() {
775            return false;
776        }
777
778        if deps.is_empty() {
779            return true;
780        }
781
782        if deps.iter().any(|x| self.shallow_since_vv.includes_id(x)) {
783            return true;
784        }
785
786        if deps
787            .iter()
788            .any(|x| self.shallow_since_frontiers.contains(&x))
789        {
790            return deps != &self.shallow_since_frontiers;
791        }
792
793        false
794    }
795
796    pub(crate) fn import_deps_before_shallow_root(&self, deps: &Frontiers) -> bool {
797        if self.shallow_since_vv.is_empty() {
798            return false;
799        }
800
801        if deps.is_empty() {
802            return true;
803        }
804
805        // Deps equal to the root's own deps describe a change CONCURRENT with
806        // the root frontier op: its causal past is covered by the root state,
807        // so the boundary shortcut in `frontiers_to_vv` would resolve it to
808        // the shallow vv below. But the dep ids themselves are trimmed from
809        // the dag, so no lamport can be computed for such a change — it would
810        // be parked as pending and then panic in `calc_unknown_lamport_change`.
811        // Reject it like any other pre-root update.
812        if deps == &self.shallow_root_frontiers_deps {
813            return true;
814        }
815
816        let shallow_vv = VersionVector::from_im_vv(&self.shallow_since_vv);
817        if let Some(vv) = self.frontiers_to_vv(deps) {
818            return !vv.includes_vv(&shallow_vv);
819        }
820
821        // Import only needs to reject updates whose causal source is older than
822        // the shallow root. A dependency set that touches the retained boundary
823        // can still be a valid post-root update, even when the rest of the deps
824        // are imported later in the same batch.
825        if deps
826            .iter()
827            .any(|id| self.shallow_since_frontiers.contains(&id))
828        {
829            return false;
830        }
831
832        deps.iter().any(|id| self.shallow_since_vv.includes_id(id))
833    }
834
835    /// Travel the ancestors of the given id, and call the callback for each node
836    ///
837    /// It will travel the ancestors in the reverse order (from the greatest lamport to the smallest)
838    pub(crate) fn travel_ancestors(
839        &self,
840        id: ID,
841        f: &mut dyn FnMut(&AppDagNode) -> ControlFlow<()>,
842    ) {
843        struct PendingNode(AppDagNode);
844        impl PartialEq for PendingNode {
845            fn eq(&self, other: &Self) -> bool {
846                self.0.lamport_last() == other.0.lamport_last() && self.0.peer == other.0.peer
847            }
848        }
849        impl Eq for PendingNode {}
850        impl PartialOrd for PendingNode {
851            fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
852                Some(self.cmp(other))
853            }
854        }
855        impl Ord for PendingNode {
856            fn cmp(&self, other: &Self) -> Ordering {
857                self.0
858                    .lamport_last()
859                    .cmp(&other.0.lamport_last())
860                    .then_with(|| self.0.peer.cmp(&other.0.peer))
861            }
862        }
863
864        let mut visited = FxHashSet::default();
865        let mut pending: BinaryHeap<PendingNode> = BinaryHeap::new();
866        pending.push(PendingNode(self.get(id).unwrap()));
867        while let Some(PendingNode(node)) = pending.pop() {
868            if f(&node).is_break() {
869                break;
870            }
871
872            for dep in node.deps.iter() {
873                let Some(dep_node) = self.get(dep) else {
874                    continue;
875                };
876                if visited.contains(&dep_node.id_start()) {
877                    continue;
878                }
879
880                visited.insert(dep_node.id_start());
881                pending.push(PendingNode(dep_node));
882            }
883        }
884    }
885
886    pub(crate) fn update_version_on_new_local_op(
887        &mut self,
888        deps: &Frontiers,
889        start_id: ID,
890        start_lamport: Lamport,
891        len: usize,
892    ) {
893        let last_id = start_id.inc(len as Counter - 1);
894        // PERF: we can cache this last_id - this is a hot path
895        self.vv.set_last(last_id);
896        self.frontiers.update_frontiers_on_new_change(last_id, deps);
897        match &mut self.pending_txn_node {
898            Some(node) => {
899                assert!(
900                    node.peer == start_id.peer
901                        && node.cnt + node.len as Counter == start_id.counter
902                        && deps.len() == 1
903                        && deps.as_single().unwrap().peer == start_id.peer
904                );
905                let inner = Arc::make_mut(&mut node.inner);
906                inner.len += len;
907            }
908            None => {
909                let node = AppDagNode {
910                    inner: Arc::new(AppDagNodeInner {
911                        peer: start_id.peer,
912                        cnt: start_id.counter,
913                        lamport: start_lamport,
914                        deps: deps.clone(),
915                        vv: OnceCell::new(),
916                        has_succ: false,
917                        len,
918                    }),
919                };
920                self.pending_txn_node = Some(node);
921            }
922        }
923    }
924
925    pub(crate) fn latest_vv_contains_peer(&self, peer: PeerID) -> bool {
926        self.vv.contains_key(&peer) && *self.vv.get(&peer).unwrap() > 0
927    }
928}
929
930fn check_always_dep_on_last_id(map: &BTreeMap<ID, AppDagNode>) {
931    for (_, node) in map.iter() {
932        for dep in node.deps.iter() {
933            let Some((&dep_id, dep_node)) = map.range(..=dep).next_back() else {
934                // It's shallow
935                continue;
936            };
937            assert_eq!(dep_node.id_start(), dep_id);
938            if dep_node.contains_id(dep) {
939                assert_eq!(dep_node.id_last(), dep);
940            }
941        }
942    }
943}
944
945impl HasIndex for AppDagNode {
946    type Int = Counter;
947    fn get_start_index(&self) -> Self::Int {
948        self.cnt
949    }
950
951    fn get_end_index(&self) -> Self::Int {
952        self.cnt + self.len as Counter
953    }
954}
955
956impl Sliceable for AppDagNode {
957    fn slice(&self, from: usize, to: usize) -> Self {
958        AppDagNodeInner {
959            peer: self.peer,
960            cnt: self.cnt + from as Counter,
961            lamport: self.lamport + from as Lamport,
962            deps: if from > 0 {
963                Frontiers::from_id(self.id_start().inc(from as Counter - 1))
964            } else {
965                self.deps.clone()
966            },
967            vv: if let Some(vv) = self.vv.get() {
968                let mut new = vv.clone();
969                new.insert(self.peer, self.cnt + from as Counter);
970                OnceCell::with_value(new)
971            } else {
972                OnceCell::new()
973            },
974            has_succ: if to == self.len { self.has_succ } else { true },
975            len: to - from,
976        }
977        .into()
978    }
979}
980
981impl HasId for AppDagNode {
982    fn id_start(&self) -> ID {
983        ID {
984            peer: self.peer,
985            counter: self.cnt,
986        }
987    }
988}
989
990impl HasCounter for AppDagNode {
991    fn ctr_start(&self) -> Counter {
992        self.cnt
993    }
994}
995
996impl HasLength for AppDagNode {
997    fn atom_len(&self) -> usize {
998        self.len
999    }
1000
1001    fn content_len(&self) -> usize {
1002        self.len
1003    }
1004}
1005
1006impl Mergable for AppDagNode {
1007    fn is_mergable(&self, other: &Self, _conf: &()) -> bool
1008    where
1009        Self: Sized,
1010    {
1011        !self.has_succ
1012            && self.peer == other.peer
1013            && self.cnt + self.len as Counter == other.cnt
1014            && other.deps.len() == 1
1015            && self.lamport + self.len as Lamport == other.lamport
1016            && other.deps.as_single().unwrap().peer == self.peer
1017    }
1018
1019    fn merge(&mut self, other: &Self, _conf: &())
1020    where
1021        Self: Sized,
1022    {
1023        assert_eq!(
1024            other.deps.as_single().unwrap().counter,
1025            self.cnt + self.len as Counter - 1
1026        );
1027        let this = Arc::make_mut(&mut self.inner);
1028        this.len += other.len;
1029        this.has_succ = other.has_succ;
1030    }
1031}
1032
1033impl HasLamport for AppDagNode {
1034    fn lamport(&self) -> Lamport {
1035        self.lamport
1036    }
1037}
1038
1039impl DagNode for AppDagNode {
1040    fn deps(&self) -> &Frontiers {
1041        &self.deps
1042    }
1043}
1044
1045impl Dag for AppDag {
1046    type Node = AppDagNode;
1047
1048    fn frontier(&self) -> &Frontiers {
1049        &self.frontiers
1050    }
1051
1052    fn get(&self, id: ID) -> Option<Self::Node> {
1053        self.ensure_lazy_load_node(id);
1054        let binding = self.map.lock();
1055        if let Some(x) = binding.range(..=id).next_back() {
1056            if x.1.contains_id(id) {
1057                // PERF: do we need to optimize clone like this?
1058                // by adding another layer of Arc?
1059                return Some(x.1.clone());
1060            }
1061        }
1062
1063        if let Some(node) = &self.pending_txn_node {
1064            if node.peer == id.peer && node.cnt <= id.counter {
1065                assert!(node.cnt + node.len as Counter > id.counter);
1066                return Some(node.clone());
1067            }
1068        }
1069
1070        None
1071    }
1072
1073    fn vv(&self) -> &VersionVector {
1074        &self.vv
1075    }
1076
1077    fn contains(&self, id: ID) -> bool {
1078        self.vv.includes_id(id)
1079    }
1080}
1081
1082impl AppDag {
1083    // PERF: this may be painfully slow
1084    /// get the version vector for a certain op.
1085    /// It's the version when the op is applied
1086    pub fn get_vv(&self, id: ID) -> Option<ImVersionVector> {
1087        self.get(id).map(|x| {
1088            let mut vv = self.ensure_vv_for(&x);
1089            vv.insert(id.peer, id.counter + 1);
1090            vv
1091        })
1092    }
1093
1094    pub(crate) fn ensure_vv_for(&self, target_node: &AppDagNode) -> ImVersionVector {
1095        if target_node.vv.get().is_none() {
1096            // Iterative DFS. When the DAG contains a diamond, a dep can end
1097            // up on the stack multiple times (once for each ancestor that
1098            // re-queued the current node before its deps were ready). We
1099            // skip nodes whose vv has already been computed — the original
1100            // code eagerly called `OnceCell::set(..).unwrap()` on every pop
1101            // and panicked on the second visit (see loro-dev/loro#929).
1102            let mut stack: SmallVec<[AppDagNode; 4]> = smallvec::smallvec![target_node.clone()];
1103            while let Some(top_node) = stack.pop() {
1104                if top_node.vv.get().is_some() {
1105                    continue;
1106                }
1107
1108                let mut ans_vv = ImVersionVector::default();
1109                if top_node.deps == self.shallow_root_frontiers_deps {
1110                    for (&p, &c) in self.shallow_since_vv.iter() {
1111                        ans_vv.insert(p, c);
1112                    }
1113                } else {
1114                    let mut all_deps_processed = true;
1115                    for id in top_node.deps.iter() {
1116                        let Some(node) = self.get(id) else {
1117                            if self.shallow_since_vv.includes_id(id) {
1118                                continue;
1119                            }
1120
1121                            panic!("deps should be in the dag");
1122                        };
1123                        if node.vv.get().is_none() {
1124                            if all_deps_processed {
1125                                stack.push(top_node.clone());
1126                            }
1127                            all_deps_processed = false;
1128                            stack.push(node);
1129                            continue;
1130                        };
1131                    }
1132
1133                    if !all_deps_processed {
1134                        continue;
1135                    }
1136
1137                    for id in top_node.deps.iter() {
1138                        let Some(node) = self.get(id) else {
1139                            if self.shallow_since_vv.includes_id(id) {
1140                                ans_vv.extend_to_include_vv(self.shallow_since_vv.iter());
1141                                continue;
1142                            }
1143
1144                            panic!("deps should be in the dag");
1145                        };
1146                        let dep_vv = node.vv.get().unwrap();
1147                        if ans_vv.is_empty() {
1148                            ans_vv = dep_vv.clone();
1149                        } else {
1150                            ans_vv.extend_to_include_vv(dep_vv.iter());
1151                        }
1152
1153                        ans_vv.insert(node.peer, node.ctr_end());
1154                    }
1155                }
1156
1157                // Tolerate a racing set from the diamond case above: if
1158                // another path already initialized this cell, trust that
1159                // value (it was computed from the same DAG) and move on.
1160                let _ = top_node.vv.set(ans_vv);
1161            }
1162        }
1163
1164        target_node.vv.get().unwrap().clone()
1165    }
1166
1167    /// Compare the causal order of two versions.
1168    /// If None, two versions are concurrent to each other
1169    pub fn cmp_version(&self, a: ID, b: ID) -> Option<Ordering> {
1170        if a.peer == b.peer {
1171            return Some(a.counter.cmp(&b.counter));
1172        }
1173
1174        let a = self.get_vv(a).unwrap();
1175        let b = self.get_vv(b).unwrap();
1176        a.partial_cmp(&b)
1177    }
1178
1179    pub fn get_lamport(&self, id: &ID) -> Option<Lamport> {
1180        self.get(*id).and_then(|node| {
1181            assert!(id.counter >= node.cnt);
1182            if node.cnt + node.len as Counter > id.counter {
1183                Some(node.lamport + (id.counter - node.cnt) as Lamport)
1184            } else {
1185                None
1186            }
1187        })
1188    }
1189
1190    pub fn get_change_lamport_from_deps(&self, deps: &Frontiers) -> Option<Lamport> {
1191        let mut lamport = 0;
1192        for id in deps.iter() {
1193            let x = self.get_lamport(&id)?;
1194            lamport = lamport.max(x + 1);
1195        }
1196
1197        Some(lamport)
1198    }
1199
1200    /// Convert a frontiers to a version vector
1201    ///
1202    /// If the frontiers version is not found in the dag, return None
1203    pub fn frontiers_to_vv(&self, frontiers: &Frontiers) -> Option<VersionVector> {
1204        if frontiers == &self.shallow_root_frontiers_deps {
1205            let vv = VersionVector::from_im_vv(&self.shallow_since_vv);
1206            return Some(vv);
1207        }
1208
1209        let mut vv: VersionVector = Default::default();
1210        for id in frontiers.iter() {
1211            let x = self.get(id)?;
1212            let target_vv = self.ensure_vv_for(&x);
1213            vv.extend_to_include_vv(target_vv.iter());
1214            vv.extend_to_include_last_id(id);
1215        }
1216
1217        Some(vv)
1218    }
1219
1220    #[allow(unused)]
1221    pub(crate) fn frontiers_to_im_vv(&self, frontiers: &Frontiers) -> ImVersionVector {
1222        if frontiers.is_empty() {
1223            return Default::default();
1224        }
1225
1226        let mut iter = frontiers.iter();
1227        let mut vv = {
1228            let id = iter.next().unwrap();
1229            let Some(x) = self.get(id) else {
1230                unreachable!()
1231            };
1232            let mut vv = self.ensure_vv_for(&x);
1233            vv.extend_to_include_last_id(id);
1234            vv
1235        };
1236
1237        for id in iter {
1238            let Some(x) = self.get(id) else {
1239                unreachable!()
1240            };
1241            let x = self.ensure_vv_for(&x);
1242            vv.extend_to_include_vv(x.iter());
1243            vv.extend_to_include_last_id(id);
1244        }
1245
1246        vv
1247    }
1248
1249    pub fn im_vv_to_frontiers(&self, vv: &ImVersionVector) -> Frontiers {
1250        if vv.is_empty() {
1251            return Default::default();
1252        }
1253
1254        let this = vv;
1255        let last_ids: Frontiers = this
1256            .iter()
1257            .filter_map(|(client_id, cnt)| {
1258                if *cnt <= 0 {
1259                    return None;
1260                }
1261
1262                if self
1263                    .shallow_since_vv
1264                    .includes_id(ID::new(*client_id, *cnt - 1))
1265                {
1266                    return None;
1267                }
1268
1269                Some(ID::new(*client_id, cnt - 1))
1270            })
1271            .collect();
1272
1273        if last_ids.is_empty() {
1274            return self.shallow_since_frontiers.clone();
1275        }
1276
1277        shrink_frontiers(&last_ids, self).unwrap()
1278    }
1279
1280    pub fn vv_to_frontiers(&self, vv: &VersionVector) -> Frontiers {
1281        if vv.is_empty() {
1282            return Default::default();
1283        }
1284
1285        let this = vv;
1286        let last_ids: Frontiers = this
1287            .iter()
1288            .filter_map(|(client_id, cnt)| {
1289                if *cnt <= 0 {
1290                    return None;
1291                }
1292
1293                if self
1294                    .shallow_since_vv
1295                    .includes_id(ID::new(*client_id, *cnt - 1))
1296                {
1297                    return None;
1298                }
1299
1300                Some(ID::new(*client_id, cnt - 1))
1301            })
1302            .collect();
1303
1304        if last_ids.is_empty() {
1305            return self.shallow_since_frontiers.clone();
1306        }
1307
1308        shrink_frontiers(&last_ids, self).unwrap()
1309    }
1310
1311    pub(crate) fn frontiers_to_next_lamport(&self, frontiers: &Frontiers) -> Lamport {
1312        if frontiers.is_empty() {
1313            return 0;
1314        }
1315
1316        let mut iter = frontiers.iter();
1317        let mut lamport = {
1318            let id = iter.next().unwrap();
1319            let Some(x) = self.get(id) else {
1320                unreachable!()
1321            };
1322            assert!(id.counter >= x.cnt);
1323            (id.counter - x.cnt) as Lamport + x.lamport + 1
1324        };
1325
1326        for id in iter {
1327            let Some(x) = self.get(id) else {
1328                unreachable!()
1329            };
1330            assert!(id.counter >= x.cnt);
1331            lamport = lamport.max((id.counter - x.cnt) as Lamport + x.lamport + 1);
1332        }
1333
1334        lamport
1335    }
1336
1337    pub fn get_frontiers(&self) -> &Frontiers {
1338        &self.frontiers
1339    }
1340
1341    /// - Ordering::Less means self is less than target or parallel
1342    /// - Ordering::Equal means versions equal
1343    /// - Ordering::Greater means self's version is greater than target
1344    pub fn cmp_with_frontiers(&self, other: &Frontiers) -> Ordering {
1345        if &self.frontiers == other {
1346            Ordering::Equal
1347        } else if other.iter().all(|id| self.vv.includes_id(id)) {
1348            Ordering::Greater
1349        } else {
1350            Ordering::Less
1351        }
1352    }
1353
1354    // PERF
1355    /// Compare two [Frontiers] causally.
1356    ///
1357    /// If one of the [Frontiers] are not included, it will return [FrontiersNotIncluded].
1358    pub fn cmp_frontiers(
1359        &self,
1360        a: &Frontiers,
1361        b: &Frontiers,
1362    ) -> Result<Option<Ordering>, FrontiersNotIncluded> {
1363        let a = self.frontiers_to_vv(a).ok_or(FrontiersNotIncluded)?;
1364        let b = self.frontiers_to_vv(b).ok_or(FrontiersNotIncluded)?;
1365        Ok(a.partial_cmp(&b))
1366    }
1367}
1368
1369#[derive(Debug, PartialEq, Eq)]
1370pub struct FrontiersNotIncluded;
1371impl Display for FrontiersNotIncluded {
1372    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1373        f.write_str("The given Frontiers are not included by the doc")
1374    }
1375}
1376
1377#[cfg(test)]
1378mod ensure_vv_for_tests {
1379    use super::*;
1380    use crate::arena::SharedArena;
1381    use std::sync::atomic::AtomicI64;
1382
1383    fn make_dag_node(peer: PeerID, cnt: Counter, len: usize, deps: Frontiers) -> AppDagNode {
1384        AppDagNodeInner {
1385            vv: OnceCell::new(),
1386            peer,
1387            cnt,
1388            lamport: cnt as Lamport,
1389            deps,
1390            has_succ: false,
1391            len,
1392        }
1393        .into()
1394    }
1395
1396    fn make_shallow_dag_for_import_deps() -> AppDag {
1397        let change_store = ChangeStore::new_mem(&SharedArena::new(), Arc::new(AtomicI64::new(0)));
1398        let mut dag = AppDag::new(change_store);
1399        let root_deps = Frontiers::from_id(ID::new(1, 1));
1400        let boundary = make_dag_node(1, 2, 1, root_deps.clone());
1401
1402        {
1403            let mut map = dag.map.lock();
1404            map.insert(boundary.id_start(), boundary);
1405        }
1406
1407        dag.shallow_since_vv.insert(1, 2);
1408        dag.shallow_since_frontiers = Frontiers::from_id(ID::new(1, 2));
1409        dag.shallow_root_frontiers_deps = root_deps;
1410        dag
1411    }
1412
1413    /// Regression for loro-dev/loro#929: when computing the vv for a node
1414    /// whose DAG fan-in contains a shared ancestor reached through multiple
1415    /// paths, the iterative DFS used to push the shared ancestor onto the
1416    /// stack twice. The second visit would then call `OnceCell::set(..)
1417    /// .unwrap()` on an already-initialized cell and panic with
1418    /// "called `Result::unwrap()` on an `Err` value: ImVersionVector(..)".
1419    ///
1420    /// Topology:
1421    ///    x (peer 1, counter 0)
1422    ///    |
1423    ///    y (peer 2, counter 0, deps = [x])
1424    ///    |
1425    ///    z (peer 3, counter 0, deps = [x, y])
1426    ///
1427    /// When we call `ensure_vv_for(z)`, z pushes x and y. Processing y then
1428    /// re-pushes x (still uninitialized), so the stack ends up with two
1429    /// copies of x; the second pop must be a no-op after the fix.
1430    #[test]
1431    fn diamond_dep_ensure_vv_for_does_not_double_set() {
1432        let change_store = ChangeStore::new_mem(&SharedArena::new(), Arc::new(AtomicI64::new(0)));
1433        let dag = AppDag::new(change_store);
1434
1435        let x = make_dag_node(1, 0, 1, Frontiers::default());
1436        let y = make_dag_node(2, 0, 1, Frontiers::from_id(ID::new(1, 0)));
1437        let mut z_deps = Frontiers::default();
1438        z_deps.push(ID::new(1, 0));
1439        z_deps.push(ID::new(2, 0));
1440        let z = make_dag_node(3, 0, 1, z_deps);
1441
1442        {
1443            let mut map = dag.map.lock();
1444            map.insert(x.id_start(), x);
1445            map.insert(y.id_start(), y);
1446            map.insert(z.id_start(), z.clone());
1447        }
1448
1449        // Historically this panicked at the inner `vv.set(...).unwrap()`.
1450        // `ensure_vv_for` returns the vv *at* the node: it covers every
1451        // peer in the causal past, but not the node's own peer counter.
1452        let vv = dag.ensure_vv_for(&z);
1453        assert_eq!(vv.get(&1).copied(), Some(1));
1454        assert_eq!(vv.get(&2).copied(), Some(1));
1455        assert!(vv.get(&3).is_none());
1456    }
1457
1458    #[test]
1459    fn import_deps_before_shallow_root_rejects_trimmed_history_dep() {
1460        let dag = make_shallow_dag_for_import_deps();
1461        let deps = Frontiers::from_id(ID::new(1, 0));
1462
1463        assert!(dag.frontiers_to_vv(&deps).is_none());
1464        assert!(dag.import_deps_before_shallow_root(&deps));
1465    }
1466
1467    /// Deps exactly equal to the root's own deps describe a change concurrent
1468    /// with the root frontier op. The boundary shortcut in `frontiers_to_vv`
1469    /// resolves them to the shallow vv, but the dep ids are trimmed from the
1470    /// dag, so the change could never get a lamport — reject it.
1471    #[test]
1472    fn import_deps_before_shallow_root_rejects_deps_equal_to_root_deps() {
1473        let dag = make_shallow_dag_for_import_deps();
1474        let deps = Frontiers::from_id(ID::new(1, 1));
1475
1476        // The boundary shortcut resolves these deps to the shallow vv...
1477        assert!(dag.frontiers_to_vv(&deps).is_some());
1478        // ...but the import check must still reject them.
1479        assert!(dag.import_deps_before_shallow_root(&deps));
1480    }
1481
1482    #[test]
1483    fn import_deps_before_shallow_root_allows_boundary_with_missing_peer() {
1484        let dag = make_shallow_dag_for_import_deps();
1485        let mut deps = Frontiers::default();
1486        deps.push(ID::new(1, 2));
1487        deps.push(ID::new(2, 0));
1488
1489        assert!(dag.frontiers_to_vv(&deps).is_none());
1490        assert!(!dag.import_deps_before_shallow_root(&deps));
1491    }
1492
1493    #[test]
1494    fn import_deps_before_shallow_root_allows_missing_non_trimmed_dep() {
1495        let dag = make_shallow_dag_for_import_deps();
1496        let deps = Frontiers::from_id(ID::new(2, 0));
1497
1498        assert!(dag.frontiers_to_vv(&deps).is_none());
1499        assert!(!dag.import_deps_before_shallow_root(&deps));
1500    }
1501}