Skip to main content

aranya_runtime/storage/
mod.rs

1//! Interfaces for graph storage.
2//!
3//! The [`StorageProvider`] and [`Storage`] interfaces enable high-level
4//! actions on the graph. Traversing the graph is made simpler by splitting
5//! its [`Command`]s into [`Segment`]s. Updating the graph is possible using
6//! [`Perspective`]s, which represent a slice of state.
7
8use alloc::{boxed::Box, string::String, vec::Vec};
9use core::{borrow::Borrow, fmt, ops::Deref};
10
11use buggy::{Bug, BugExt as _};
12use rend::u64_le;
13
14use crate::{Address, CmdId, Command, CommandExt as _, PolicyId, Prior};
15
16pub mod head_set;
17pub use head_set::HeadSet;
18
19pub mod linear;
20
21#[cfg(any(feature = "libc", feature = "testing"))]
22mod spill;
23#[cfg(feature = "libc")]
24pub use spill::LibcSpill;
25#[cfg(feature = "testing")]
26pub use spill::MemSpill;
27
28/// Byte-addressable overflow storage for braid and convergence data.
29///
30/// Implemented by [`LibcSpill`] (file-backed) and [`MemSpill`] (in-memory);
31/// each backend has its own constructor signature (paths, etc.), matching
32/// how [`IoManager`](linear::io::IoManager) backends are constructed.
33/// Callers build a spill and pass it in.
34pub trait Spill {
35    /// Write `data` at the given byte offset.
36    fn write_at(&mut self, offset: usize, data: &[u8]) -> Result<(), StorageError>;
37    /// Read exactly `data.len()` bytes starting at the given byte offset.
38    fn read_at(&mut self, offset: usize, data: &mut [u8]) -> Result<(), StorageError>;
39}
40
41/// Default capacity for the traversal queue.
42///
43/// This should be large enough to hold the maximum expected "active frontier"
44/// during backward traversal, which is bounded by peer count.
45pub const QUEUE_CAPACITY: usize = 512;
46
47/// Type for the queue used in traversal operations.
48///
49/// Locations with the highest `max_cut` are processed first. This bounds the
50/// queue size to the graph width at any given `max_cut` level, rather than
51/// accumulating entries across many levels as a FIFO would.
52///
53/// Entries are partitioned into uncovered (`entries[0..partition]`) and
54/// covered (`entries[partition..len]`). See [`push_covered`](Self::push_covered)
55/// for the rules governing partition transitions.
56#[derive(Debug, Default)]
57pub struct TraversalQueue {
58    entries: Vec<Location>,
59    /// Index separating uncovered (below) from covered (at and above).
60    partition: usize,
61}
62
63impl TraversalQueue {
64    /// Create an empty traversal queue.
65    pub const fn new() -> Self {
66        Self {
67            entries: Vec::new(),
68            partition: 0,
69        }
70    }
71
72    /// Clear the traversal queue.
73    pub fn clear(&mut self) {
74        self.entries.clear();
75        self.partition = 0;
76    }
77
78    /// Returns true if no entries.
79    pub fn is_empty(&self) -> bool {
80        self.entries.is_empty()
81    }
82
83    /// Enqueues a location as uncovered.
84    ///
85    /// If an entry with the same segment exists, its max cut will be updated
86    /// to the max of the two.
87    pub fn push(&mut self, loc: Location) -> Result<(), StorageError> {
88        self.push_covered(loc, false)
89    }
90
91    /// Enqueues a location with the given covered flag.
92    ///
93    /// If an entry with the same segment already exists, max cut is updated
94    /// to the max. When a higher max_cut changes the head, the new push's
95    /// covered status is adopted (old coverage was below the new head).
96    /// At the same max_cut, covered flags are OR'd. Lower max_cut is ignored.
97    pub fn push_covered(&mut self, loc: Location, covered: bool) -> Result<(), StorageError> {
98        if let Some(i) = self.entries.iter().position(|x| x.same_segment(loc)) {
99            let was_covered = i >= self.partition;
100            let new_covered = if loc.max_cut > self.entries[i].max_cut {
101                self.entries[i].max_cut = loc.max_cut;
102                covered
103            } else if loc.max_cut == self.entries[i].max_cut {
104                was_covered || covered
105            } else {
106                return Ok(());
107            };
108            if !was_covered && new_covered {
109                self.partition = self
110                    .partition
111                    .checked_sub(1)
112                    .assume("partition must be >= 1 when uncovered entry exists")?;
113                self.entries.swap(i, self.partition);
114            } else if was_covered && !new_covered {
115                self.entries.swap(i, self.partition);
116                self.partition = self
117                    .partition
118                    .checked_add(1)
119                    .assume("partition must not overflow")?;
120            }
121            return Ok(());
122        }
123        self.entries.push(loc);
124        if !covered {
125            let last = self
126                .entries
127                .len()
128                .checked_sub(1)
129                .assume("just pushed, len must be >= 1")?;
130            self.entries.swap(self.partition, last);
131            self.partition = self
132                .partition
133                .checked_add(1)
134                .assume("partition must not overflow")?;
135        }
136        Ok(())
137    }
138
139    /// Enqueues a location without deduplication.
140    ///
141    /// Unlike [`Self::push`], each call adds a new entry even if the location
142    /// is already present. Used by the convergence pre-pass where
143    /// duplicate tracking is needed.
144    pub fn push_duplicate(&mut self, loc: Location) -> Result<(), StorageError> {
145        self.entries.push(loc);
146        // All duplicate entries are uncovered.
147        let last = self
148            .entries
149            .len()
150            .checked_sub(1)
151            .assume("just pushed, len must be >= 1")?;
152        self.entries.swap(self.partition, last);
153        self.partition = self
154            .partition
155            .checked_add(1)
156            .assume("partition must not overflow")?;
157        Ok(())
158    }
159
160    /// Pop the location with the highest max cut, discarding the covered flag.
161    pub fn pop(&mut self) -> Result<Option<Location>, StorageError> {
162        Ok(self.pop_covered()?.map(|(loc, _)| loc))
163    }
164
165    /// Pop the location with the highest max cut, including its covered flag.
166    pub fn pop_covered(&mut self) -> Result<Option<(Location, bool)>, StorageError> {
167        let Some((i, _)) = self.entries.iter().enumerate().max_by_key(|&(_, loc)| *loc) else {
168            return Ok(None);
169        };
170        if i < self.partition {
171            Ok(Some((self.remove_uncovered(i)?, false)))
172        } else {
173            // Removing from covered region: swap_remove is fine.
174            let loc = self.entries.swap_remove(i);
175            Ok(Some((loc, true)))
176        }
177    }
178
179    /// Remove an entry from the uncovered region at index `i`,
180    /// maintaining the partition invariant.
181    fn remove_uncovered(&mut self, i: usize) -> Result<Location, StorageError> {
182        self.partition = self
183            .partition
184            .checked_sub(1)
185            .assume("partition must be >= 1 when uncovered entry exists")?;
186        self.entries.swap(i, self.partition);
187        Ok(self.entries.swap_remove(self.partition))
188    }
189
190    /// Returns the entry with the highest `max_cut` without removing it.
191    pub fn peek(&self) -> Option<&Location> {
192        self.entries.iter().max_by_key(|loc| *loc)
193    }
194
195    /// Pop the entry with the highest `max_cut`, removing all entries
196    /// at that exact location. Returns `(location, count)`.
197    ///
198    /// Used by the convergence pre-pass. Entries are matched by full
199    /// `Location` equality (segment + max_cut), not just max_cut.
200    pub fn pop_duplicates(&mut self) -> Result<Option<(Location, usize)>, StorageError> {
201        let Some(location) = self.entries.iter().max_by_key(|loc| *loc).copied() else {
202            return Ok(None);
203        };
204
205        // Remove all entries matching this location.
206        // Count them as we go. Iterate backward to avoid index shifts.
207        let mut count: usize = 0;
208        let mut j = self.entries.len();
209        while j > 0 {
210            j = j.checked_sub(1).assume("j > 0 checked in loop condition")?;
211            if self.entries[j] == location {
212                count = count
213                    .checked_add(1)
214                    .assume("count bounded by `entries.len()`")?;
215                if j < self.partition {
216                    self.partition = self
217                        .partition
218                        .checked_sub(1)
219                        .assume("partition >= 1 when uncovered entry at j < partition")?;
220                    self.entries.swap(j, self.partition);
221                    self.entries.swap_remove(self.partition);
222                } else {
223                    self.entries.swap_remove(j);
224                }
225            }
226        }
227
228        Ok(Some((location, count)))
229    }
230
231    /// Returns true if all entries are covered (uncovered partition is empty).
232    pub fn all_covered(&self) -> bool {
233        self.partition == 0
234    }
235
236    /// Remove all entries with `max_cut > threshold` from the queue.
237    ///
238    /// Uncovered entries are passed to `f`. Covered entries are discarded
239    /// (the peer already has them).
240    pub fn drain_above(
241        &mut self,
242        threshold: MaxCut,
243        mut f: impl FnMut(Location),
244    ) -> Result<(), StorageError> {
245        // Drain from uncovered region.
246        let mut i = 0;
247        while i < self.partition {
248            if self.entries[i].max_cut > threshold {
249                f(self.remove_uncovered(i)?);
250            } else {
251                i = i.checked_add(1).assume("index must not overflow")?;
252            }
253        }
254        // Discard covered entries above the threshold — the peer
255        // already has these, so they don't belong in the result.
256        let mut i = self.partition;
257        while i < self.entries.len() {
258            if self.entries[i].max_cut > threshold {
259                self.entries.swap_remove(i);
260            } else {
261                i = i.checked_add(1).assume("index must not overflow")?;
262            }
263        }
264        Ok(())
265    }
266
267    /// Mark a segment as covered up to `coverage_mc`. If the segment
268    /// exists in the queue:
269    /// - If `coverage_mc >= longest_mc`: the segment is fully covered.
270    /// - If `coverage_mc >= entry.max_cut`: the entry is updated to
271    ///   `coverage_mc + 1` (still uncovered — the peer needs the rest).
272    /// - If `coverage_mc < entry.max_cut`: no-op (already sending from
273    ///   above the coverage point).
274    pub fn cover_up_to(
275        &mut self,
276        segment: SegmentIndex,
277        coverage_mc: MaxCut,
278        longest_mc: MaxCut,
279    ) -> Result<(), StorageError> {
280        let Some(i) = self.entries.iter().position(|x| x.segment == segment) else {
281            return Ok(());
282        };
283        let was_covered = i >= self.partition;
284        if was_covered {
285            return Ok(());
286        }
287        if coverage_mc >= longest_mc {
288            // Fully covered — move to covered partition.
289            self.partition = self
290                .partition
291                .checked_sub(1)
292                .assume("partition must be >= 1 when uncovered entry exists")?;
293            self.entries.swap(i, self.partition);
294        } else if coverage_mc >= self.entries[i].max_cut {
295            // Partially covered — advance start past the covered portion.
296            self.entries[i].max_cut = coverage_mc
297                .checked_add(1)
298                .assume("coverage_mc + 1 must not overflow")?;
299        }
300        // else: coverage is below our start, nothing to do.
301        Ok(())
302    }
303
304    /// Drain all entries. Uncovered entries are passed to `f`.
305    /// Covered entries are discarded. O(n) single pass.
306    pub fn drain_all(&mut self, mut f: impl FnMut(Location)) {
307        for i in 0..self.partition {
308            f(self.entries[i]);
309        }
310        self.entries.clear();
311        self.partition = 0;
312    }
313}
314
315/// A queue buffer for a single graph traversal operation.
316///
317/// Access via [`get()`](Self::get), which clears the buffer automatically.
318pub struct TraversalBuffer {
319    queue: TraversalQueue,
320}
321
322impl TraversalBuffer {
323    pub const fn new() -> Self {
324        Self {
325            queue: TraversalQueue::new(),
326        }
327    }
328
329    /// Returns a cleared queue ready for use.
330    pub fn get(&mut self) -> &mut TraversalQueue {
331        self.queue.clear();
332        &mut self.queue
333    }
334}
335
336impl Default for TraversalBuffer {
337    fn default() -> Self {
338        Self::new()
339    }
340}
341
342/// Reusable buffers for graph traversal operations.
343///
344/// Contains two independent queue buffers so that an outer traversal
345/// (e.g. `find_needed_segments`) can maintain state in one buffer while
346/// calling leaf operations (e.g. `is_ancestor`) that use the other.
347pub struct TraversalBuffers {
348    pub primary: TraversalBuffer,
349    pub secondary: TraversalBuffer,
350}
351
352impl TraversalBuffers {
353    pub const fn new() -> Self {
354        Self {
355            primary: TraversalBuffer::new(),
356            secondary: TraversalBuffer::new(),
357        }
358    }
359}
360
361impl Default for TraversalBuffers {
362    fn default() -> Self {
363        Self::new()
364    }
365}
366
367#[cfg(feature = "low-mem-usage")]
368pub const MAX_COMMAND_LENGTH: usize = 400;
369#[cfg(not(feature = "low-mem-usage"))]
370pub const MAX_COMMAND_LENGTH: usize = 2048;
371
372aranya_crypto::custom_id! {
373    /// The ID of the graph, taken from initialization.
374    pub struct GraphId;
375}
376
377#[derive(
378    Copy,
379    Clone,
380    Debug,
381    Hash,
382    PartialEq,
383    Eq,
384    PartialOrd,
385    Ord,
386    serde::Serialize,
387    serde::Deserialize,
388    rkyv::Archive,
389    rkyv::Serialize,
390    rkyv::Deserialize,
391    rkyv::Portable,
392    rkyv::bytecheck::CheckBytes,
393    zerocopy::IntoBytes,
394    zerocopy::FromBytes,
395    zerocopy::Immutable,
396    zerocopy::KnownLayout,
397)]
398#[rkyv(as = Self)]
399#[bytecheck(crate = rkyv::bytecheck)]
400#[serde(transparent)]
401#[repr(transparent)]
402pub struct SegmentIndex(#[serde(with = "crate::util::u64_le_serde")] u64_le);
403
404impl fmt::Display for SegmentIndex {
405    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
406        fmt::Display::fmt(&self.0, f)
407    }
408}
409
410impl SegmentIndex {
411    pub const fn new(val: u64) -> Self {
412        Self(u64_le::from_native(val))
413    }
414
415    pub const fn get(self) -> u64 {
416        self.0.to_native()
417    }
418}
419
420#[derive(
421    Copy,
422    Clone,
423    Debug,
424    Hash,
425    PartialEq,
426    Eq,
427    PartialOrd,
428    Ord,
429    serde::Serialize,
430    serde::Deserialize,
431    rkyv::Archive,
432    rkyv::Serialize,
433    rkyv::Deserialize,
434    rkyv::Portable,
435    rkyv::bytecheck::CheckBytes,
436    zerocopy::IntoBytes,
437    zerocopy::FromBytes,
438    zerocopy::Immutable,
439    zerocopy::KnownLayout,
440)]
441#[rkyv(as = Self)]
442#[bytecheck(crate = rkyv::bytecheck)]
443#[serde(transparent)]
444#[repr(transparent)]
445pub struct MaxCut(#[serde(with = "crate::util::u64_le_serde")] u64_le);
446
447impl fmt::Display for MaxCut {
448    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
449        fmt::Display::fmt(&self.0, f)
450    }
451}
452
453impl MaxCut {
454    pub const fn new(val: u64) -> Self {
455        Self(u64_le::from_native(val))
456    }
457
458    pub const fn get(self) -> u64 {
459        self.0.to_native()
460    }
461
462    /// Adds an amount to the max cut, returning `None` on overflow.
463    #[must_use]
464    pub fn checked_add(self, other: u64) -> Option<Self> {
465        self.get().checked_add(other).map(Self::new)
466    }
467
468    /// Gets a max cut one lower than this, returning `None` on overflow.
469    #[must_use]
470    pub fn decremented(self) -> Option<Self> {
471        self.get().checked_sub(1).map(Self::new)
472    }
473
474    /// Gets the distance between two max cuts, returning `None` on overflow.
475    #[must_use]
476    pub fn distance_from(self, other: Self) -> Option<u64> {
477        self.get().checked_sub(other.get())
478    }
479}
480
481#[derive(
482    Copy,
483    Clone,
484    Debug,
485    Hash,
486    PartialEq,
487    Eq,
488    PartialOrd,
489    Ord,
490    serde::Serialize,
491    serde::Deserialize,
492    rkyv::Archive,
493    rkyv::Serialize,
494    rkyv::Deserialize,
495    rkyv::Portable,
496    rkyv::bytecheck::CheckBytes,
497    zerocopy::IntoBytes,
498    zerocopy::FromBytes,
499    zerocopy::Immutable,
500    zerocopy::KnownLayout,
501)]
502#[rkyv(as = Self)]
503#[bytecheck(crate = rkyv::bytecheck)]
504#[repr(C)]
505pub struct Location {
506    pub max_cut: MaxCut,
507    pub segment: SegmentIndex,
508}
509
510impl From<(SegmentIndex, MaxCut)> for Location {
511    fn from((segment, max_cut): (SegmentIndex, MaxCut)) -> Self {
512        Self::new(segment, max_cut)
513    }
514}
515
516impl AsRef<Self> for Location {
517    fn as_ref(&self) -> &Self {
518        self
519    }
520}
521
522impl Location {
523    pub fn new(segment: SegmentIndex, max_cut: MaxCut) -> Self {
524        Self { max_cut, segment }
525    }
526
527    /// Returns true if other location is in the same segment.
528    pub fn same_segment(self, other: Self) -> bool {
529        self.segment == other.segment
530    }
531}
532
533impl fmt::Display for Location {
534    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
535        write!(f, "{}:{}", self.segment, self.max_cut)
536    }
537}
538
539#[derive(
540    Copy, Clone, Debug, Hash, PartialEq, Eq, PartialOrd, Ord, serde::Serialize, serde::Deserialize,
541)]
542pub struct LocatedAddress {
543    pub id: CmdId,
544    pub segment: SegmentIndex,
545    pub max_cut: MaxCut,
546}
547
548impl LocatedAddress {
549    pub fn address(self) -> Address {
550        Address {
551            id: self.id,
552            max_cut: self.max_cut,
553        }
554    }
555
556    pub fn location(self) -> Location {
557        Location {
558            segment: self.segment,
559            max_cut: self.max_cut,
560        }
561    }
562}
563
564/// Backend-assigned stamp for the committed head set, changed by every
565/// [`Storage::commit_heads`]. A captured value compared against the current
566/// one detects intervening commits without cloning or comparing head sets.
567/// Linear storage uses the file offset of the appended head-set record.
568#[derive(Clone, Copy, Debug, PartialEq, Eq)]
569pub struct HeadSetOffset(u64);
570
571impl HeadSetOffset {
572    /// Wraps a backend-provided raw value.
573    pub fn new(offset: u64) -> Self {
574        Self(offset)
575    }
576}
577
578/// An error returned by [`Storage`] or [`StorageProvider`].
579#[derive(Debug, thiserror::Error)]
580#[cfg_attr(test, derive(PartialEq, Eq))]
581#[non_exhaustive]
582pub enum StorageError {
583    #[error("storage already exists")]
584    StorageExists,
585    #[error("no such storage")]
586    NoSuchStorage,
587    #[error("storage created but not initialized by a first commit")]
588    NotInitialized,
589    #[error("segment index {} is out of bounds", .0.segment)]
590    SegmentOutOfBounds(Location),
591    #[error("max cut {} is out of bounds in segment {}", .0.max_cut, .0.segment)]
592    CommandOutOfBounds(Location),
593    #[error("IO error")]
594    IoError,
595    #[error("policy mismatch")]
596    PolicyMismatch,
597    #[error("cannot write an empty perspective")]
598    EmptyPerspective,
599    #[error("traversal queue overflow (capacity {0})")]
600    TraversalQueueOverflow(usize),
601    #[error("strand heap overflow (capacity {0})")]
602    StrandHeapOverflow(usize),
603    #[error("convergence root index overflow (capacity {0})")]
604    ConvergenceRootOverflow(usize),
605    #[error("command's parents do not match the perspective head")]
606    PerspectiveHeadMismatch,
607    #[error("graph has multiple heads ({0}); no single head to report")]
608    MultipleHeads(usize),
609    #[error(transparent)]
610    Bug(#[from] Bug),
611}
612
613/// Handle to storage implementations used by the runtime.
614pub trait StorageProvider {
615    type Perspective: Perspective + Revertable;
616    type Segment: Segment;
617    type Storage: Storage<
618            Segment = Self::Segment,
619            Perspective = Self::Perspective,
620            FactIndex = <Self::Segment as Segment>::FactIndex,
621        >;
622
623    /// Create an unrooted perspective, intended for creating a new graph.
624    ///
625    /// # Arguments
626    ///
627    /// * `policy_id` - The policy to associate with the graph.
628    fn new_perspective(&mut self, policy_id: PolicyId) -> Self::Perspective;
629
630    /// Create a new graph.
631    ///
632    /// # Arguments
633    ///
634    /// * `graph` - ID of the graph, taken from the initialization command.
635    /// * `init` - Contains the data necessary to initialize the new graph.
636    fn new_storage(
637        &mut self,
638        init: Self::Perspective,
639    ) -> Result<(GraphId, &mut Self::Storage), StorageError>;
640
641    /// Get an existing graph.
642    ///
643    /// # Arguments
644    ///
645    /// * `graph` - ID of the graph, taken from the initialization command.
646    fn get_storage(&mut self, graph: GraphId) -> Result<&mut Self::Storage, StorageError>;
647
648    /// Remove a graph.
649    ///
650    /// # Arguments
651    ///
652    /// * `graph` - ID of the graph, taken from the initialization command.
653    fn remove_storage(&mut self, graph: GraphId) -> Result<(), StorageError>;
654
655    /// Gets a list of all stored graphs by their graph ID.
656    // TODO(nikki): rewrite this once we can use coroutines/generators?
657    fn list_graph_ids(
658        &mut self,
659    ) -> Result<impl Iterator<Item = Result<GraphId, StorageError>>, StorageError>;
660}
661
662/// Backward-traversal search for `address`, starting from the locations
663/// already seeded into `queue`. Seeds must have `max_cut >= address.max_cut`.
664///
665/// See `aranya-docs/docs/graph-traversal.md` for the traversal algorithm
666/// specification.
667fn search_queued<S: Storage + ?Sized>(
668    storage: &S,
669    address: Address,
670    queue: &mut TraversalQueue,
671) -> Result<Option<Location>, StorageError> {
672    while let Some(loc) = queue.pop()? {
673        debug_assert!(
674            loc.max_cut >= address.max_cut,
675            "Invariant: we only enqueue locations with at least the target max cut"
676        );
677
678        // Must load segment
679        let segment = storage.get_segment(loc)?;
680
681        // Search commands in this segment.
682        if let Some(found) = segment.get_by_address(address) {
683            return Ok(Some(found));
684        }
685
686        // Try to use skip list to jump directly backward.
687        // Skip list is sorted by max_cut ascending, so the first entry
688        // with max_cut >= target has the lowest valid max_cut, jumping
689        // furthest back in the graph.
690        if let Some(&skip) = segment
691            .skip_list()
692            .iter()
693            .find(|skip| skip.max_cut >= address.max_cut)
694        {
695            queue.push(skip)?;
696        } else {
697            // No valid skip - add prior locations to queue
698            for prior in segment.prior() {
699                if prior.max_cut >= address.max_cut {
700                    queue.push(prior)?;
701                }
702            }
703        }
704    }
705    Ok(None)
706}
707
708/// Represents the runtime's graph; [`Command`]s in storage have been validated
709/// by an associated policy and committed to state.
710pub trait Storage {
711    type Perspective: Perspective + Revertable;
712    type FactPerspective: FactPerspective;
713    type Segment: Segment<FactIndex = Self::FactIndex>;
714    type FactIndex: FactIndex;
715
716    /// Returns the location of Command with id if it has been stored by
717    /// searching from the head.
718    fn get_location(
719        &self,
720        address: Address,
721        buffer: &mut TraversalBuffer,
722    ) -> Result<Option<Location>, StorageError> {
723        // The graph may be multi-head (lazy merges). Run one search seeded
724        // with every head that could reach the target, so ancestry shared
725        // between heads is traversed once rather than once per head.
726        let queue = buffer.get();
727        for head in self.get_heads()?.iter() {
728            if head.max_cut >= address.max_cut {
729                queue.push(head.location())?;
730            }
731        }
732        search_queued(self, address, queue)
733    }
734
735    /// Returns the location of Command with id by searching from the given location.
736    ///
737    /// See `aranya-docs/docs/graph-traversal.md` for the traversal algorithm specification.
738    fn get_location_from(
739        &self,
740        start: Location,
741        address: Address,
742        buffer: &mut TraversalBuffer,
743    ) -> Result<Option<Location>, StorageError> {
744        if start.max_cut < address.max_cut {
745            return Ok(None);
746        }
747
748        let queue = buffer.get();
749        queue.push(start)?;
750        search_queued(self, address, queue)
751    }
752
753    /// Returns the address of the command at the given location.
754    ///
755    /// By default, this fetches the segment, then the command, then the address.
756    fn get_command_address(&self, location: Location) -> Result<Address, StorageError> {
757        let segment = self.get_segment(location)?;
758        let command = segment
759            .get_command(location)
760            .ok_or(StorageError::CommandOutOfBounds(location))?;
761        let address = command.address()?;
762        Ok(address)
763    }
764
765    /// Returns a linear perspective at the given location.
766    fn get_linear_perspective(&self, parent: Location) -> Result<Self::Perspective, StorageError>;
767
768    /// Returns a fact perspective at the given location, intended for evaluating braids.
769    /// The fact perspective will include the facts of the command at the given location.
770    fn get_fact_perspective(&self, first: Location) -> Result<Self::FactPerspective, StorageError>;
771
772    /// Returns a merge perspective based on the given locations with the braid as prior facts.
773    fn new_merge_perspective(
774        &self,
775        left: Location,
776        right: Location,
777        last_common_ancestor: Location,
778        policy_id: PolicyId,
779        braid: Self::FactIndex,
780    ) -> Result<Self::Perspective, StorageError>;
781
782    /// Returns the segment at the given location.
783    fn get_segment(&self, location: Location) -> Result<Self::Segment, StorageError>;
784
785    /// Returns the committed head set.
786    ///
787    /// Borrows an in-memory cache, so this is cheap to call repeatedly on hot
788    /// paths (no per-call deserialize or copy). Callers that need an owned set
789    /// should clone the returned reference.
790    fn get_heads(&self) -> Result<&HeadSet, StorageError>;
791
792    /// Returns the stamp of the committed head set.
793    ///
794    /// Changes on every [`commit_heads`](Self::commit_heads), so a value
795    /// captured at transaction start detects intervening commits.
796    fn heads_offset(&self) -> Result<HeadSetOffset, StorageError>;
797
798    /// Returns the cached merged fact index for the current head set.
799    fn fact_cache(&self) -> Result<Self::FactIndex, StorageError>;
800
801    /// Commit the given head set with its rebuilt fact cache.
802    fn commit_heads(
803        &mut self,
804        heads: HeadSet,
805        fact_cache: Self::FactIndex,
806    ) -> Result<(), StorageError>;
807
808    /// Returns the address of the sole graph head.
809    ///
810    /// Errors with [`StorageError::MultipleHeads`] on a multi-head (lazy-merge)
811    /// graph, where there is no single head to report. Callers that may face a
812    /// multi-head graph should use [`get_heads`](Self::get_heads) instead, or
813    /// [`ClientState::hello_head`](crate::ClientState::hello_head) when
814    /// advertising graph state to peers.
815    ///
816    /// An initialized graph always has at least one head, so an empty head set
817    /// is treated as an invariant violation (a [`Bug`]).
818    fn get_head_address(&self) -> Result<Address, StorageError> {
819        let heads = self.get_heads()?;
820        let mut it = heads.iter();
821        let first = it.next().assume("initialized graph always has >= 1 head")?;
822        if it.next().is_some() {
823            return Err(StorageError::MultipleHeads(heads.len()));
824        }
825        Ok(first.address())
826    }
827
828    /// Writes the given perspective to a segment.
829    fn write(&mut self, perspective: Self::Perspective) -> Result<Self::Segment, StorageError>;
830
831    /// Writes the given fact perspective to a fact index.
832    fn write_facts(
833        &mut self,
834        fact_perspective: Self::FactPerspective,
835    ) -> Result<Self::FactIndex, StorageError>;
836
837    /// Determine whether the given location is an ancestor of the given location.
838    fn is_ancestor(
839        &self,
840        search_location: Location,
841        start_location: Location,
842        buffer: &mut TraversalBuffer,
843    ) -> Result<bool, StorageError> {
844        if search_location.max_cut > start_location.max_cut || search_location == start_location {
845            return Ok(false);
846        }
847
848        let queue = buffer.get();
849        queue.push(start_location)?;
850
851        while let Some(loc) = queue.pop()? {
852            debug_assert!(
853                loc.max_cut >= search_location.max_cut,
854                "Invariant: we only enqueue locations with at least the target max cut"
855            );
856
857            // Must load segment
858            let segment = self.get_segment(loc)?;
859
860            // Search commands in this segment.
861            if segment.get_command(search_location).is_some() {
862                return Ok(true);
863            }
864
865            // Try to use skip list to jump directly backward.
866            // Skip list is sorted by max_cut ascending, so the first entry
867            // with max_cut >= target has the lowest valid max_cut, jumping
868            // furthest back in the graph.
869            if let Some(&skip) = segment
870                .skip_list()
871                .iter()
872                .find(|skip| skip.max_cut >= search_location.max_cut)
873            {
874                queue.push(skip)?;
875            } else {
876                // No valid skip - add prior locations to queue
877                for prior in segment.prior() {
878                    if prior.max_cut >= search_location.max_cut {
879                        queue.push(prior)?;
880                    }
881                }
882            }
883        }
884        Ok(false)
885    }
886}
887
888/// A segment is a nonempty sequence of commands persisted to storage.
889///
890/// A segment can be one of three types. This might be encoded in a future version of the API.
891/// * init   - This segment is the first segment of the graph and begins with an init command.
892/// * linear - This segment has a single prior command and is simply a sequence of linear commands.
893/// * merge  - This segment merges two other segments and thus begins with a merge command. A merge
894///   segment has a braid as it's prior facts.
895///
896/// Each command past the first must have the parent of the previous command in the segment.
897pub trait Segment {
898    type FactIndex: FactIndex;
899    type Command<'a>: Command
900    where
901        Self: 'a;
902
903    /// Returns the segment's index.
904    fn index(&self) -> SegmentIndex;
905
906    /// Returns the ID of the head of the segment.
907    fn head_id(&self) -> CmdId;
908
909    /// Returns the id for the policy used for this segment.
910    fn policy(&self) -> PolicyId;
911
912    /// Returns the prior segments for this segment.
913    fn prior(&self) -> Prior<Location>;
914
915    /// Returns the command at the given location.
916    fn get_command(&self, location: Location) -> Option<Self::Command<'_>>;
917
918    /// Get the fact index associated with this segment.
919    fn facts(&self) -> Result<Self::FactIndex, StorageError>;
920
921    /// The shortest max cut for this segment.
922    ///
923    /// This will always the max cut of the first command in the segment.
924    fn shortest_max_cut(&self) -> MaxCut;
925
926    /// The longest max cut for this segment.
927    ///
928    /// This will always be the max cut of the last command in the segment.
929    fn longest_max_cut(&self) -> Result<MaxCut, StorageError>;
930
931    /// The skip list is a series of locations that can be safely jumped to
932    /// when searching for a location. As long as the max cut of the location
933    /// you're jumping to is greater than or equal to the location you're
934    /// searching for you can jump to it and be guaranteed not to miss
935    /// the location you're searching for.
936    ///
937    /// For merge commands the last location in the skip list is the least
938    /// common ancestor.
939    fn skip_list(&self) -> &[Location];
940
941    /// Returns an iterator of commands starting at the given location.
942    fn get_from(&self, location: Location) -> Vec<Self::Command<'_>> {
943        let segment = location.segment;
944        core::iter::successors(Some(location.max_cut), |max_cut| max_cut.checked_add(1))
945            .map_while(|max_cut| self.get_command(Location { max_cut, segment }))
946            .collect()
947    }
948
949    /// Returns the location of the command with the given address from within this segment.
950    fn get_by_address(&self, address: Address) -> Option<Location> {
951        let loc = Location::new(self.index(), address.max_cut);
952        let cmd = self.get_command(loc)?;
953        if cmd.id() != address.id {
954            return None;
955        }
956        Some(loc)
957    }
958
959    /// Returns the location of the first command.
960    fn first_location(&self) -> Location {
961        Location {
962            max_cut: self.shortest_max_cut(),
963            segment: self.index(),
964        }
965    }
966
967    /// Returns the location of the head of the segment.
968    fn head_location(&self) -> Result<Location, StorageError> {
969        Ok(Location {
970            max_cut: self.longest_max_cut()?,
971            segment: self.index(),
972        })
973    }
974
975    /// Returns the address of the head of the segment.
976    fn head_address(&self) -> Result<Address, StorageError> {
977        Ok(Address {
978            id: self.head_id(),
979            max_cut: self.longest_max_cut()?,
980        })
981    }
982
983    /// Walks a location toward init if it would still point within this segment.
984    #[must_use]
985    fn previous(&self, mut location: Location) -> Option<Location> {
986        debug_assert_eq!(location.segment, self.index());
987        if location.max_cut <= self.shortest_max_cut() {
988            return None;
989        }
990        location.max_cut = location.max_cut.decremented()?;
991        Some(location)
992    }
993}
994
995/// An index of facts in storage.
996pub trait FactIndex: Query {}
997
998/// A perspective is essentially a mutable, in-memory version of a [`Segment`],
999/// with the same three types.
1000pub trait Perspective: FactPerspective {
1001    /// Returns the id for the policy used for this perspective.
1002    fn policy(&self) -> PolicyId;
1003
1004    /// Adds the given command to the head of the perspective. The command's
1005    /// parent must be the head of the perspective.
1006    fn add_command(&mut self, command: &impl Command) -> Result<usize, StorageError>;
1007
1008    /// Returns true if the perspective contains a command with the given ID.
1009    fn includes(&self, id: CmdId) -> bool;
1010
1011    /// Returns the head address in the perspective, if it exists
1012    fn head_address(&self) -> Result<Prior<Address>, Bug>;
1013}
1014
1015/// A fact perspective is essentially a mutable, in-memory version of a [`FactIndex`].
1016pub trait FactPerspective: QueryMut {}
1017
1018/// A revertable perspective can make checkpoints and be reverted such that the
1019/// state of the perspective matches that when the checkpoint was created.
1020pub trait Revertable {
1021    /// Create a checkpoint which can be used to revert the perspective.
1022    fn checkpoint(&self) -> Checkpoint;
1023
1024    /// Revert the perspective to the state it was at when the checkpoint was created.
1025    fn revert(&mut self, checkpoint: Checkpoint) -> Result<(), StorageError>;
1026}
1027
1028/// A checkpoint used to revert perspectives.
1029pub struct Checkpoint {
1030    /// An index interpreted by a given `Revertable` implementation to revert to a prior point.
1031    pub index: usize,
1032}
1033
1034/// Can be queried to look up facts.
1035///
1036/// Facts are labeled by a name, which are generally a bounded set of human-readable strings determined in advance.
1037///
1038/// Within a name, facts are an association of compound keys to values. The facts are keyed by a compound key
1039/// `(k_1, k_2, ..., k_n)`, where each `k` is a sequence of bytes. The fact value is also a sequence of bytes.
1040pub trait Query {
1041    /// Look up a named fact by an exact match of the compound key.
1042    fn query(&self, name: &str, keys: &[Bytes]) -> Result<Option<Bytes>, StorageError>;
1043
1044    /// Iterator for [`Query::query_prefix`].
1045    type QueryIterator: Iterator<Item = Result<Fact, StorageError>>;
1046
1047    /// Look up all named facts that begin with the prefix of keys, in sorted key order.
1048    ///
1049    /// The `prefix` is a partial compound key `(k_1, k_2, ..., k_n)`, where each `k` is a sequence of bytes.
1050    /// This returns all facts under the name with keys such that `prefix` is equal to a prefix of the fact's keys.
1051    fn query_prefix(
1052        &self,
1053        name: &str,
1054        prefix: &[Bytes],
1055    ) -> Result<Self::QueryIterator, StorageError>;
1056}
1057
1058/// A fact with a key and value.
1059#[derive(Debug, PartialEq, Eq)]
1060pub struct Fact {
1061    /// The sequence of keys.
1062    pub key: Keys,
1063    /// The bytes of the value.
1064    pub value: Bytes,
1065}
1066
1067/// Can mutate facts by inserting and deleting them.
1068///
1069/// See [`Query`] for details on the nature of facts.
1070pub trait QueryMut: Query {
1071    /// Insert a fact labeled by a name, with a given compound key and a value.
1072    ///
1073    /// This fact can later be looked up by [`Query`] methods, using the name and keys.
1074    fn insert(&mut self, name: String, keys: Keys, value: Bytes) -> Result<(), StorageError>;
1075
1076    /// Delete any fact associated to the compound key, under the given name.
1077    fn delete(&mut self, name: String, keys: Keys) -> Result<(), StorageError>;
1078}
1079
1080// TODO(jdygert): Expose this?
1081#[cfg(all(test, feature = "graphviz"))]
1082pub(crate) trait FactIndexExtra {
1083    fn name(&self) -> String;
1084    fn prior(&self) -> Result<Option<Self>, StorageError>
1085    where
1086        Self: Sized;
1087}
1088
1089/// A sequence of byte-based keys, used for facts.
1090#[derive(
1091    Clone,
1092    Debug,
1093    Default,
1094    PartialEq,
1095    Eq,
1096    PartialOrd,
1097    Ord,
1098    serde::Serialize,
1099    serde::Deserialize,
1100    rkyv::Archive,
1101    rkyv::Serialize,
1102    rkyv::Deserialize,
1103)]
1104pub struct Keys(Box<[Bytes]>);
1105
1106impl Deref for Keys {
1107    type Target = [Bytes];
1108    fn deref(&self) -> &[Bytes] {
1109        self.0.as_ref()
1110    }
1111}
1112
1113impl AsRef<[Bytes]> for Keys {
1114    fn as_ref(&self) -> &[Bytes] {
1115        self.0.as_ref()
1116    }
1117}
1118
1119impl Borrow<[Bytes]> for Keys {
1120    fn borrow(&self) -> &[Bytes] {
1121        self.0.as_ref()
1122    }
1123}
1124
1125impl From<Vec<Bytes>> for Keys {
1126    fn from(value: Vec<Bytes>) -> Self {
1127        Self(value.into_boxed_slice())
1128    }
1129}
1130
1131impl From<&[&[u8]]> for Keys {
1132    fn from(value: &[&[u8]]) -> Self {
1133        value.iter().copied().collect()
1134    }
1135}
1136
1137impl<B: Into<Bytes>> FromIterator<B> for Keys {
1138    fn from_iter<T: IntoIterator<Item = B>>(iter: T) -> Self {
1139        Self(iter.into_iter().map(Into::into).collect())
1140    }
1141}
1142
1143impl<'a> IntoIterator for &'a Keys {
1144    type Item = &'a Bytes;
1145    type IntoIter = core::slice::Iter<'a, Bytes>;
1146    fn into_iter(self) -> Self::IntoIter {
1147        self.0.iter()
1148    }
1149}
1150
1151impl ArchivedKeys {
1152    pub fn iter(&self) -> impl Iterator<Item = &[u8]> {
1153        self.0.iter().map(AsRef::as_ref)
1154    }
1155}
1156
1157pub type Bytes = Box<[u8]>;
1158
1159mod impls {
1160    use alloc::boxed::Box;
1161
1162    use super::{GraphId, PolicyId, StorageError, StorageProvider};
1163
1164    impl<SP: StorageProvider> StorageProvider for &mut SP {
1165        type Perspective = SP::Perspective;
1166        type Segment = SP::Segment;
1167        type Storage = SP::Storage;
1168
1169        fn new_perspective(&mut self, policy_id: PolicyId) -> Self::Perspective {
1170            SP::new_perspective(self, policy_id)
1171        }
1172
1173        fn new_storage(
1174            &mut self,
1175            init: Self::Perspective,
1176        ) -> Result<(GraphId, &mut Self::Storage), StorageError> {
1177            SP::new_storage(self, init)
1178        }
1179
1180        fn get_storage(&mut self, graph: GraphId) -> Result<&mut Self::Storage, StorageError> {
1181            SP::get_storage(self, graph)
1182        }
1183
1184        fn remove_storage(&mut self, graph: GraphId) -> Result<(), StorageError> {
1185            SP::remove_storage(self, graph)
1186        }
1187
1188        fn list_graph_ids(
1189            &mut self,
1190        ) -> Result<impl Iterator<Item = Result<GraphId, StorageError>>, StorageError> {
1191            SP::list_graph_ids(self)
1192        }
1193    }
1194
1195    impl<SP: StorageProvider> StorageProvider for Box<SP> {
1196        type Perspective = SP::Perspective;
1197        type Segment = SP::Segment;
1198        type Storage = SP::Storage;
1199
1200        fn new_perspective(&mut self, policy_id: PolicyId) -> Self::Perspective {
1201            SP::new_perspective(self, policy_id)
1202        }
1203
1204        fn new_storage(
1205            &mut self,
1206            init: Self::Perspective,
1207        ) -> Result<(GraphId, &mut Self::Storage), StorageError> {
1208            SP::new_storage(self, init)
1209        }
1210
1211        fn get_storage(&mut self, graph: GraphId) -> Result<&mut Self::Storage, StorageError> {
1212            SP::get_storage(self, graph)
1213        }
1214
1215        fn remove_storage(&mut self, graph: GraphId) -> Result<(), StorageError> {
1216            SP::remove_storage(self, graph)
1217        }
1218
1219        fn list_graph_ids(
1220            &mut self,
1221        ) -> Result<impl Iterator<Item = Result<GraphId, StorageError>>, StorageError> {
1222            SP::list_graph_ids(self)
1223        }
1224    }
1225}
1226
1227#[cfg(test)]
1228mod queue_tests {
1229    use super::*;
1230
1231    fn loc(seg: usize, mc: usize) -> Location {
1232        Location::new(SegmentIndex::new(seg as u64), MaxCut::new(mc as u64))
1233    }
1234
1235    #[test]
1236    #[ignore = "queue is currently unbounded"]
1237    fn test_queue_overflow_returns_error() {
1238        let mut queue = TraversalQueue::new();
1239        // Fill to capacity
1240        for i in 0..QUEUE_CAPACITY {
1241            queue.push(loc(i, i)).unwrap();
1242        }
1243        // Next push should fail with TraversalQueueOverflow
1244        let result = queue
1245            .push(loc(999, 999))
1246            .expect_err("expected push_queue to fail");
1247        assert_eq!(result, StorageError::TraversalQueueOverflow(QUEUE_CAPACITY));
1248    }
1249
1250    #[test]
1251    fn test_push_defaults_covered_false() {
1252        let mut queue = TraversalQueue::new();
1253        queue.push(loc(0, 5)).unwrap();
1254        let (_, covered) = queue.pop_covered().unwrap().unwrap();
1255        assert!(!covered);
1256    }
1257
1258    #[test]
1259    fn test_push_covered_preserves_flag() {
1260        let mut queue = TraversalQueue::new();
1261        queue.push_covered(loc(0, 5), true).unwrap();
1262        let (_, covered) = queue.pop_covered().unwrap().unwrap();
1263        assert!(covered);
1264    }
1265
1266    #[test]
1267    fn test_push_covered_same_max_cut_ors_flags() {
1268        let mut queue = TraversalQueue::new();
1269        queue.push_covered(loc(0, 5), false).unwrap();
1270        queue.push_covered(loc(0, 5), true).unwrap();
1271        let (_, covered) = queue.pop_covered().unwrap().unwrap();
1272        assert!(covered);
1273    }
1274
1275    #[test]
1276    fn test_push_covered_same_max_cut_cannot_uncover() {
1277        let mut queue = TraversalQueue::new();
1278        queue.push_covered(loc(0, 5), true).unwrap();
1279        // Pushing uncovered at the same max_cut must not clear covered.
1280        queue.push_covered(loc(0, 5), false).unwrap();
1281        let (_, covered) = queue.pop_covered().unwrap().unwrap();
1282        assert!(covered);
1283    }
1284
1285    #[test]
1286    fn test_push_same_segment_updates_max_cut() {
1287        let mut queue = TraversalQueue::new();
1288        queue.push(loc(0, 5)).unwrap();
1289        queue.push(loc(0, 8)).unwrap();
1290        let l = queue.pop().unwrap().unwrap();
1291        assert_eq!(l.max_cut, MaxCut::new(8));
1292        assert!(queue.is_empty());
1293    }
1294
1295    #[test]
1296    fn test_push_covered_higher_max_cut_adopts_new_flag() {
1297        let mut queue = TraversalQueue::new();
1298        queue.push_covered(loc(0, 5), true).unwrap();
1299        // Higher max_cut uncovered: segment head moved beyond covered point.
1300        queue.push_covered(loc(0, 8), false).unwrap();
1301        let (l, covered) = queue.pop_covered().unwrap().unwrap();
1302        assert_eq!(l.max_cut, MaxCut::new(8));
1303        assert!(!covered);
1304    }
1305
1306    #[test]
1307    fn test_push_covered_lower_max_cut_no_change() {
1308        let mut queue = TraversalQueue::new();
1309        queue.push_covered(loc(0, 8), false).unwrap();
1310        // Lower max_cut should not change anything.
1311        queue.push_covered(loc(0, 3), true).unwrap();
1312        let (l, covered) = queue.pop_covered().unwrap().unwrap();
1313        assert_eq!(l.max_cut, MaxCut::new(8));
1314        assert!(!covered);
1315    }
1316
1317    #[test]
1318    fn test_pop_discards_covered_flag() {
1319        let mut queue = TraversalQueue::new();
1320        queue.push_covered(loc(0, 5), true).unwrap();
1321        // pop() should return only the location.
1322        let l = queue.pop().unwrap().unwrap();
1323        assert_eq!(l.max_cut, MaxCut::new(5));
1324        assert!(queue.is_empty());
1325    }
1326
1327    #[test]
1328    fn test_all_covered() {
1329        let mut queue = TraversalQueue::new();
1330        queue.push_covered(loc(0, 1), true).unwrap();
1331        queue.push_covered(loc(1, 2), true).unwrap();
1332        assert!(queue.all_covered());
1333
1334        queue.push_covered(loc(2, 3), false).unwrap();
1335        assert!(!queue.all_covered());
1336    }
1337
1338    #[test]
1339    fn test_drain_above() {
1340        let mut queue = TraversalQueue::new();
1341        queue.push(loc(0, 3)).unwrap();
1342        queue.push(loc(1, 7)).unwrap();
1343        queue.push(loc(2, 5)).unwrap();
1344
1345        let mut result: heapless::Vec<Location, 8> = heapless::Vec::new();
1346        queue
1347            .drain_above(MaxCut::new(4), |loc| {
1348                let _ = result.push(loc);
1349            })
1350            .unwrap();
1351
1352        // Entries with max_cut > 4 should be drained.
1353        assert_eq!(result.len(), 2);
1354        assert!(result.iter().any(|l| l.max_cut == MaxCut::new(7)));
1355        assert!(result.iter().any(|l| l.max_cut == MaxCut::new(5)));
1356
1357        // Only max_cut=3 should remain in the queue.
1358        let remaining = queue.pop().unwrap().unwrap();
1359        assert_eq!(remaining.max_cut, MaxCut::new(3));
1360        assert!(queue.is_empty());
1361    }
1362
1363    #[test]
1364    fn test_drain_above_with_covered_entries() {
1365        let mut queue = TraversalQueue::new();
1366        // Mix of uncovered and covered entries above and below threshold.
1367        queue.push(loc(0, 3)).unwrap(); // uncovered, below
1368        queue.push(loc(1, 7)).unwrap(); // uncovered, above
1369        queue.push_covered(loc(2, 6), true).unwrap(); // covered, above
1370        queue.push_covered(loc(3, 2), true).unwrap(); // covered, below
1371        queue.push(loc(4, 5)).unwrap(); // uncovered, above
1372
1373        let mut drained: heapless::Vec<Location, 8> = heapless::Vec::new();
1374        queue
1375            .drain_above(MaxCut::new(4), |loc| {
1376                let _ = drained.push(loc);
1377            })
1378            .unwrap();
1379
1380        // Only uncovered entries above threshold should be passed to f.
1381        assert_eq!(drained.len(), 2);
1382        assert!(drained.iter().any(|l| l.segment.get() == 1));
1383        assert!(drained.iter().any(|l| l.segment.get() == 4));
1384
1385        // Covered entry above threshold (seg=2) should be discarded.
1386        // Entries below threshold should remain: seg=0 (uncovered), seg=3 (covered).
1387        let mut remaining = Vec::new();
1388        while let Some((l, covered)) = queue.pop_covered().unwrap() {
1389            remaining.push((l.segment, covered));
1390        }
1391        assert_eq!(remaining.len(), 2);
1392        assert!(remaining.contains(&(SegmentIndex::new(0), false)));
1393        assert!(remaining.contains(&(SegmentIndex::new(3), true)));
1394    }
1395
1396    #[test]
1397    fn test_push_duplicate_keeps_separate_entries() {
1398        let mut queue = TraversalQueue::new();
1399        queue.push_duplicate(loc(0, 5)).unwrap();
1400        queue.push_duplicate(loc(0, 5)).unwrap();
1401        let first = queue.pop().unwrap();
1402        assert!(first.is_some());
1403        let second = queue.pop().unwrap();
1404        assert!(second.is_some());
1405        assert!(queue.is_empty());
1406    }
1407
1408    #[test]
1409    #[ignore = "queue is currently unbounded"]
1410    fn test_push_duplicate_overflow() {
1411        let mut queue = TraversalQueue::new();
1412        for i in 0..QUEUE_CAPACITY {
1413            queue.push_duplicate(loc(0, i)).unwrap();
1414        }
1415        let result = queue.push_duplicate(loc(0, 999));
1416        assert_eq!(
1417            result.unwrap_err(),
1418            StorageError::TraversalQueueOverflow(QUEUE_CAPACITY)
1419        );
1420    }
1421
1422    #[test]
1423    fn test_pop_duplicates_returns_count() {
1424        let mut queue = TraversalQueue::new();
1425        queue.push_duplicate(loc(0, 5)).unwrap();
1426        queue.push_duplicate(loc(0, 5)).unwrap();
1427        queue.push_duplicate(loc(1, 3)).unwrap();
1428
1429        let (location, count) = queue.pop_duplicates().unwrap().unwrap();
1430        assert_eq!(location, loc(0, 5));
1431        assert_eq!(count, 2);
1432
1433        let (location, count) = queue.pop_duplicates().unwrap().unwrap();
1434        assert_eq!(location, loc(1, 3));
1435        assert_eq!(count, 1);
1436
1437        assert!(queue.pop_duplicates().unwrap().is_none());
1438    }
1439
1440    #[test]
1441    fn test_pop_duplicates_different_segments_same_max_cut() {
1442        let mut queue = TraversalQueue::new();
1443        queue.push_duplicate(loc(0, 5)).unwrap();
1444        queue.push_duplicate(loc(1, 5)).unwrap();
1445
1446        let (location, count) = queue.pop_duplicates().unwrap().unwrap();
1447        assert_eq!(count, 1);
1448        assert_eq!(location.max_cut, MaxCut::new(5));
1449
1450        let (_, count) = queue.pop_duplicates().unwrap().unwrap();
1451        assert_eq!(count, 1);
1452
1453        assert!(queue.pop_duplicates().unwrap().is_none());
1454    }
1455
1456    #[test]
1457    fn test_pop_duplicates_empty() {
1458        let mut queue = TraversalQueue::new();
1459        assert!(queue.pop_duplicates().unwrap().is_none());
1460    }
1461}