use crate::{
index::{
Cursor, Unordered as Index,
partitioned::{PartitionRange, Partitioned},
},
journal::{Error as JournalError, contiguous::Contiguous},
merkle::{
Bagging, Family, Location,
hasher::{Hasher as MerkleHasher, Standard as StandardHasher},
},
qmdb::operation::{Floored, Operation},
translator::Translator,
};
use cache::Cache;
use commonware_codec::Encode;
use commonware_cryptography::Hasher;
use commonware_runtime::{AbortOnDrop, ReadOptions, Spawner};
use commonware_utils::{
bitmap::{Atomic, BitMap},
channel::mpsc,
prefetch::Lookahead,
};
use core::{num::NonZeroUsize, ops::Range};
use futures::{StreamExt as _, pin_mut};
use std::{collections::VecDeque, sync::Arc};
use thiserror::Error;
pub mod any;
pub(crate) mod bitmap;
mod cache;
pub mod chain;
pub(crate) mod compact;
#[cfg(test)]
mod conformance;
pub mod current;
pub mod floor;
pub mod immutable;
pub mod keyless;
mod metrics;
pub mod operation;
pub mod store;
pub mod sync;
pub mod verify;
pub use verify::{
create_multi_proof, create_proof_store, verify_multi_proof, verify_proof,
verify_proof_and_extract_digests, verify_proof_and_pinned_nodes,
};
pub(crate) fn validate_initialization_bound<F: Family>(
bound: Option<Location<F>>,
) -> Result<(), Error<F>> {
if bound.is_some_and(|size| size == 0) {
return Err(Error::InvalidInitializationBound);
}
Ok(())
}
fn validate_initialization_commit<F: Family>(
start: u64,
size: u64,
fresh: bool,
commit: Option<&impl Floored<F>>,
replay_from_floor: bool,
) -> Result<Option<Location<F>>, Error<F>> {
if size == 0 {
return if fresh {
Ok(None)
} else {
Err(Error::DataCorrupted("no retained commit"))
};
}
let floor = commit
.and_then(Floored::has_floor)
.ok_or(Error::DataCorrupted(
"selected operation has no commit floor",
))?;
if *floor >= size {
return Err(Error::DataCorrupted(
"inactivity floor exceeds commit location",
));
}
if replay_from_floor && *floor < start {
return Err(Error::HistoricalFloorPruned(Location::new(size)));
}
Ok(Some(floor))
}
pub(crate) async fn validate_initialization<F, E, C, H, S>(
pending: &crate::journal::authenticated::Recovery<F, E, C, H, S>,
replay_from_floor: bool,
) -> Result<Option<Location<F>>, Error<F>>
where
F: Family,
E: crate::Context,
C: crate::journal::authenticated::Backing<E, Item: Floored<F> + commonware_codec::EncodeShared>,
H: Hasher,
S: commonware_parallel::Strategy,
{
let bounds = pending.bounds();
let commit = if bounds.end == 0 {
None
} else {
Some(pending.read(bounds.end - 1).await?)
};
validate_initialization_commit(
bounds.start,
bounds.end,
pending.is_fresh(),
commit.as_ref(),
replay_from_floor,
)
}
pub(crate) async fn prepare_initialization<F, E, C, H, S>(
context: E,
merkle: crate::merkle::full::Config<S>,
journal: C::Config,
max_size: Option<Location<F>>,
) -> Result<crate::journal::authenticated::Recovery<F, E, C, H, S>, Error<F>>
where
F: Family,
E: crate::Context,
C: crate::journal::authenticated::Backing<E, Item: Floored<F> + commonware_codec::EncodeShared>,
H: Hasher,
S: commonware_parallel::Strategy,
{
validate_initialization_bound(max_size)?;
Ok(crate::journal::authenticated::Journal::prepare(
context,
merkle,
journal,
max_size.map(|size| *size),
|op: &C::Item| op.has_floor().is_some(),
ROOT_BAGGING,
)
.await?)
}
#[commonware_macros::boxed]
pub(crate) async fn init_journal<F, E, C, H, S>(
context: E,
merkle: crate::merkle::full::Config<S>,
journal: C::Config,
max_size: Option<Location<F>>,
replay_from_floor: bool,
) -> Result<crate::journal::authenticated::Journal<F, E, C, H, S>, Error<F>>
where
F: Family,
E: crate::Context,
C: crate::journal::authenticated::Backing<E, Item: Floored<F> + commonware_codec::EncodeShared>,
H: Hasher,
S: commonware_parallel::Strategy,
{
let pending = prepare_initialization(context, merkle, journal, max_size).await?;
validate_initialization(&pending, replay_from_floor).await?;
Ok(pending.finish().await?)
}
pub(crate) const ROOT_BAGGING: Bagging = Bagging::BackwardFold;
pub const fn hasher<H: Hasher>() -> StandardHasher<H> {
StandardHasher::new(ROOT_BAGGING)
}
fn single_operation_root<F: Family, H: Hasher>(operation: &impl Encode) -> H::Digest {
let hasher = hasher::<H>();
let leaf = MerkleHasher::<F>::leaf_digest(
&hasher,
F::location_to_position(Location::new(0)),
&operation.encode(),
);
MerkleHasher::<F>::root(&hasher, Location::new(1), 0, [&leaf])
.expect("a single-leaf Merkle root is always valid")
}
pub(crate) async fn find_inactivity_floor_at<F, R>(
reader: &R,
op_count: Location<F>,
) -> Result<Location<F>, Error<F>>
where
F: Family,
R: Contiguous<Item: Floored<F>>,
{
let Some(last_op) = op_count.checked_sub(1) else {
return Err(Error::HistoricalFloorPruned(op_count));
};
let last_op = *last_op;
let bounds = reader.bounds();
if last_op < bounds.start {
return Err(JournalError::ItemPruned(last_op).into());
}
let op = reader.read(last_op).await?;
let floor = op
.has_floor()
.ok_or(Error::HistoricalFloorPruned(op_count))?;
if floor > Location::new(last_op) {
return Err(Error::DataCorrupted(
"inactivity floor exceeds commit location",
));
}
Ok(floor)
}
pub(crate) async fn inactive_peaks_at<F, R>(
reader: &R,
op_count: Location<F>,
) -> Result<usize, Error<F>>
where
F: Family,
R: Contiguous<Item: Floored<F>>,
{
if op_count == Location::new(0) {
return Ok(0);
}
let floor = find_inactivity_floor_at::<F, _>(reader, op_count).await?;
Ok(F::inactive_peaks(op_count, floor))
}
#[derive(Error, Debug)]
pub enum Error<F: Family> {
#[error("initialization bound must allow at least one operation")]
InvalidInitializationBound,
#[error("data corrupted: {0}")]
DataCorrupted(&'static str),
#[error("merkle error: {0}")]
Merkle(#[from] crate::merkle::Error<F>),
#[error("metadata error: {0}")]
Metadata(#[from] crate::metadata::Error),
#[error("journal error: {0}")]
Journal(#[from] crate::journal::Error),
#[error("runtime error: {0}")]
Runtime(#[from] commonware_runtime::Error),
#[error("operation pruned: {0}")]
OperationPruned(Location<F>),
#[error("key not found")]
KeyNotFound,
#[error("key exists")]
KeyExists,
#[error("unexpected data at location: {0}")]
UnexpectedData(Location<F>),
#[error("location out of bounds: {0} >= {1}")]
LocationOutOfBounds(Location<F>, Location<F>),
#[error("location {1} out of order after {0}")]
LocationOutOfOrder(Location<F>, Location<F>),
#[error("prune location {0} beyond minimum required location {1}")]
PruneBeyondMinRequired(Location<F>, Location<F>),
#[error("stale batch: current database state does not match the batch")]
StaleBatch,
#[error("stale read: database is at no state of the batch's chain")]
StaleRead,
#[error("location below inactivity floor: {0}")]
BelowInactivityFloor(Location<F>),
#[error("floor regressed: batch floor {0} < prior floor {1}")]
FloorRegressed(Location<F>, Location<F>),
#[error("floor beyond commit location: floor {0} > commit loc {1}")]
FloorBeyondSize(Location<F>, Location<F>),
#[error("historical floor pruned for size: {0}")]
HistoricalFloorPruned(Location<F>),
}
impl<F: Family> From<crate::journal::authenticated::Error<F>> for Error<F> {
fn from(e: crate::journal::authenticated::Error<F>) -> Self {
match e {
crate::journal::authenticated::Error::Journal(j) => Self::Journal(j),
crate::journal::authenticated::Error::Merkle(m) => Self::Merkle(m),
}
}
}
pub(super) async fn build_snapshot_from_log<F, C, I, Fn>(
inactivity_floor_loc: crate::merkle::Location<F>,
reader: &C,
snapshot: &mut I,
init_buffer: NonZeroUsize,
cache_size: Option<NonZeroUsize>,
mut callback: Fn,
) -> Result<usize, Error<F>>
where
F: crate::merkle::Family,
C: Contiguous<Item: Operation<F>>,
I: Index<Value = crate::merkle::Location<F>>,
Fn: FnMut(bool, Option<crate::merkle::Location<F>>),
{
let bounds = reader.bounds();
let stream = reader
.replay(
*inactivity_floor_loc..bounds.end,
init_buffer,
ReadOptions::DONT_CACHE,
)
.await?;
pin_mut!(stream);
let last_commit_loc = bounds.end.saturating_sub(1);
let mut cache = cache_size.map(Cache::<<C::Item as Operation<F>>::Key>::new);
let mut active_keys: usize = 0;
while let Some(batch) = stream.next().await {
for (loc, op) in batch? {
if let Some(key) = op.key() {
if op.is_delete() {
let old_loc = delete_key(snapshot, reader, key, cache.as_mut()).await?;
callback(false, old_loc);
if old_loc.is_some() {
active_keys -= 1;
}
} else if op.is_update() {
let new_loc = crate::merkle::Location::new(loc);
let old_loc =
update_key(snapshot, reader, key, new_loc, cache.as_mut()).await?;
callback(true, old_loc);
if old_loc.is_none() {
active_keys += 1;
}
if let Some(cache) = cache.as_mut() {
cache.put(loc, op.into_key().expect("operation without key"));
}
}
} else if op.has_floor().is_some() {
callback(loc == last_commit_loc, None);
}
}
}
Ok(active_keys)
}
async fn delete_key<F, I, R>(
snapshot: &mut I,
reader: &R,
key: &<R::Item as Operation<F>>::Key,
cache: Option<&mut Cache<<R::Item as Operation<F>>::Key>>,
) -> Result<Option<Location<F>>, Error<F>>
where
F: Family,
I: Index<Value = Location<F>>,
R: Contiguous,
R::Item: Operation<F>,
{
let Some(cursor) = snapshot.get_mut(key) else {
return Ok(None);
};
delete_at_cursor::<F, _, _>(cursor, reader, key, cache).await
}
async fn delete_at_cursor<F, C, R>(
mut cursor: C,
reader: &R,
key: &<R::Item as Operation<F>>::Key,
mut cache: Option<&mut Cache<<R::Item as Operation<F>>::Key>>,
) -> Result<Option<Location<F>>, Error<F>>
where
F: Family,
C: Cursor<Value = Location<F>>,
R: Contiguous,
R::Item: Operation<F>,
{
let Some(loc) = find_update_op::<F, _>(reader, &mut cursor, key, cache.as_deref_mut()).await?
else {
return Ok(None);
};
cursor.delete();
if let Some(cache) = cache {
cache.remove(*loc);
}
Ok(Some(loc))
}
async fn update_key<F, I, R>(
snapshot: &mut I,
reader: &R,
key: &<R::Item as Operation<F>>::Key,
new_loc: Location<F>,
cache: Option<&mut Cache<<R::Item as Operation<F>>::Key>>,
) -> Result<Option<Location<F>>, Error<F>>
where
F: Family,
I: Index<Value = Location<F>>,
R: Contiguous,
R::Item: Operation<F>,
{
let Some(cursor) = snapshot.get_mut_or_insert(key, new_loc) else {
return Ok(None);
};
update_at_cursor::<F, _, _>(cursor, reader, key, new_loc, cache).await
}
async fn update_at_cursor<F, C, R>(
mut cursor: C,
reader: &R,
key: &<R::Item as Operation<F>>::Key,
new_loc: Location<F>,
mut cache: Option<&mut Cache<<R::Item as Operation<F>>::Key>>,
) -> Result<Option<Location<F>>, Error<F>>
where
F: Family,
C: Cursor<Value = Location<F>>,
R: Contiguous,
R::Item: Operation<F>,
{
if let Some(loc) =
find_update_op::<F, _>(reader, &mut cursor, key, cache.as_deref_mut()).await?
{
assert!(new_loc > loc);
cursor.update(new_loc);
if let Some(cache) = cache {
cache.remove(*loc);
}
return Ok(Some(loc));
}
cursor.insert(new_loc);
Ok(None)
}
async fn find_update_op<F, R>(
reader: &R,
cursor: &mut impl Cursor<Value = Location<F>>,
key: &<R::Item as Operation<F>>::Key,
mut cache: Option<&mut Cache<<R::Item as Operation<F>>::Key>>,
) -> Result<Option<Location<F>>, Error<F>>
where
F: Family,
R: Contiguous,
R::Item: Operation<F>,
{
while let Some(&loc) = cursor.next() {
let matches = if let Some(k) = cache.as_deref().and_then(|c| c.get(*loc)) {
*k == *key
} else {
let op = reader.read(*loc).await?;
let k = op.key().expect("operation without key");
let matches = *k == *key;
if !matches && let Some(cache) = cache.as_deref_mut() {
cache.put(*loc, op.into_key().expect("operation without key"));
}
matches
};
if matches {
return Ok(Some(loc));
}
}
Ok(None)
}
const SNAPSHOT_CHANNEL_DEPTH: usize = 4;
type RoutedBatch<K> = Vec<(K, u64, bool)>;
#[derive(Clone, Copy)]
struct SnapshotRouting {
workers: usize,
partition_of: fn(&[u8]) -> usize,
range_size: usize,
init_buffer: NonZeroUsize,
}
#[cfg(not(test))]
const SNAPSHOT_ROUTE_BATCH: usize = 4096;
#[cfg(test)]
const SNAPSHOT_ROUTE_BATCH: usize = 3;
#[cfg(not(test))]
const PREFETCH_DISTANCE: usize = 16;
#[cfg(test)]
const PREFETCH_DISTANCE: usize = 1;
#[cfg(not(test))]
const SNAPSHOT_DECODE_CHUNK: u64 = 1 << 17;
#[cfg(test)]
const SNAPSHOT_DECODE_CHUNK: u64 = 64;
async fn route_snapshot<F, C, Fut>(
log: &C,
range: Range<u64>,
routing: SnapshotRouting,
mut send: impl FnMut(usize, RoutedBatch<<C::Item as Operation<F>>::Key>) -> Fut + Send,
) -> Result<(), Error<F>>
where
F: Family,
C: Contiguous<Item: Operation<F>>,
Fut: Future<Output = bool> + Send,
{
let stream = log
.replay(range, routing.init_buffer, ReadOptions::DONT_CACHE)
.await?;
pin_mut!(stream);
let mut batches: Vec<RoutedBatch<_>> = (0..routing.workers)
.map(|_| Vec::with_capacity(SNAPSHOT_ROUTE_BATCH))
.collect();
while let Some(batch) = stream.next().await {
for (loc, op) in batch? {
let is_delete = op.is_delete();
let Some(key) = op.into_key() else { continue };
let w = (routing.partition_of)(key.as_ref()) / routing.range_size;
batches[w].push((key, loc, is_delete));
if batches[w].len() >= SNAPSHOT_ROUTE_BATCH {
let routed =
std::mem::replace(&mut batches[w], Vec::with_capacity(SNAPSHOT_ROUTE_BATCH));
if !send(w, routed).await {
return Ok(());
}
}
}
}
for (w, batch) in batches.into_iter().enumerate() {
if !batch.is_empty() && !send(w, batch).await {
break;
}
}
Ok(())
}
async fn build_snapshot_worker<F, C, R>(
log: Arc<C>,
mut rx: mpsc::Receiver<RoutedBatch<<C::Item as Operation<F>>::Key>>,
mut index: R,
activity: Range<u64>,
active: Arc<Atomic>,
cache_size: Option<NonZeroUsize>,
) -> Result<(R, usize), Error<F>>
where
F: Family,
C: Contiguous<Item: Operation<F>>,
R: PartitionRange<Value = Location<F>>,
{
let mut cache = cache_size.map(Cache::<<C::Item as Operation<F>>::Key>::new);
while let Some(batch) = rx.recv().await {
let mut ops = batch.into_iter();
let mut headers = Lookahead::new(2 * PREFETCH_DISTANCE);
let mut entries = Lookahead::new(PREFETCH_DISTANCE);
loop {
for (key, _, _) in headers.advance(ops.as_slice()) {
index.prefetch_header(key.as_ref());
}
for (key, loc, is_delete) in entries.advance(ops.as_slice()) {
index.prefetch_entries(key.as_ref());
if !is_delete && let Some(cache) = &cache {
cache.prefetch(*loc);
}
}
let Some((key, loc, is_delete)) = ops.next() else {
break;
};
if is_delete {
if let Some(cursor) = index.get_mut(&key) {
delete_at_cursor::<F, _, _>(cursor, &*log, &key, cache.as_mut()).await?;
}
} else {
let new_loc = Location::new(loc);
if let Some(cursor) = index.get_mut_or_insert(&key, new_loc) {
update_at_cursor::<F, _, _>(cursor, &*log, &key, new_loc, cache.as_mut())
.await?;
}
if let Some(cache) = cache.as_mut() {
cache.put(loc, key);
}
}
}
}
let mut active_keys = 0;
index.for_each_value(|loc| {
active.set(**loc - activity.start);
active_keys += 1;
});
Ok((index, active_keys))
}
async fn build_snapshot_serial<F, C, I>(
inactivity_floor_loc: Location<F>,
reader: &C,
snapshot: &mut I,
init_buffer: NonZeroUsize,
cache_size: Option<NonZeroUsize>,
) -> Result<(usize, BitMap), Error<F>>
where
F: Family,
C: Contiguous<Item: Operation<F>>,
I: Index<Value = Location<F>>,
{
let mut activity = BitMap::new();
let floor = *inactivity_floor_loc;
let active_keys = build_snapshot_from_log(
inactivity_floor_loc,
reader,
snapshot,
init_buffer,
cache_size,
|is_active, old_loc| {
activity.push(is_active);
if let Some(loc) = old_loc {
activity.set(*loc - floor, false);
}
},
)
.await?;
Ok((active_keys, activity))
}
async fn build_snapshot_parallel<F, E, C, I>(
snapshot: &mut I,
context: E,
inactivity_floor_loc: Location<F>,
log: &Arc<C>,
init_concurrency: NonZeroUsize,
init_buffer: NonZeroUsize,
cache_size: Option<NonZeroUsize>,
) -> Result<(usize, BitMap), Error<F>>
where
F: Family,
E: Spawner,
C: Contiguous<Item: Operation<F>> + 'static,
I: Partitioned + Index<Value = Location<F>>,
{
let count = snapshot.partition_count();
let concurrency = init_concurrency.get();
let decoders = if concurrency <= 3 {
0
} else {
let share = concurrency / 5 * 2 + concurrency % 5 * 2 / 5;
share.max(2).min(concurrency / 2)
};
let workers = if decoders == 0 {
concurrency.saturating_sub(1).min(count)
} else {
(concurrency - decoders).min(count)
};
if workers == 0 {
return build_snapshot_serial(
inactivity_floor_loc,
&**log,
snapshot,
init_buffer,
cache_size,
)
.await;
}
let floor = *inactivity_floor_loc;
let range_size = count.div_ceil(workers);
let workers = count.div_ceil(range_size);
let per_worker_cache = cache_size.and_then(|n| NonZeroUsize::new(n.get() / workers));
let end = log.bounds().end;
let active = Arc::new(Atomic::zeroes(end - floor));
let mut senders = Vec::with_capacity(workers);
let mut handles = Vec::with_capacity(workers);
for w in 0..workers {
let (tx, rx) = mpsc::channel(SNAPSHOT_CHANNEL_DEPTH);
senders.push(tx);
let log = log.clone();
let lo = w * range_size;
let range_len = range_size.min(count - lo);
let worker_index = snapshot.new_range(lo, range_len);
let active = active.clone();
let handle = context
.child("snapshot_worker")
.with_attribute("worker", w)
.dedicated()
.spawn(move |_| {
build_snapshot_worker::<F, C, I::Range>(
log,
rx,
worker_index,
floor..end,
active,
per_worker_cache,
)
});
handles.push(handle.abort_on_drop());
}
let mut pending = VecDeque::new();
let routing = SnapshotRouting {
workers,
partition_of: I::partition_of,
range_size,
init_buffer,
};
let routing_result: Result<(), Error<F>> = async {
if decoders == 0 {
let senders = &senders;
return route_snapshot::<F, C, _>(&**log, floor..end, routing, |w, batch| async move {
senders[w].send(batch).await.is_ok()
})
.await;
}
let chunk_capacity = SNAPSHOT_DECODE_CHUNK as usize / SNAPSHOT_ROUTE_BATCH + workers;
let mut starts = (floor..end)
.step_by(usize::try_from(SNAPSHOT_DECODE_CHUNK).expect("chunk size fits usize"));
let mut spawn_next = |pending: &mut VecDeque<_>| {
let Some(start) = starts.next() else {
return false;
};
let log = log.clone();
let len = SNAPSHOT_DECODE_CHUNK.min(end - start);
let (tx, rx) = mpsc::channel(chunk_capacity);
let handle = context
.child("snapshot_decoder")
.dedicated()
.spawn(move |_| async move {
let tx = &tx;
route_snapshot::<F, C, _>(
&*log,
start..start + len,
routing,
|w, batch| async move { tx.send((w, batch)).await.is_ok() },
)
.await
});
pending.push_back((rx, handle.abort_on_drop()));
true
};
for _ in 0..decoders {
if !spawn_next(&mut pending) {
break;
}
}
while let Some((rx, _)) = pending.front_mut() {
while let Some((w, batch)) = rx.recv().await {
if senders[w].send(batch).await.is_err() {
return Ok(());
}
}
let (_, decoder) = pending.pop_front().expect("front exists");
decoder.join().await??;
spawn_next(&mut pending);
}
Ok(())
}
.await;
drop(senders);
while let Some((rx, decoder)) = pending.pop_front() {
drop(rx);
decoder.abort().await;
}
let joined = AbortOnDrop::join_all::<Error<F>>(handles).await;
routing_result?;
let joined = joined?;
let mut total_items = 0;
for (worker_index, worker_keys) in joined {
snapshot.install_range(worker_index);
total_items += worker_keys;
}
let mut active = Arc::into_inner(active)
.expect("workers were joined")
.into_bitmap();
if let Some(last_commit) = end.checked_sub(1)
&& last_commit >= floor
{
active.set(last_commit - floor, true);
}
Ok((total_items, active))
}
pub trait SnapshotBuild<F: Family>:
sealed::SnapshotBuildSealed + Index<Value = Location<F>> + Sized + 'static
{
type Concurrency: Copy + Send + Sync + 'static;
fn build_snapshot<E, C>(
&mut self,
_context: E,
inactivity_floor_loc: Location<F>,
log: &Arc<C>,
_init_concurrency: Self::Concurrency,
init_buffer: NonZeroUsize,
cache_size: Option<NonZeroUsize>,
) -> impl Future<Output = Result<(usize, BitMap), Error<F>>> + Send
where
E: Spawner,
C: Contiguous<Item: Operation<F>> + 'static,
{
async move {
build_snapshot_serial(inactivity_floor_loc, &**log, self, init_buffer, cache_size).await
}
}
}
mod sealed {
use crate::translator::Translator;
pub trait SnapshotBuildSealed {}
impl<T: Translator, V: Send + Sync> SnapshotBuildSealed for crate::index::unordered::Index<T, V> {}
impl<T: Translator, V: Send + Sync> SnapshotBuildSealed for crate::index::ordered::Index<T, V> {}
impl<T: Translator, V: Send + Sync, const P: usize> SnapshotBuildSealed
for crate::index::partitioned::unordered::Index<T, V, P>
{
}
impl<T: Translator, V: Send + Sync, const P: usize> SnapshotBuildSealed
for crate::index::partitioned::ordered::Index<T, V, P>
{
}
}
impl<F: Family, T: Translator> SnapshotBuild<F> for crate::index::unordered::Index<T, Location<F>> {
type Concurrency = ();
}
impl<F: Family, T: Translator> SnapshotBuild<F> for crate::index::ordered::Index<T, Location<F>> {
type Concurrency = ();
}
impl<F: Family, T: Translator, const P: usize> SnapshotBuild<F>
for crate::index::partitioned::unordered::Index<T, Location<F>, P>
{
type Concurrency = NonZeroUsize;
async fn build_snapshot<E, C>(
&mut self,
context: E,
inactivity_floor_loc: Location<F>,
log: &Arc<C>,
init_concurrency: NonZeroUsize,
init_buffer: NonZeroUsize,
cache_size: Option<NonZeroUsize>,
) -> Result<(usize, BitMap), Error<F>>
where
E: Spawner,
C: Contiguous<Item: Operation<F>> + 'static,
{
build_snapshot_parallel(
self,
context,
inactivity_floor_loc,
log,
init_concurrency,
init_buffer,
cache_size,
)
.await
}
}
impl<F: Family, T: Translator, const P: usize> SnapshotBuild<F>
for crate::index::partitioned::ordered::Index<T, Location<F>, P>
{
type Concurrency = NonZeroUsize;
async fn build_snapshot<E, C>(
&mut self,
context: E,
inactivity_floor_loc: Location<F>,
log: &Arc<C>,
init_concurrency: NonZeroUsize,
init_buffer: NonZeroUsize,
cache_size: Option<NonZeroUsize>,
) -> Result<(usize, BitMap), Error<F>>
where
E: Spawner,
C: Contiguous<Item: Operation<F>> + 'static,
{
build_snapshot_parallel(
self,
context,
inactivity_floor_loc,
log,
init_concurrency,
init_buffer,
cache_size,
)
.await
}
}
fn update_known_loc<F: Family, I: Index<Value = Location<F>>>(
snapshot: &mut I,
key: &[u8],
old_loc: Location<F>,
new_loc: Location<F>,
) {
let mut cursor = snapshot.get_mut(key).expect("key should be known to exist");
assert!(
cursor.find(|&loc| *loc == old_loc),
"known key with given old_loc should have been found"
);
cursor.update(new_loc);
}
fn delete_known_loc<F: Family, I: Index<Value = Location<F>>>(
snapshot: &mut I,
key: &[u8],
old_loc: Location<F>,
) {
let mut cursor = snapshot.get_mut(key).expect("key should be known to exist");
assert!(
cursor.find(|&loc| *loc == old_loc),
"known key with given old_loc should have been found"
);
cursor.delete();
}