1use crate::{
80 index::{
81 Cursor, Unordered as Index,
82 partitioned::{PartitionRange, Partitioned},
83 },
84 journal::{Error as JournalError, contiguous::Contiguous},
85 merkle::{
86 Bagging, Family, Location,
87 hasher::{Hasher as MerkleHasher, Standard as StandardHasher},
88 },
89 qmdb::operation::{Floored, Operation},
90 translator::Translator,
91};
92use cache::Cache;
93use commonware_codec::Encode;
94use commonware_cryptography::Hasher;
95use commonware_runtime::{AbortOnDrop, ReadOptions, Spawner};
96use commonware_utils::{
97 bitmap::{Atomic, BitMap},
98 channel::mpsc,
99 prefetch::Lookahead,
100};
101use core::{num::NonZeroUsize, ops::Range};
102use futures::{StreamExt as _, pin_mut};
103use std::{collections::VecDeque, sync::Arc};
104use thiserror::Error;
105
106pub mod any;
107pub(crate) mod bitmap;
108mod cache;
109pub mod chain;
110pub(crate) mod compact;
111#[cfg(test)]
112mod conformance;
113pub mod current;
114pub mod floor;
115pub mod immutable;
116pub mod keyless;
117mod metrics;
118pub mod operation;
119pub mod store;
120pub mod sync;
121pub mod verify;
122
123pub use verify::{
124 create_multi_proof, create_proof_store, verify_multi_proof, verify_proof,
125 verify_proof_and_extract_digests, verify_proof_and_pinned_nodes,
126};
127
128pub(crate) fn validate_initialization_bound<F: Family>(
130 bound: Option<Location<F>>,
131) -> Result<(), Error<F>> {
132 if bound.is_some_and(|size| size == 0) {
133 return Err(Error::InvalidInitializationBound);
134 }
135 Ok(())
136}
137
138fn validate_initialization_commit<F: Family>(
140 start: u64,
141 size: u64,
142 fresh: bool,
143 commit: Option<&impl Floored<F>>,
144 replay_from_floor: bool,
145) -> Result<Option<Location<F>>, Error<F>> {
146 if size == 0 {
147 return if fresh {
148 Ok(None)
149 } else {
150 Err(Error::DataCorrupted("no retained commit"))
151 };
152 }
153 let floor = commit
154 .and_then(Floored::has_floor)
155 .ok_or(Error::DataCorrupted(
156 "selected operation has no commit floor",
157 ))?;
158 if *floor >= size {
159 return Err(Error::DataCorrupted(
160 "inactivity floor exceeds commit location",
161 ));
162 }
163 if replay_from_floor && *floor < start {
164 return Err(Error::HistoricalFloorPruned(Location::new(size)));
165 }
166 Ok(Some(floor))
167}
168
169pub(crate) async fn validate_initialization<F, E, C, H, S>(
172 pending: &crate::journal::authenticated::Recovery<F, E, C, H, S>,
173 replay_from_floor: bool,
174) -> Result<Option<Location<F>>, Error<F>>
175where
176 F: Family,
177 E: crate::Context,
178 C: crate::journal::authenticated::Backing<E, Item: Floored<F> + commonware_codec::EncodeShared>,
179 H: Hasher,
180 S: commonware_parallel::Strategy,
181{
182 let bounds = pending.bounds();
183 let commit = if bounds.end == 0 {
184 None
185 } else {
186 Some(pending.read(bounds.end - 1).await?)
187 };
188 validate_initialization_commit(
189 bounds.start,
190 bounds.end,
191 pending.is_fresh(),
192 commit.as_ref(),
193 replay_from_floor,
194 )
195}
196
197pub(crate) async fn prepare_initialization<F, E, C, H, S>(
199 context: E,
200 merkle: crate::merkle::full::Config<S>,
201 journal: C::Config,
202 max_size: Option<Location<F>>,
203) -> Result<crate::journal::authenticated::Recovery<F, E, C, H, S>, Error<F>>
204where
205 F: Family,
206 E: crate::Context,
207 C: crate::journal::authenticated::Backing<E, Item: Floored<F> + commonware_codec::EncodeShared>,
208 H: Hasher,
209 S: commonware_parallel::Strategy,
210{
211 validate_initialization_bound(max_size)?;
212 Ok(crate::journal::authenticated::Journal::prepare(
213 context,
214 merkle,
215 journal,
216 max_size.map(|size| *size),
217 |op: &C::Item| op.has_floor().is_some(),
218 ROOT_BAGGING,
219 )
220 .await?)
221}
222
223#[commonware_macros::boxed]
225pub(crate) async fn init_journal<F, E, C, H, S>(
226 context: E,
227 merkle: crate::merkle::full::Config<S>,
228 journal: C::Config,
229 max_size: Option<Location<F>>,
230 replay_from_floor: bool,
231) -> Result<crate::journal::authenticated::Journal<F, E, C, H, S>, Error<F>>
232where
233 F: Family,
234 E: crate::Context,
235 C: crate::journal::authenticated::Backing<E, Item: Floored<F> + commonware_codec::EncodeShared>,
236 H: Hasher,
237 S: commonware_parallel::Strategy,
238{
239 let pending = prepare_initialization(context, merkle, journal, max_size).await?;
240 validate_initialization(&pending, replay_from_floor).await?;
241 Ok(pending.finish().await?)
242}
243
244pub(crate) const ROOT_BAGGING: Bagging = Bagging::BackwardFold;
246
247pub const fn hasher<H: Hasher>() -> StandardHasher<H> {
249 StandardHasher::new(ROOT_BAGGING)
250}
251
252fn single_operation_root<F: Family, H: Hasher>(operation: &impl Encode) -> H::Digest {
257 let hasher = hasher::<H>();
258 let leaf = MerkleHasher::<F>::leaf_digest(
259 &hasher,
260 F::location_to_position(Location::new(0)),
261 &operation.encode(),
262 );
263 MerkleHasher::<F>::root(&hasher, Location::new(1), 0, [&leaf])
264 .expect("a single-leaf Merkle root is always valid")
265}
266
267pub(crate) async fn find_inactivity_floor_at<F, R>(
280 reader: &R,
281 op_count: Location<F>,
282) -> Result<Location<F>, Error<F>>
283where
284 F: Family,
285 R: Contiguous<Item: Floored<F>>,
286{
287 let Some(last_op) = op_count.checked_sub(1) else {
288 return Err(Error::HistoricalFloorPruned(op_count));
289 };
290 let last_op = *last_op;
291 let bounds = reader.bounds();
292 if last_op < bounds.start {
293 return Err(JournalError::ItemPruned(last_op).into());
294 }
295
296 let op = reader.read(last_op).await?;
297 let floor = op
298 .has_floor()
299 .ok_or(Error::HistoricalFloorPruned(op_count))?;
300 if floor > Location::new(last_op) {
301 return Err(Error::DataCorrupted(
302 "inactivity floor exceeds commit location",
303 ));
304 }
305 Ok(floor)
306}
307
308pub(crate) async fn inactive_peaks_at<F, R>(
310 reader: &R,
311 op_count: Location<F>,
312) -> Result<usize, Error<F>>
313where
314 F: Family,
315 R: Contiguous<Item: Floored<F>>,
316{
317 if op_count == Location::new(0) {
318 return Ok(0);
319 }
320
321 let floor = find_inactivity_floor_at::<F, _>(reader, op_count).await?;
322 Ok(F::inactive_peaks(op_count, floor))
323}
324
325#[derive(Error, Debug)]
327pub enum Error<F: Family> {
328 #[error("initialization bound must allow at least one operation")]
330 InvalidInitializationBound,
331
332 #[error("data corrupted: {0}")]
333 DataCorrupted(&'static str),
334
335 #[error("merkle error: {0}")]
336 Merkle(#[from] crate::merkle::Error<F>),
337
338 #[error("metadata error: {0}")]
339 Metadata(#[from] crate::metadata::Error),
340
341 #[error("journal error: {0}")]
342 Journal(#[from] crate::journal::Error),
343
344 #[error("runtime error: {0}")]
345 Runtime(#[from] commonware_runtime::Error),
346
347 #[error("operation pruned: {0}")]
348 OperationPruned(Location<F>),
349
350 #[error("key not found")]
352 KeyNotFound,
353
354 #[error("key exists")]
356 KeyExists,
357
358 #[error("unexpected data at location: {0}")]
359 UnexpectedData(Location<F>),
360
361 #[error("location out of bounds: {0} >= {1}")]
362 LocationOutOfBounds(Location<F>, Location<F>),
363
364 #[error("location {1} out of order after {0}")]
366 LocationOutOfOrder(Location<F>, Location<F>),
367
368 #[error("prune location {0} beyond minimum required location {1}")]
369 PruneBeyondMinRequired(Location<F>, Location<F>),
370
371 #[error("stale batch: current database state does not match the batch")]
385 StaleBatch,
386
387 #[error("stale read: database is at no state of the batch's chain")]
391 StaleRead,
392
393 #[error("location below inactivity floor: {0}")]
399 BelowInactivityFloor(Location<F>),
400
401 #[error("floor regressed: batch floor {0} < prior floor {1}")]
404 FloorRegressed(Location<F>, Location<F>),
405
406 #[error("floor beyond commit location: floor {0} > commit loc {1}")]
410 FloorBeyondSize(Location<F>, Location<F>),
411
412 #[error("historical floor pruned for size: {0}")]
415 HistoricalFloorPruned(Location<F>),
416}
417
418impl<F: Family> From<crate::journal::authenticated::Error<F>> for Error<F> {
419 fn from(e: crate::journal::authenticated::Error<F>) -> Self {
420 match e {
421 crate::journal::authenticated::Error::Journal(j) => Self::Journal(j),
422 crate::journal::authenticated::Error::Merkle(m) => Self::Merkle(m),
423 }
424 }
425}
426
427pub(super) async fn build_snapshot_from_log<F, C, I, Fn>(
437 inactivity_floor_loc: crate::merkle::Location<F>,
438 reader: &C,
439 snapshot: &mut I,
440 init_buffer: NonZeroUsize,
441 cache_size: Option<NonZeroUsize>,
442 mut callback: Fn,
443) -> Result<usize, Error<F>>
444where
445 F: crate::merkle::Family,
446 C: Contiguous<Item: Operation<F>>,
447 I: Index<Value = crate::merkle::Location<F>>,
448 Fn: FnMut(bool, Option<crate::merkle::Location<F>>),
449{
450 let bounds = reader.bounds();
451 let stream = reader
454 .replay(
455 *inactivity_floor_loc..bounds.end,
456 init_buffer,
457 ReadOptions::DONT_CACHE,
458 )
459 .await?;
460 pin_mut!(stream);
461 let last_commit_loc = bounds.end.saturating_sub(1);
462
463 let mut cache = cache_size.map(Cache::<<C::Item as Operation<F>>::Key>::new);
467
468 let mut active_keys: usize = 0;
469 while let Some(batch) = stream.next().await {
470 for (loc, op) in batch? {
471 if let Some(key) = op.key() {
472 if op.is_delete() {
473 let old_loc = delete_key(snapshot, reader, key, cache.as_mut()).await?;
474 callback(false, old_loc);
475 if old_loc.is_some() {
476 active_keys -= 1;
477 }
478 } else if op.is_update() {
479 let new_loc = crate::merkle::Location::new(loc);
480 let old_loc =
481 update_key(snapshot, reader, key, new_loc, cache.as_mut()).await?;
482 callback(true, old_loc);
483 if old_loc.is_none() {
484 active_keys += 1;
485 }
486
487 if let Some(cache) = cache.as_mut() {
490 cache.put(loc, op.into_key().expect("operation without key"));
491 }
492 }
493 } else if op.has_floor().is_some() {
494 callback(loc == last_commit_loc, None);
495 }
496 }
497 }
498
499 Ok(active_keys)
500}
501
502async fn delete_key<F, I, R>(
505 snapshot: &mut I,
506 reader: &R,
507 key: &<R::Item as Operation<F>>::Key,
508 cache: Option<&mut Cache<<R::Item as Operation<F>>::Key>>,
509) -> Result<Option<Location<F>>, Error<F>>
510where
511 F: Family,
512 I: Index<Value = Location<F>>,
513 R: Contiguous,
514 R::Item: Operation<F>,
515{
516 let Some(cursor) = snapshot.get_mut(key) else {
518 return Ok(None);
519 };
520 delete_at_cursor::<F, _, _>(cursor, reader, key, cache).await
521}
522
523async fn delete_at_cursor<F, C, R>(
527 mut cursor: C,
528 reader: &R,
529 key: &<R::Item as Operation<F>>::Key,
530 mut cache: Option<&mut Cache<<R::Item as Operation<F>>::Key>>,
531) -> Result<Option<Location<F>>, Error<F>>
532where
533 F: Family,
534 C: Cursor<Value = Location<F>>,
535 R: Contiguous,
536 R::Item: Operation<F>,
537{
538 let Some(loc) = find_update_op::<F, _>(reader, &mut cursor, key, cache.as_deref_mut()).await?
540 else {
541 return Ok(None);
542 };
543
544 cursor.delete();
547 if let Some(cache) = cache {
548 cache.remove(*loc);
549 }
550
551 Ok(Some(loc))
552}
553
554async fn update_key<F, I, R>(
556 snapshot: &mut I,
557 reader: &R,
558 key: &<R::Item as Operation<F>>::Key,
559 new_loc: Location<F>,
560 cache: Option<&mut Cache<<R::Item as Operation<F>>::Key>>,
561) -> Result<Option<Location<F>>, Error<F>>
562where
563 F: Family,
564 I: Index<Value = Location<F>>,
565 R: Contiguous,
566 R::Item: Operation<F>,
567{
568 let Some(cursor) = snapshot.get_mut_or_insert(key, new_loc) else {
571 return Ok(None);
572 };
573 update_at_cursor::<F, _, _>(cursor, reader, key, new_loc, cache).await
574}
575
576async fn update_at_cursor<F, C, R>(
581 mut cursor: C,
582 reader: &R,
583 key: &<R::Item as Operation<F>>::Key,
584 new_loc: Location<F>,
585 mut cache: Option<&mut Cache<<R::Item as Operation<F>>::Key>>,
586) -> Result<Option<Location<F>>, Error<F>>
587where
588 F: Family,
589 C: Cursor<Value = Location<F>>,
590 R: Contiguous,
591 R::Item: Operation<F>,
592{
593 if let Some(loc) =
595 find_update_op::<F, _>(reader, &mut cursor, key, cache.as_deref_mut()).await?
596 {
597 assert!(new_loc > loc);
600 cursor.update(new_loc);
601 if let Some(cache) = cache {
602 cache.remove(*loc);
603 }
604 return Ok(Some(loc));
605 }
606
607 cursor.insert(new_loc);
609
610 Ok(None)
611}
612
613async fn find_update_op<F, R>(
616 reader: &R,
617 cursor: &mut impl Cursor<Value = Location<F>>,
618 key: &<R::Item as Operation<F>>::Key,
619 mut cache: Option<&mut Cache<<R::Item as Operation<F>>::Key>>,
620) -> Result<Option<Location<F>>, Error<F>>
621where
622 F: Family,
623 R: Contiguous,
624 R::Item: Operation<F>,
625{
626 while let Some(&loc) = cursor.next() {
627 let matches = if let Some(k) = cache.as_deref().and_then(|c| c.get(*loc)) {
629 *k == *key
630 } else {
631 let op = reader.read(*loc).await?;
632 let k = op.key().expect("operation without key");
633 let matches = *k == *key;
634
635 if !matches && let Some(cache) = cache.as_deref_mut() {
638 cache.put(*loc, op.into_key().expect("operation without key"));
639 }
640 matches
641 };
642 if matches {
643 return Ok(Some(loc));
644 }
645 }
646
647 Ok(None)
648}
649
650const SNAPSHOT_CHANNEL_DEPTH: usize = 4;
653
654type RoutedBatch<K> = Vec<(K, u64, bool)>;
657
658#[derive(Clone, Copy)]
660struct SnapshotRouting {
661 workers: usize,
663 partition_of: fn(&[u8]) -> usize,
665 range_size: usize,
667 init_buffer: NonZeroUsize,
669}
670
671#[cfg(not(test))]
675const SNAPSHOT_ROUTE_BATCH: usize = 4096;
676#[cfg(test)]
677const SNAPSHOT_ROUTE_BATCH: usize = 3;
678
679#[cfg(not(test))]
683const PREFETCH_DISTANCE: usize = 16;
684#[cfg(test)]
685const PREFETCH_DISTANCE: usize = 1;
686
687#[cfg(not(test))]
693const SNAPSHOT_DECODE_CHUNK: u64 = 1 << 17;
694#[cfg(test)]
695const SNAPSHOT_DECODE_CHUNK: u64 = 64;
696
697async fn route_snapshot<F, C, Fut>(
703 log: &C,
704 range: Range<u64>,
705 routing: SnapshotRouting,
706 mut send: impl FnMut(usize, RoutedBatch<<C::Item as Operation<F>>::Key>) -> Fut + Send,
707) -> Result<(), Error<F>>
708where
709 F: Family,
710 C: Contiguous<Item: Operation<F>>,
711 Fut: Future<Output = bool> + Send,
712{
713 let stream = log
716 .replay(range, routing.init_buffer, ReadOptions::DONT_CACHE)
717 .await?;
718 pin_mut!(stream);
719 let mut batches: Vec<RoutedBatch<_>> = (0..routing.workers)
720 .map(|_| Vec::with_capacity(SNAPSHOT_ROUTE_BATCH))
721 .collect();
722 while let Some(batch) = stream.next().await {
723 for (loc, op) in batch? {
724 let is_delete = op.is_delete();
725 let Some(key) = op.into_key() else { continue };
726 let w = (routing.partition_of)(key.as_ref()) / routing.range_size;
727 batches[w].push((key, loc, is_delete));
728 if batches[w].len() >= SNAPSHOT_ROUTE_BATCH {
729 let routed =
730 std::mem::replace(&mut batches[w], Vec::with_capacity(SNAPSHOT_ROUTE_BATCH));
731 if !send(w, routed).await {
732 return Ok(());
733 }
734 }
735 }
736 }
737
738 for (w, batch) in batches.into_iter().enumerate() {
740 if !batch.is_empty() && !send(w, batch).await {
741 break;
742 }
743 }
744 Ok(())
745}
746
747async fn build_snapshot_worker<F, C, R>(
753 log: Arc<C>,
754 mut rx: mpsc::Receiver<RoutedBatch<<C::Item as Operation<F>>::Key>>,
755 mut index: R,
756 activity: Range<u64>,
757 active: Arc<Atomic>,
758 cache_size: Option<NonZeroUsize>,
759) -> Result<(R, usize), Error<F>>
760where
761 F: Family,
762 C: Contiguous<Item: Operation<F>>,
763 R: PartitionRange<Value = Location<F>>,
764{
765 let mut cache = cache_size.map(Cache::<<C::Item as Operation<F>>::Key>::new);
766
767 while let Some(batch) = rx.recv().await {
780 let mut ops = batch.into_iter();
781 let mut headers = Lookahead::new(2 * PREFETCH_DISTANCE);
782 let mut entries = Lookahead::new(PREFETCH_DISTANCE);
783 loop {
784 for (key, _, _) in headers.advance(ops.as_slice()) {
785 index.prefetch_header(key.as_ref());
786 }
787 for (key, loc, is_delete) in entries.advance(ops.as_slice()) {
788 index.prefetch_entries(key.as_ref());
789 if !is_delete && let Some(cache) = &cache {
790 cache.prefetch(*loc);
791 }
792 }
793 let Some((key, loc, is_delete)) = ops.next() else {
794 break;
795 };
796
797 if is_delete {
798 if let Some(cursor) = index.get_mut(&key) {
799 delete_at_cursor::<F, _, _>(cursor, &*log, &key, cache.as_mut()).await?;
800 }
801 } else {
802 let new_loc = Location::new(loc);
803 if let Some(cursor) = index.get_mut_or_insert(&key, new_loc) {
804 update_at_cursor::<F, _, _>(cursor, &*log, &key, new_loc, cache.as_mut())
805 .await?;
806 }
807
808 if let Some(cache) = cache.as_mut() {
811 cache.put(loc, key);
812 }
813 }
814 }
815 }
816
817 let mut active_keys = 0;
821 index.for_each_value(|loc| {
822 active.set(**loc - activity.start);
823 active_keys += 1;
824 });
825 Ok((index, active_keys))
826}
827
828async fn build_snapshot_serial<F, C, I>(
832 inactivity_floor_loc: Location<F>,
833 reader: &C,
834 snapshot: &mut I,
835 init_buffer: NonZeroUsize,
836 cache_size: Option<NonZeroUsize>,
837) -> Result<(usize, BitMap), Error<F>>
838where
839 F: Family,
840 C: Contiguous<Item: Operation<F>>,
841 I: Index<Value = Location<F>>,
842{
843 let mut activity = BitMap::new();
846 let floor = *inactivity_floor_loc;
847 let active_keys = build_snapshot_from_log(
848 inactivity_floor_loc,
849 reader,
850 snapshot,
851 init_buffer,
852 cache_size,
853 |is_active, old_loc| {
854 activity.push(is_active);
855 if let Some(loc) = old_loc {
856 activity.set(*loc - floor, false);
857 }
858 },
859 )
860 .await?;
861 Ok((active_keys, activity))
862}
863
864async fn build_snapshot_parallel<F, E, C, I>(
869 snapshot: &mut I,
870 context: E,
871 inactivity_floor_loc: Location<F>,
872 log: &Arc<C>,
873 init_concurrency: NonZeroUsize,
874 init_buffer: NonZeroUsize,
875 cache_size: Option<NonZeroUsize>,
876) -> Result<(usize, BitMap), Error<F>>
877where
878 F: Family,
879 E: Spawner,
880 C: Contiguous<Item: Operation<F>> + 'static,
881 I: Partitioned + Index<Value = Location<F>>,
882{
883 let count = snapshot.partition_count();
884
885 let concurrency = init_concurrency.get();
890
891 let decoders = if concurrency <= 3 {
895 0
896 } else {
897 let share = concurrency / 5 * 2 + concurrency % 5 * 2 / 5;
899 share.max(2).min(concurrency / 2)
900 };
901 let workers = if decoders == 0 {
902 concurrency.saturating_sub(1).min(count)
903 } else {
904 (concurrency - decoders).min(count)
905 };
906
907 if workers == 0 {
909 return build_snapshot_serial(
910 inactivity_floor_loc,
911 &**log,
912 snapshot,
913 init_buffer,
914 cache_size,
915 )
916 .await;
917 }
918
919 let floor = *inactivity_floor_loc;
920 let range_size = count.div_ceil(workers);
921
922 let workers = count.div_ceil(range_size);
926 let per_worker_cache = cache_size.and_then(|n| NonZeroUsize::new(n.get() / workers));
927 let end = log.bounds().end;
928
929 let active = Arc::new(Atomic::zeroes(end - floor));
931
932 let mut senders = Vec::with_capacity(workers);
934 let mut handles = Vec::with_capacity(workers);
935 for w in 0..workers {
936 let (tx, rx) = mpsc::channel(SNAPSHOT_CHANNEL_DEPTH);
937 senders.push(tx);
938 let log = log.clone();
939
940 let lo = w * range_size;
943 let range_len = range_size.min(count - lo);
944 let worker_index = snapshot.new_range(lo, range_len);
945 let active = active.clone();
946 let handle = context
947 .child("snapshot_worker")
948 .with_attribute("worker", w)
949 .dedicated()
950 .spawn(move |_| {
951 build_snapshot_worker::<F, C, I::Range>(
952 log,
953 rx,
954 worker_index,
955 floor..end,
956 active,
957 per_worker_cache,
958 )
959 });
960 handles.push(handle.abort_on_drop());
961 }
962
963 let mut pending = VecDeque::new();
972 let routing = SnapshotRouting {
973 workers,
974 partition_of: I::partition_of,
975 range_size,
976 init_buffer,
977 };
978 let routing_result: Result<(), Error<F>> = async {
979 if decoders == 0 {
982 let senders = &senders;
983 return route_snapshot::<F, C, _>(&**log, floor..end, routing, |w, batch| async move {
984 senders[w].send(batch).await.is_ok()
985 })
986 .await;
987 }
988
989 let chunk_capacity = SNAPSHOT_DECODE_CHUNK as usize / SNAPSHOT_ROUTE_BATCH + workers;
992 let mut starts = (floor..end)
993 .step_by(usize::try_from(SNAPSHOT_DECODE_CHUNK).expect("chunk size fits usize"));
994 let mut spawn_next = |pending: &mut VecDeque<_>| {
995 let Some(start) = starts.next() else {
996 return false;
997 };
998 let log = log.clone();
999 let len = SNAPSHOT_DECODE_CHUNK.min(end - start);
1000 let (tx, rx) = mpsc::channel(chunk_capacity);
1001 let handle = context
1002 .child("snapshot_decoder")
1003 .dedicated()
1004 .spawn(move |_| async move {
1005 let tx = &tx;
1006 route_snapshot::<F, C, _>(
1007 &*log,
1008 start..start + len,
1009 routing,
1010 |w, batch| async move { tx.send((w, batch)).await.is_ok() },
1011 )
1012 .await
1013 });
1014 pending.push_back((rx, handle.abort_on_drop()));
1015 true
1016 };
1017
1018 for _ in 0..decoders {
1020 if !spawn_next(&mut pending) {
1021 break;
1022 }
1023 }
1024
1025 while let Some((rx, _)) = pending.front_mut() {
1028 while let Some((w, batch)) = rx.recv().await {
1029 if senders[w].send(batch).await.is_err() {
1030 return Ok(());
1031 }
1032 }
1033 let (_, decoder) = pending.pop_front().expect("front exists");
1034 decoder.join().await??;
1035 spawn_next(&mut pending);
1036 }
1037 Ok(())
1038 }
1039 .await;
1040
1041 drop(senders);
1043
1044 while let Some((rx, decoder)) = pending.pop_front() {
1046 drop(rx);
1047 decoder.abort().await;
1048 }
1049
1050 let joined = AbortOnDrop::join_all::<Error<F>>(handles).await;
1052 routing_result?;
1053 let joined = joined?;
1054
1055 let mut total_items = 0;
1057 for (worker_index, worker_keys) in joined {
1058 snapshot.install_range(worker_index);
1059 total_items += worker_keys;
1060 }
1061
1062 let mut active = Arc::into_inner(active)
1064 .expect("workers were joined")
1065 .into_bitmap();
1066
1067 if let Some(last_commit) = end.checked_sub(1)
1070 && last_commit >= floor
1071 {
1072 active.set(last_commit - floor, true);
1073 }
1074
1075 Ok((total_items, active))
1076}
1077
1078pub trait SnapshotBuild<F: Family>:
1087 sealed::SnapshotBuildSealed + Index<Value = Location<F>> + Sized + 'static
1088{
1089 type Concurrency: Copy + Send + Sync + 'static;
1092
1093 fn build_snapshot<E, C>(
1100 &mut self,
1101 _context: E,
1102 inactivity_floor_loc: Location<F>,
1103 log: &Arc<C>,
1104 _init_concurrency: Self::Concurrency,
1105 init_buffer: NonZeroUsize,
1106 cache_size: Option<NonZeroUsize>,
1107 ) -> impl Future<Output = Result<(usize, BitMap), Error<F>>> + Send
1108 where
1109 E: Spawner,
1110 C: Contiguous<Item: Operation<F>> + 'static,
1111 {
1112 async move {
1113 build_snapshot_serial(inactivity_floor_loc, &**log, self, init_buffer, cache_size).await
1114 }
1115 }
1116}
1117
1118mod sealed {
1119 use crate::translator::Translator;
1120
1121 pub trait SnapshotBuildSealed {}
1122 impl<T: Translator, V: Send + Sync> SnapshotBuildSealed for crate::index::unordered::Index<T, V> {}
1123 impl<T: Translator, V: Send + Sync> SnapshotBuildSealed for crate::index::ordered::Index<T, V> {}
1124 impl<T: Translator, V: Send + Sync, const P: usize> SnapshotBuildSealed
1125 for crate::index::partitioned::unordered::Index<T, V, P>
1126 {
1127 }
1128 impl<T: Translator, V: Send + Sync, const P: usize> SnapshotBuildSealed
1129 for crate::index::partitioned::ordered::Index<T, V, P>
1130 {
1131 }
1132}
1133
1134impl<F: Family, T: Translator> SnapshotBuild<F> for crate::index::unordered::Index<T, Location<F>> {
1135 type Concurrency = ();
1136}
1137impl<F: Family, T: Translator> SnapshotBuild<F> for crate::index::ordered::Index<T, Location<F>> {
1138 type Concurrency = ();
1139}
1140
1141impl<F: Family, T: Translator, const P: usize> SnapshotBuild<F>
1142 for crate::index::partitioned::unordered::Index<T, Location<F>, P>
1143{
1144 type Concurrency = NonZeroUsize;
1145
1146 async fn build_snapshot<E, C>(
1147 &mut self,
1148 context: E,
1149 inactivity_floor_loc: Location<F>,
1150 log: &Arc<C>,
1151 init_concurrency: NonZeroUsize,
1152 init_buffer: NonZeroUsize,
1153 cache_size: Option<NonZeroUsize>,
1154 ) -> Result<(usize, BitMap), Error<F>>
1155 where
1156 E: Spawner,
1157 C: Contiguous<Item: Operation<F>> + 'static,
1158 {
1159 build_snapshot_parallel(
1160 self,
1161 context,
1162 inactivity_floor_loc,
1163 log,
1164 init_concurrency,
1165 init_buffer,
1166 cache_size,
1167 )
1168 .await
1169 }
1170}
1171
1172impl<F: Family, T: Translator, const P: usize> SnapshotBuild<F>
1173 for crate::index::partitioned::ordered::Index<T, Location<F>, P>
1174{
1175 type Concurrency = NonZeroUsize;
1176
1177 async fn build_snapshot<E, C>(
1178 &mut self,
1179 context: E,
1180 inactivity_floor_loc: Location<F>,
1181 log: &Arc<C>,
1182 init_concurrency: NonZeroUsize,
1183 init_buffer: NonZeroUsize,
1184 cache_size: Option<NonZeroUsize>,
1185 ) -> Result<(usize, BitMap), Error<F>>
1186 where
1187 E: Spawner,
1188 C: Contiguous<Item: Operation<F>> + 'static,
1189 {
1190 build_snapshot_parallel(
1191 self,
1192 context,
1193 inactivity_floor_loc,
1194 log,
1195 init_concurrency,
1196 init_buffer,
1197 cache_size,
1198 )
1199 .await
1200 }
1201}
1202
1203fn update_known_loc<F: Family, I: Index<Value = Location<F>>>(
1210 snapshot: &mut I,
1211 key: &[u8],
1212 old_loc: Location<F>,
1213 new_loc: Location<F>,
1214) {
1215 let mut cursor = snapshot.get_mut(key).expect("key should be known to exist");
1216 assert!(
1217 cursor.find(|&loc| *loc == old_loc),
1218 "known key with given old_loc should have been found"
1219 );
1220 cursor.update(new_loc);
1221}
1222
1223fn delete_known_loc<F: Family, I: Index<Value = Location<F>>>(
1230 snapshot: &mut I,
1231 key: &[u8],
1232 old_loc: Location<F>,
1233) {
1234 let mut cursor = snapshot.get_mut(key).expect("key should be known to exist");
1235 assert!(
1236 cursor.find(|&loc| *loc == old_loc),
1237 "known key with given old_loc should have been found"
1238 );
1239 cursor.delete();
1240}