1use crate::translator::Translator;
170use commonware_runtime::buffer::paged::CacheRef;
171use std::num::{NonZeroU64, NonZeroUsize};
172
173mod storage;
174pub use storage::Archive;
175
176#[derive(Clone)]
178pub struct Config<T: Translator, C> {
179 pub translator: T,
184
185 pub key_partition: String,
187
188 pub key_page_cache: CacheRef,
190
191 pub value_partition: String,
193
194 pub compression: Option<u8>,
196
197 pub codec_config: C,
199
200 pub items_per_section: NonZeroU64,
202
203 pub key_write_buffer: NonZeroUsize,
206
207 pub value_write_buffer: NonZeroUsize,
210
211 pub replay_buffer: NonZeroUsize,
213}
214
215#[cfg(test)]
216mod tests {
217 use super::*;
218 use crate::{
219 archive::{Archive as _, Error, Identifier, MultiArchive as _},
220 journal::Error as JournalError,
221 translator::{FourCap, TwoCap},
222 };
223 use commonware_codec::{DecodeExt, Error as CodecError};
224 use commonware_macros::{test_group, test_traced};
225 use commonware_runtime::{
226 deterministic,
227 mocks::{
228 fail_pending_syncs, release_next_pending_syncs, release_pending_syncs,
229 DelayedSyncContext, PendingSyncs,
230 },
231 telemetry::metrics::has_metric_value,
232 BufferPooler, Error as RError, Metrics as _, Runner, Spawner as _, Supervisor as _,
233 };
234 use commonware_utils::{sequence::FixedBytes, NZUsize, NZU16, NZU64};
235 use rand::RngExt as _;
236 use std::{
237 collections::BTreeMap,
238 num::{NonZeroU16, NonZeroU64},
239 sync::{
240 atomic::{AtomicUsize, Ordering},
241 Arc,
242 },
243 };
244
245 fn test_key(key: &str) -> FixedBytes<64> {
246 let mut buf = [0u8; 64];
247 let key = key.as_bytes();
248 assert!(key.len() <= buf.len());
249 buf[..key.len()].copy_from_slice(key);
250 FixedBytes::decode(buf.as_ref()).unwrap()
251 }
252
253 const DEFAULT_ITEMS_PER_SECTION: u64 = 65536;
254 const DEFAULT_WRITE_BUFFER: usize = 1024;
255 const DEFAULT_REPLAY_BUFFER: usize = 4096;
256 const PAGE_SIZE: NonZeroU16 = NZU16!(1024);
257 const PAGE_CACHE_SIZE: NonZeroUsize = NZUsize!(10);
258
259 fn test_config<E: BufferPooler>(
260 context: &E,
261 items_per_section: NonZeroU64,
262 ) -> Config<FourCap, ()> {
263 Config {
264 translator: FourCap,
265 key_partition: "test-index".into(),
266 key_page_cache: CacheRef::from_pooler(context, PAGE_SIZE, PAGE_CACHE_SIZE),
267 value_partition: "test-value".into(),
268 codec_config: (),
269 compression: None,
270 key_write_buffer: NZUsize!(DEFAULT_WRITE_BUFFER),
271 value_write_buffer: NZUsize!(DEFAULT_WRITE_BUFFER),
272 replay_buffer: NZUsize!(DEFAULT_REPLAY_BUFFER),
273 items_per_section,
274 }
275 }
276
277 #[test_traced]
278 fn test_put_after_start_sync_is_accepted_before_handle_completes() {
279 let executor = deterministic::Runner::default();
280 let (_, checkpoint) = executor.start_and_recover(|context| async move {
281 let pending = PendingSyncs::default();
282 let context = DelayedSyncContext {
283 inner: context,
284 pending: pending.clone(),
285 };
286 let cfg = test_config(&context, NZU64!(DEFAULT_ITEMS_PER_SECTION));
287 let mut archive = Archive::init(context.child("storage"), cfg)
288 .await
289 .expect("Failed to initialize archive");
290
291 let handle = archive
292 .put_start_sync(1, test_key("aaa"), 10)
293 .await
294 .expect("Failed to start sync");
295 let pending_after_start = pending.lock().len();
296 assert!(
297 pending_after_start > 0,
298 "put_start_sync should return while the sync handle is still pending"
299 );
300
301 archive
302 .put(2, test_key("bbb"), 20)
303 .await
304 .expect("archive should remain usable before sync completion");
305 assert_eq!(
306 pending.lock().len(),
307 pending_after_start,
308 "put should not issue a new storage sync while accepting later data"
309 );
310 assert_eq!(archive.get(Identifier::Index(1)).await.unwrap(), Some(10));
311
312 release_pending_syncs(&pending);
313 handle.await.expect("sync handle should complete");
314
315 let follow_up = archive
316 .start_sync()
317 .await
318 .expect("Failed to start next sync");
319 assert!(
320 !pending.lock().is_empty(),
321 "the later put must remain pending for a future sync"
322 );
323 release_pending_syncs(&pending);
324 follow_up.await.expect("follow-up sync should complete");
325 });
326
327 deterministic::Runner::from(checkpoint).start(|context| async move {
328 let cfg = test_config(&context, NZU64!(DEFAULT_ITEMS_PER_SECTION));
329 let archive = Archive::<_, _, FixedBytes<64>, i32>::init(context.child("reopen"), cfg)
330 .await
331 .expect("Failed to reopen archive");
332
333 assert_eq!(archive.get(Identifier::Index(1)).await.unwrap(), Some(10));
334 assert_eq!(archive.get(Identifier::Index(2)).await.unwrap(), Some(20));
335 });
336 }
337
338 #[test_traced]
339 fn test_duplicate_put_start_sync_observes_in_flight_sync() {
340 let executor = deterministic::Runner::default();
341 executor.start(|context| async move {
342 let pending = PendingSyncs::default();
343 let context = DelayedSyncContext {
344 inner: context,
345 pending: pending.clone(),
346 };
347 let cfg = test_config(&context, NZU64!(DEFAULT_ITEMS_PER_SECTION));
348 let mut archive = Archive::init(context.child("storage"), cfg)
349 .await
350 .expect("Failed to initialize archive");
351
352 let first = archive
353 .put_start_sync(1, test_key("aaa"), 10)
354 .await
355 .expect("Failed to start sync");
356 assert_eq!(pending.lock().len(), 2);
357
358 let second = archive
359 .put_start_sync(1, test_key("duplicate"), 99)
360 .await
361 .expect("Failed to start duplicate sync");
362 assert_eq!(
363 pending.lock().len(),
364 2,
365 "duplicate put_start_sync must not issue a new storage sync"
366 );
367
368 let started = Arc::new(AtomicUsize::new(0));
369 let completed = Arc::new(AtomicUsize::new(0));
370 let started_clone = started.clone();
371 let completed_clone = completed.clone();
372 let waiter = context.inner.child("duplicate").spawn(|_| async move {
373 started_clone.fetch_add(1, Ordering::Relaxed);
374 second.await.expect("duplicate sync handle should complete");
375 completed_clone.fetch_add(1, Ordering::Relaxed);
376 });
377
378 while started.load(Ordering::Relaxed) == 0 {
379 commonware_runtime::reschedule().await;
380 }
381 commonware_runtime::reschedule().await;
382 assert_eq!(
383 completed.load(Ordering::Relaxed),
384 0,
385 "duplicate put_start_sync must observe the original in-flight sync"
386 );
387
388 release_pending_syncs(&pending);
389 first.await.expect("first sync handle should complete");
390 while completed.load(Ordering::Relaxed) == 0 {
391 commonware_runtime::reschedule().await;
392 }
393 waiter.await.expect("duplicate waiter failed");
394
395 assert_eq!(archive.get(Identifier::Index(1)).await.unwrap(), Some(10));
396 });
397 }
398
399 #[test_traced]
400 fn test_overlapping_put_start_sync_waits_for_in_flight_sync() {
401 let executor = deterministic::Runner::default();
402 executor.start(|context| async move {
403 let pending = PendingSyncs::default();
404 let context = DelayedSyncContext {
405 inner: context,
406 pending: pending.clone(),
407 };
408 let cfg = test_config(&context, NZU64!(DEFAULT_ITEMS_PER_SECTION));
409 let mut archive = Archive::init(context.child("storage"), cfg)
410 .await
411 .expect("Failed to initialize archive");
412
413 let first = archive
414 .put_start_sync(1, test_key("aaa"), 10)
415 .await
416 .expect("Failed to start sync");
417 let pending_after_first = pending.lock().len();
418 assert!(pending_after_first > 0);
419
420 let started = Arc::new(AtomicUsize::new(0));
421 let completed = Arc::new(AtomicUsize::new(0));
422 let started_clone = started.clone();
423 let completed_clone = completed.clone();
424 let waiter = context.inner.child("second").spawn(|_| async move {
425 started_clone.fetch_add(1, Ordering::Relaxed);
426 let second = archive
427 .put_start_sync(2, test_key("bbb"), 20)
428 .await
429 .expect("Failed to start second sync");
430 completed_clone.fetch_add(1, Ordering::Relaxed);
431 (archive, second)
432 });
433
434 while started.load(Ordering::Relaxed) == 0 {
435 commonware_runtime::reschedule().await;
436 }
437 commonware_runtime::reschedule().await;
438 assert_eq!(completed.load(Ordering::Relaxed), 0);
439 assert_eq!(
440 pending.lock().len(),
441 pending_after_first,
442 "second put_start_sync must not start new syncs before the first completes"
443 );
444
445 release_pending_syncs(&pending);
446 first.await.expect("first sync handle should complete");
447 while completed.load(Ordering::Relaxed) == 0 {
448 commonware_runtime::reschedule().await;
449 }
450 let (archive, second) = waiter.await.expect("second put task failed");
451 assert!(!pending.lock().is_empty());
452 release_pending_syncs(&pending);
453 second.await.expect("second sync handle should complete");
454
455 assert_eq!(archive.get(Identifier::Index(1)).await.unwrap(), Some(10));
456 assert_eq!(archive.get(Identifier::Index(2)).await.unwrap(), Some(20));
457 });
458 }
459
460 #[test_traced]
461 fn test_sync_after_put_start_sync_waits_for_in_flight_sync() {
462 let executor = deterministic::Runner::default();
463 executor.start(|context| async move {
464 let pending = PendingSyncs::default();
465 let context = DelayedSyncContext {
466 inner: context,
467 pending: pending.clone(),
468 };
469 let cfg = test_config(&context, NZU64!(DEFAULT_ITEMS_PER_SECTION));
470 let mut archive = Archive::init(context.child("storage"), cfg)
471 .await
472 .expect("Failed to initialize archive");
473
474 let first = archive
475 .put_start_sync(1, test_key("aaa"), 10)
476 .await
477 .expect("Failed to start sync");
478 assert!(!pending.lock().is_empty());
479
480 let started = Arc::new(AtomicUsize::new(0));
481 let completed = Arc::new(AtomicUsize::new(0));
482 let started_clone = started.clone();
483 let completed_clone = completed.clone();
484 let waiter = context.inner.child("sync").spawn(|_| async move {
485 started_clone.fetch_add(1, Ordering::Relaxed);
486 archive.sync().await.expect("sync should complete");
487 completed_clone.fetch_add(1, Ordering::Relaxed);
488 archive
489 });
490
491 while started.load(Ordering::Relaxed) == 0 {
492 commonware_runtime::reschedule().await;
493 }
494 commonware_runtime::reschedule().await;
495 assert_eq!(
496 completed.load(Ordering::Relaxed),
497 0,
498 "shutdown sync must wait for the in-flight put_start_sync handle"
499 );
500
501 release_pending_syncs(&pending);
502 first.await.expect("first sync handle should complete");
503 while completed.load(Ordering::Relaxed) == 0 {
504 commonware_runtime::reschedule().await;
505 }
506 let archive = waiter.await.expect("sync task failed");
507 assert_eq!(archive.get(Identifier::Index(1)).await.unwrap(), Some(10));
508 });
509 }
510
511 #[test_traced]
512 fn test_destroy_after_put_start_sync_waits_for_in_flight_sync() {
513 let executor = deterministic::Runner::default();
514 executor.start(|context| async move {
515 let pending = PendingSyncs::default();
516 let context = DelayedSyncContext {
517 inner: context,
518 pending: pending.clone(),
519 };
520 let cfg = test_config(&context, NZU64!(DEFAULT_ITEMS_PER_SECTION));
521 let mut archive =
522 Archive::<_, _, FixedBytes<64>, i32>::init(context.child("storage"), cfg)
523 .await
524 .expect("Failed to initialize archive");
525
526 let first = archive
527 .put_start_sync(1, test_key("aaa"), 10)
528 .await
529 .expect("Failed to start sync");
530 assert!(!pending.lock().is_empty());
531
532 let started = Arc::new(AtomicUsize::new(0));
533 let completed = Arc::new(AtomicUsize::new(0));
534 let started_clone = started.clone();
535 let completed_clone = completed.clone();
536 let waiter = context.inner.child("destroy").spawn(|_| async move {
537 started_clone.fetch_add(1, Ordering::Relaxed);
538 archive.destroy().await.expect("destroy should complete");
539 completed_clone.fetch_add(1, Ordering::Relaxed);
540 });
541
542 while started.load(Ordering::Relaxed) == 0 {
543 commonware_runtime::reschedule().await;
544 }
545 commonware_runtime::reschedule().await;
546 assert_eq!(
547 completed.load(Ordering::Relaxed),
548 0,
549 "destroy must wait for the in-flight put_start_sync handle"
550 );
551
552 release_pending_syncs(&pending);
553 first.await.expect("first sync handle should complete");
554 while completed.load(Ordering::Relaxed) == 0 {
555 commonware_runtime::reschedule().await;
556 }
557 waiter.await.expect("destroy task failed");
558 });
559 }
560
561 #[test_traced]
562 fn test_prune_after_put_start_sync_waits_for_in_flight_sync() {
563 let executor = deterministic::Runner::default();
564 executor.start(|context| async move {
565 let pending = PendingSyncs::default();
566 let context = DelayedSyncContext {
567 inner: context,
568 pending: pending.clone(),
569 };
570 let cfg = test_config(&context, NZU64!(1));
571 let mut archive =
572 Archive::<_, _, FixedBytes<64>, i32>::init(context.child("storage"), cfg)
573 .await
574 .expect("Failed to initialize archive");
575
576 let first = archive
577 .put_start_sync(1, test_key("aaa"), 10)
578 .await
579 .expect("Failed to start sync");
580 assert!(!pending.lock().is_empty());
581
582 let started = Arc::new(AtomicUsize::new(0));
583 let completed = Arc::new(AtomicUsize::new(0));
584 let started_clone = started.clone();
585 let completed_clone = completed.clone();
586 let waiter = context.inner.child("prune").spawn(|_| async move {
587 started_clone.fetch_add(1, Ordering::Relaxed);
588 archive.prune(2).await.expect("prune should complete");
589 completed_clone.fetch_add(1, Ordering::Relaxed);
590 archive
591 });
592
593 while started.load(Ordering::Relaxed) == 0 {
594 commonware_runtime::reschedule().await;
595 }
596 commonware_runtime::reschedule().await;
597 assert_eq!(
598 completed.load(Ordering::Relaxed),
599 0,
600 "prune must wait for in-flight syncs on pruned sections"
601 );
602
603 release_pending_syncs(&pending);
604 first
605 .await
606 .expect("sync handle should complete despite pruning");
607 while completed.load(Ordering::Relaxed) == 0 {
608 commonware_runtime::reschedule().await;
609 }
610 let archive = waiter.await.expect("prune task failed");
611 assert_eq!(archive.get(Identifier::Index(1)).await.unwrap(), None);
612 });
613 }
614
615 #[test_traced]
616 fn test_prune_surfaces_failed_in_flight_sync() {
617 let executor = deterministic::Runner::default();
618 executor.start(|context| async move {
619 let pending = PendingSyncs::default();
620 let context = DelayedSyncContext {
621 inner: context,
622 pending: pending.clone(),
623 };
624 let cfg = test_config(&context, NZU64!(1));
625 let mut archive =
626 Archive::<_, _, FixedBytes<64>, i32>::init(context.child("storage"), cfg)
627 .await
628 .expect("Failed to initialize archive");
629
630 let first = archive
631 .put_start_sync(1, test_key("aaa"), 10)
632 .await
633 .expect("Failed to start sync");
634 fail_pending_syncs(&pending);
635
636 let err = archive
637 .prune(2)
638 .await
639 .expect_err("prune must surface a failed in-flight sync");
640 assert!(matches!(
641 err,
642 Error::Journal(JournalError::Runtime(RError::Io(_)))
643 ));
644
645 let err = first.await.expect_err("first sync handle should fail");
646 assert!(matches!(err, RError::Io(_)));
647 });
648 }
649
650 #[test_traced]
651 fn test_put_start_sync_after_prune_drops_pruned_sync_requests() {
652 let executor = deterministic::Runner::default();
653 executor.start(|context| async move {
654 let pending = PendingSyncs::default();
655 let context = DelayedSyncContext {
656 inner: context,
657 pending: pending.clone(),
658 };
659 let cfg = test_config(&context, NZU64!(1));
660 let mut archive =
661 Archive::<_, _, FixedBytes<64>, i32>::init(context.child("storage"), cfg)
662 .await
663 .expect("Failed to initialize archive");
664
665 let first = archive
666 .put_start_sync(1, test_key("aaa"), 10)
667 .await
668 .expect("Failed to start sync");
669 release_pending_syncs(&pending);
670 first.await.expect("first sync handle should complete");
671
672 archive.prune(2).await.expect("Failed to prune");
673
674 let second = archive
677 .put_start_sync(2, test_key("bbb"), 20)
678 .await
679 .expect("put_start_sync after prune should succeed");
680 release_pending_syncs(&pending);
681 second.await.expect("second sync handle should complete");
682 archive
683 .sync()
684 .await
685 .expect("sync after prune should succeed");
686
687 assert_eq!(archive.get(Identifier::Index(1)).await.unwrap(), None);
688 assert_eq!(archive.get(Identifier::Index(2)).await.unwrap(), Some(20));
689 });
690 }
691
692 #[test_traced]
693 fn test_overlapping_put_start_sync_restarts_after_all_handles_complete() {
694 let executor = deterministic::Runner::default();
695 let (_, checkpoint) = executor.start_and_recover(|context| async move {
696 let pending = PendingSyncs::default();
697 let context = DelayedSyncContext {
698 inner: context,
699 pending: pending.clone(),
700 };
701 let cfg = test_config(&context, NZU64!(1));
702 let mut archive =
703 Archive::<_, _, FixedBytes<64>, i32>::init(context.child("storage"), cfg)
704 .await
705 .expect("Failed to initialize archive");
706
707 let first = archive
708 .put_start_sync(1, test_key("aaa"), 10)
709 .await
710 .expect("Failed to start first sync");
711 assert_eq!(pending.lock().len(), 2);
712
713 let second = archive
714 .put_start_sync(2, test_key("bbb"), 20)
715 .await
716 .expect("Failed to start second sync");
717 assert_eq!(
718 pending.lock().len(),
719 4,
720 "different sections should be able to have independent in-flight syncs"
721 );
722
723 release_pending_syncs(&pending);
724 first.await.expect("first sync handle should complete");
725 second.await.expect("second sync handle should complete");
726 });
727
728 deterministic::Runner::from(checkpoint).start(|context| async move {
729 let cfg = test_config(&context, NZU64!(1));
730 let archive = Archive::<_, _, FixedBytes<64>, i32>::init(context.child("reopen"), cfg)
731 .await
732 .expect("Failed to reopen archive");
733
734 assert_eq!(archive.get(Identifier::Index(1)).await.unwrap(), Some(10));
735 assert_eq!(archive.get(Identifier::Index(2)).await.unwrap(), Some(20));
736 });
737 }
738
739 #[test_traced]
740 fn test_overlapping_put_start_sync_restarts_only_completed_handles() {
741 let executor = deterministic::Runner::default();
742 let (_, checkpoint) = executor.start_and_recover(|context| async move {
743 let pending = PendingSyncs::default();
744 let context = DelayedSyncContext {
745 inner: context,
746 pending: pending.clone(),
747 };
748 let cfg = test_config(&context, NZU64!(1));
749 let mut archive =
750 Archive::<_, _, FixedBytes<64>, i32>::init(context.child("storage"), cfg)
751 .await
752 .expect("Failed to initialize archive");
753
754 let first = archive
755 .put_start_sync(1, test_key("aaa"), 10)
756 .await
757 .expect("Failed to start first sync");
758 let second = archive
759 .put_start_sync(2, test_key("bbb"), 20)
760 .await
761 .expect("Failed to start second sync");
762 assert_eq!(pending.lock().len(), 4);
763
764 release_next_pending_syncs(&pending, 2);
765 first.await.expect("first sync handle should complete");
766
767 drop(second);
768 drop(archive);
769 });
770
771 deterministic::Runner::from(checkpoint).start(|context| async move {
772 let cfg = test_config(&context, NZU64!(1));
773 let archive = Archive::<_, _, FixedBytes<64>, i32>::init(context.child("reopen"), cfg)
774 .await
775 .expect("Failed to reopen archive");
776
777 assert_eq!(archive.get(Identifier::Index(1)).await.unwrap(), Some(10));
778 assert_eq!(archive.get(Identifier::Index(2)).await.unwrap(), None);
779 });
780 }
781
782 #[test_traced]
783 fn test_failed_start_sync_is_returned_by_next_start_sync_handle() {
784 let executor = deterministic::Runner::default();
785 executor.start(|context| async move {
786 let pending = PendingSyncs::default();
787 let context = DelayedSyncContext {
788 inner: context,
789 pending: pending.clone(),
790 };
791 let cfg = test_config(&context, NZU64!(DEFAULT_ITEMS_PER_SECTION));
792 let mut archive =
793 Archive::<_, _, FixedBytes<64>, i32>::init(context.child("storage"), cfg)
794 .await
795 .expect("Failed to initialize archive");
796
797 let first = archive
798 .put_start_sync(1, test_key("aaa"), 10)
799 .await
800 .expect("Failed to start sync");
801 assert_eq!(pending.lock().len(), 2);
802 fail_pending_syncs(&pending);
803
804 archive
805 .put(2, test_key("bbb"), 20)
806 .await
807 .expect("write should be accepted before observing the failed sync");
808
809 let second = archive
810 .start_sync()
811 .await
812 .expect("start_sync should return a handle for the failed sync");
813 let err = second
814 .await
815 .expect_err("next start_sync handle should observe failed in-flight sync");
816 assert!(matches!(err, RError::Io(_)));
817
818 let err = first.await.expect_err("first sync handle should fail");
819 assert!(matches!(err, RError::Io(_)));
820 });
821 }
822
823 #[test_traced]
824 fn test_archive_compression_then_none() {
825 let executor = deterministic::Runner::default();
827 executor.start(|context| async move {
828 let cfg = Config {
830 translator: FourCap,
831 key_partition: "test-index".into(),
832 key_page_cache: CacheRef::from_pooler(&context, PAGE_SIZE, PAGE_CACHE_SIZE),
833 value_partition: "test-value".into(),
834 codec_config: (),
835 compression: Some(3),
836 key_write_buffer: NZUsize!(DEFAULT_WRITE_BUFFER),
837 value_write_buffer: NZUsize!(DEFAULT_WRITE_BUFFER),
838 replay_buffer: NZUsize!(DEFAULT_REPLAY_BUFFER),
839 items_per_section: NZU64!(DEFAULT_ITEMS_PER_SECTION),
840 };
841 let mut archive = Archive::init(context.child("first"), cfg.clone())
842 .await
843 .expect("Failed to initialize archive");
844
845 let index = 1u64;
847 let key = test_key("testkey");
848 let data = 1;
849 archive
850 .put(index, key.clone(), data)
851 .await
852 .expect("Failed to put data");
853
854 archive.sync().await.expect("Failed to sync archive");
856 drop(archive);
857
858 let cfg = Config {
861 translator: FourCap,
862 key_partition: "test-index".into(),
863 key_page_cache: CacheRef::from_pooler(&context, PAGE_SIZE, PAGE_CACHE_SIZE),
864 value_partition: "test-value".into(),
865 codec_config: (),
866 compression: None,
867 key_write_buffer: NZUsize!(DEFAULT_WRITE_BUFFER),
868 value_write_buffer: NZUsize!(DEFAULT_WRITE_BUFFER),
869 replay_buffer: NZUsize!(DEFAULT_REPLAY_BUFFER),
870 items_per_section: NZU64!(DEFAULT_ITEMS_PER_SECTION),
871 };
872 let archive =
873 Archive::<_, _, FixedBytes<64>, i32>::init(context.child("second"), cfg.clone())
874 .await
875 .unwrap();
876
877 let result: Result<Option<i32>, _> = archive.get(Identifier::Index(index)).await;
881 assert!(matches!(
882 result,
883 Err(Error::Journal(JournalError::Codec(CodecError::ExtraData(
884 _
885 ))))
886 ));
887 });
888 }
889
890 #[test_traced]
891 fn test_archive_overlapping_key_basic() {
892 let executor = deterministic::Runner::default();
894 executor.start(|context| async move {
895 let cfg = Config {
897 translator: FourCap,
898 key_partition: "test-index".into(),
899 key_page_cache: CacheRef::from_pooler(&context, PAGE_SIZE, PAGE_CACHE_SIZE),
900 value_partition: "test-value".into(),
901 codec_config: (),
902 compression: None,
903 key_write_buffer: NZUsize!(DEFAULT_WRITE_BUFFER),
904 value_write_buffer: NZUsize!(DEFAULT_WRITE_BUFFER),
905 replay_buffer: NZUsize!(DEFAULT_REPLAY_BUFFER),
906 items_per_section: NZU64!(DEFAULT_ITEMS_PER_SECTION),
907 };
908 let mut archive = Archive::init(context.child("storage"), cfg.clone())
909 .await
910 .expect("Failed to initialize archive");
911
912 let index1 = 1u64;
913 let key1 = test_key("keys1");
914 let data1 = 1;
915 let index2 = 2u64;
916 let key2 = test_key("keys2");
917 let data2 = 2;
918
919 archive
921 .put(index1, key1.clone(), data1)
922 .await
923 .expect("Failed to put data");
924
925 archive
927 .put(index2, key2.clone(), data2)
928 .await
929 .expect("Failed to put data");
930
931 let retrieved = archive
933 .get(Identifier::Key(&key1))
934 .await
935 .expect("Failed to get data")
936 .expect("Data not found");
937 assert_eq!(retrieved, data1);
938
939 let retrieved = archive
941 .get(Identifier::Key(&key2))
942 .await
943 .expect("Failed to get data")
944 .expect("Data not found");
945 assert_eq!(retrieved, data2);
946
947 let buffer = context.encode();
949 assert!(has_metric_value(&buffer, "items_tracked", 2));
950 assert!(buffer.contains("unnecessary_reads_total 1"));
951 assert!(buffer.contains("gets_total 2"));
952 });
953 }
954
955 #[test_traced]
956 fn test_archive_overlapping_key_multiple_sections() {
957 let executor = deterministic::Runner::default();
959 executor.start(|context| async move {
960 let cfg = Config {
962 translator: FourCap,
963 key_partition: "test-index".into(),
964 key_page_cache: CacheRef::from_pooler(&context, PAGE_SIZE, PAGE_CACHE_SIZE),
965 value_partition: "test-value".into(),
966 codec_config: (),
967 compression: None,
968 key_write_buffer: NZUsize!(DEFAULT_WRITE_BUFFER),
969 value_write_buffer: NZUsize!(DEFAULT_WRITE_BUFFER),
970 replay_buffer: NZUsize!(DEFAULT_REPLAY_BUFFER),
971 items_per_section: NZU64!(DEFAULT_ITEMS_PER_SECTION),
972 };
973 let mut archive = Archive::init(context.child("storage"), cfg.clone())
974 .await
975 .expect("Failed to initialize archive");
976
977 let index1 = 1u64;
978 let key1 = test_key("keys1");
979 let data1 = 1;
980 let index2 = 2_000_000u64;
981 let key2 = test_key("keys2");
982 let data2 = 2;
983
984 archive
986 .put(index1, key1.clone(), data1)
987 .await
988 .expect("Failed to put data");
989
990 archive
992 .put(index2, key2.clone(), data2)
993 .await
994 .expect("Failed to put data");
995
996 let retrieved = archive
998 .get(Identifier::Key(&key1))
999 .await
1000 .expect("Failed to get data")
1001 .expect("Data not found");
1002 assert_eq!(retrieved, data1);
1003
1004 let retrieved = archive
1006 .get(Identifier::Key(&key2))
1007 .await
1008 .expect("Failed to get data")
1009 .expect("Data not found");
1010 assert_eq!(retrieved, data2);
1011 });
1012 }
1013
1014 #[test_traced]
1015 fn test_archive_prune_keys() {
1016 let executor = deterministic::Runner::default();
1018 executor.start(|context| async move {
1019 let cfg = Config {
1021 translator: FourCap,
1022 key_partition: "test-index".into(),
1023 key_page_cache: CacheRef::from_pooler(&context, PAGE_SIZE, PAGE_CACHE_SIZE),
1024 value_partition: "test-value".into(),
1025 codec_config: (),
1026 compression: None,
1027 key_write_buffer: NZUsize!(DEFAULT_WRITE_BUFFER),
1028 value_write_buffer: NZUsize!(DEFAULT_WRITE_BUFFER),
1029 replay_buffer: NZUsize!(DEFAULT_REPLAY_BUFFER),
1030 items_per_section: NZU64!(1), };
1032 let mut archive = Archive::init(context.child("storage"), cfg.clone())
1033 .await
1034 .expect("Failed to initialize archive");
1035
1036 let keys = vec![
1038 (1u64, test_key("key1-blah"), 1),
1039 (2u64, test_key("key2-blah"), 2),
1040 (3u64, test_key("key3-blah"), 3),
1041 (4u64, test_key("key3-bleh"), 3),
1042 (5u64, test_key("key4-blah"), 4),
1043 ];
1044
1045 for (index, key, data) in &keys {
1046 archive
1047 .put(*index, key.clone(), *data)
1048 .await
1049 .expect("Failed to put data");
1050 }
1051
1052 let buffer = context.encode();
1054 assert!(has_metric_value(&buffer, "items_tracked", 5));
1055
1056 archive.prune(3).await.expect("Failed to prune");
1058
1059 for (index, key, data) in keys {
1061 let retrieved = archive
1062 .get(Identifier::Key(&key))
1063 .await
1064 .expect("Failed to get data");
1065 if index < 3 {
1066 assert!(retrieved.is_none());
1067 } else {
1068 assert_eq!(retrieved.expect("Data not found"), data);
1069 }
1070 }
1071
1072 let buffer = context.encode();
1074 assert!(has_metric_value(&buffer, "items_tracked", 3));
1075 assert!(has_metric_value(&buffer, "indices_pruned_total", 2));
1076 assert!(has_metric_value(&buffer, "pruned_total", 0)); archive.prune(2).await.expect("Failed to prune");
1080
1081 archive.prune(3).await.expect("Failed to prune");
1083
1084 let result = archive.put(1, test_key("key1-blah"), 1).await;
1086 assert!(matches!(result, Err(Error::AlreadyPrunedTo(3))));
1087
1088 archive
1090 .put(6, test_key("key2-blfh"), 5)
1091 .await
1092 .expect("Failed to put data");
1093
1094 let buffer = context.encode();
1096 assert!(has_metric_value(&buffer, "items_tracked", 4)); assert!(has_metric_value(&buffer, "indices_pruned_total", 2));
1098 assert!(has_metric_value(&buffer, "pruned_total", 1));
1099 });
1100 }
1101
1102 fn test_archive_keys_and_restart(num_keys: usize) -> String {
1103 let executor = deterministic::Runner::default();
1105 executor.start(|mut context| async move {
1106 let items_per_section = 256u64;
1108 let cfg = Config {
1109 translator: TwoCap,
1110 key_partition: "test-index".into(),
1111 key_page_cache: CacheRef::from_pooler(&context, PAGE_SIZE, PAGE_CACHE_SIZE),
1112 value_partition: "test-value".into(),
1113 codec_config: (),
1114 compression: None,
1115 key_write_buffer: NZUsize!(DEFAULT_WRITE_BUFFER),
1116 value_write_buffer: NZUsize!(DEFAULT_WRITE_BUFFER),
1117 replay_buffer: NZUsize!(DEFAULT_REPLAY_BUFFER),
1118 items_per_section: NZU64!(items_per_section),
1119 };
1120 let mut archive = Archive::init(
1121 context.child("init").with_attribute("index", 1),
1122 cfg.clone(),
1123 )
1124 .await
1125 .expect("Failed to initialize archive");
1126
1127 let mut keys = BTreeMap::new();
1129 while keys.len() < num_keys {
1130 let index = keys.len() as u64;
1131 let mut key = [0u8; 64];
1132 context.fill(&mut key);
1133 let key = FixedBytes::<64>::decode(key.as_ref()).unwrap();
1134 let mut data = [0u8; 1024];
1135 context.fill(&mut data);
1136 let data = FixedBytes::<1024>::decode(data.as_ref()).unwrap();
1137
1138 archive
1139 .put(index, key.clone(), data.clone())
1140 .await
1141 .expect("Failed to put data");
1142 keys.insert(key, (index, data));
1143 }
1144
1145 for (key, (index, data)) in &keys {
1147 let retrieved = archive
1148 .get(Identifier::Index(*index))
1149 .await
1150 .expect("Failed to get data")
1151 .expect("Data not found");
1152 assert_eq!(&retrieved, data);
1153 let retrieved = archive
1154 .get(Identifier::Key(key))
1155 .await
1156 .expect("Failed to get data")
1157 .expect("Data not found");
1158 assert_eq!(&retrieved, data);
1159 }
1160
1161 let buffer = context.encode();
1163 assert!(has_metric_value(&buffer, "items_tracked", num_keys));
1164 assert!(has_metric_value(&buffer, "pruned_total", 0));
1165
1166 archive.sync().await.expect("Failed to sync archive");
1168 drop(archive);
1169
1170 let cfg = Config {
1172 translator: TwoCap,
1173 key_partition: "test-index".into(),
1174 key_page_cache: CacheRef::from_pooler(&context, PAGE_SIZE, PAGE_CACHE_SIZE),
1175 value_partition: "test-value".into(),
1176 codec_config: (),
1177 compression: None,
1178 key_write_buffer: NZUsize!(DEFAULT_WRITE_BUFFER),
1179 value_write_buffer: NZUsize!(DEFAULT_WRITE_BUFFER),
1180 replay_buffer: NZUsize!(DEFAULT_REPLAY_BUFFER),
1181 items_per_section: NZU64!(items_per_section),
1182 };
1183 let mut archive = Archive::<_, _, _, FixedBytes<1024>>::init(
1184 context.child("init").with_attribute("index", 2),
1185 cfg.clone(),
1186 )
1187 .await
1188 .expect("Failed to initialize archive");
1189
1190 for (key, (index, data)) in &keys {
1192 let retrieved = archive
1193 .get(Identifier::Index(*index))
1194 .await
1195 .expect("Failed to get data")
1196 .expect("Data not found");
1197 assert_eq!(&retrieved, data);
1198 let retrieved = archive
1199 .get(Identifier::Key(key))
1200 .await
1201 .expect("Failed to get data")
1202 .expect("Data not found");
1203 assert_eq!(&retrieved, data);
1204 }
1205
1206 let min = (keys.len() / 2) as u64;
1208 archive.prune(min).await.expect("Failed to prune");
1209
1210 let min = (min / items_per_section) * items_per_section;
1212 let mut removed = 0;
1213 for (key, (index, data)) in keys {
1214 if index >= min {
1215 let retrieved = archive
1216 .get(Identifier::Key(&key))
1217 .await
1218 .expect("Failed to get data")
1219 .expect("Data not found");
1220 assert_eq!(retrieved, data);
1221
1222 let (current_end, start_next) = archive.next_gap(index);
1224 assert_eq!(current_end.unwrap(), num_keys as u64 - 1);
1225 assert!(start_next.is_none());
1226 } else {
1227 let retrieved = archive
1228 .get(Identifier::Key(&key))
1229 .await
1230 .expect("Failed to get data");
1231 assert!(retrieved.is_none());
1232 removed += 1;
1233
1234 let (current_end, start_next) = archive.next_gap(index);
1236 assert!(current_end.is_none());
1237 assert_eq!(start_next.unwrap(), min);
1238 }
1239 }
1240
1241 let buffer = context.encode();
1243 assert!(has_metric_value(
1244 &buffer,
1245 "items_tracked",
1246 num_keys - removed
1247 ));
1248 assert!(has_metric_value(&buffer, "indices_pruned_total", removed));
1249 assert!(has_metric_value(&buffer, "pruned_total", 0)); context.auditor().state()
1252 })
1253 }
1254
1255 #[test_group("slow")]
1256 #[test_traced]
1257 fn test_archive_many_keys_and_restart() {
1258 test_archive_keys_and_restart(100_000);
1259 }
1260
1261 #[test_group("slow")]
1262 #[test_traced]
1263 fn test_determinism() {
1264 let state1 = test_archive_keys_and_restart(5_000);
1265 let state2 = test_archive_keys_and_restart(5_000);
1266 assert_eq!(state1, state2);
1267 }
1268
1269 #[test_traced]
1276 fn test_archive_key_lookup_skips_pruned_duplicates() {
1277 let executor = deterministic::Runner::default();
1278 executor.start(|context| async move {
1279 let cfg = Config {
1280 translator: FourCap,
1281 key_partition: "test-index".into(),
1282 key_page_cache: CacheRef::from_pooler(&context, PAGE_SIZE, PAGE_CACHE_SIZE),
1283 value_partition: "test-value".into(),
1284 codec_config: (),
1285 compression: None,
1286 key_write_buffer: NZUsize!(DEFAULT_WRITE_BUFFER),
1287 value_write_buffer: NZUsize!(DEFAULT_WRITE_BUFFER),
1288 replay_buffer: NZUsize!(DEFAULT_REPLAY_BUFFER),
1289 items_per_section: NZU64!(1),
1290 };
1291 let mut archive = Archive::init(context.child("storage"), cfg)
1292 .await
1293 .expect("Failed to initialize archive");
1294
1295 let key = test_key("dupe-key");
1299 archive.put(2, key.clone(), 20).await.unwrap();
1300 archive.put(5, key.clone(), 50).await.unwrap();
1301
1302 assert!(archive.get(Identifier::Key(&key)).await.unwrap().is_some());
1306 assert!(archive.has(Identifier::Key(&key)).await.unwrap());
1307
1308 archive.prune(3).await.unwrap();
1311 let got = archive.get(Identifier::Key(&key)).await.unwrap();
1312 assert_eq!(
1313 got,
1314 Some(50),
1315 "key lookup must skip the pruned entry and return the surviving one"
1316 );
1317 assert!(archive.has(Identifier::Key(&key)).await.unwrap());
1318
1319 archive.prune(6).await.unwrap();
1321 assert_eq!(archive.get(Identifier::Key(&key)).await.unwrap(), None);
1322 assert!(!archive.has(Identifier::Key(&key)).await.unwrap());
1323 });
1324 }
1325
1326 #[test_traced]
1327 fn test_get_all_after_prune() {
1328 let executor = deterministic::Runner::default();
1329 executor.start(|context| async move {
1330 let cfg = Config {
1331 translator: FourCap,
1332 key_partition: "test-index".into(),
1333 key_page_cache: CacheRef::from_pooler(&context, PAGE_SIZE, PAGE_CACHE_SIZE),
1334 value_partition: "test-value".into(),
1335 codec_config: (),
1336 compression: None,
1337 key_write_buffer: NZUsize!(DEFAULT_WRITE_BUFFER),
1338 value_write_buffer: NZUsize!(DEFAULT_WRITE_BUFFER),
1339 replay_buffer: NZUsize!(DEFAULT_REPLAY_BUFFER),
1340 items_per_section: NZU64!(1),
1341 };
1342 let mut archive = Archive::init(context.child("storage"), cfg)
1343 .await
1344 .expect("Failed to initialize archive");
1345
1346 archive.put_multi(1, test_key("aaa"), 10).await.unwrap();
1347 archive.put_multi(1, test_key("bbb"), 20).await.unwrap();
1348 archive.put_multi(3, test_key("ccc"), 30).await.unwrap();
1349
1350 archive.prune(3).await.unwrap();
1352
1353 let all = archive.get_all(1).await.unwrap();
1355 assert_eq!(all, None);
1356
1357 let all = archive.get_all(3).await.unwrap();
1359 assert_eq!(all, Some(vec![30]));
1360 });
1361 }
1362
1363 #[test_traced]
1364 fn test_has_at() {
1365 let executor = deterministic::Runner::default();
1366 let (_, checkpoint) = executor.start_and_recover(|context| async move {
1367 let cfg = test_config(&context, NZU64!(2));
1368 let mut archive = Archive::init(context.child("storage"), cfg)
1369 .await
1370 .expect("Failed to initialize archive");
1371
1372 assert!(!archive.has_at(1, &test_key("aaaa1")).await.unwrap());
1374
1375 archive.put_multi(1, test_key("aaaa1"), 10).await.unwrap();
1377 assert!(archive.has_at(1, &test_key("aaaa1")).await.unwrap());
1378
1379 assert!(!archive.has_at(2, &test_key("aaaa1")).await.unwrap());
1381
1382 assert!(!archive.has_at(1, &test_key("aaaa2")).await.unwrap());
1385
1386 archive.put_multi(1, test_key("aaaa2"), 20).await.unwrap();
1388 assert!(archive.has_at(1, &test_key("aaaa1")).await.unwrap());
1389 assert!(archive.has_at(1, &test_key("aaaa2")).await.unwrap());
1390
1391 assert!(!archive.has_at(1, &test_key("bbbb")).await.unwrap());
1393
1394 archive.put_multi(3, test_key("cccc"), 30).await.unwrap();
1395 archive.sync().await.unwrap();
1396 });
1397
1398 deterministic::Runner::from(checkpoint).start(|context| async move {
1399 let cfg = test_config(&context, NZU64!(2));
1400 let mut archive =
1401 Archive::<_, _, FixedBytes<64>, i32>::init(context.child("reopen"), cfg)
1402 .await
1403 .expect("Failed to reopen archive");
1404
1405 assert!(archive.has_at(1, &test_key("aaaa1")).await.unwrap());
1407 assert!(archive.has_at(1, &test_key("aaaa2")).await.unwrap());
1408 assert!(!archive.has_at(1, &test_key("bbbb")).await.unwrap());
1409
1410 archive.prune(2).await.unwrap();
1412 assert!(!archive.has_at(1, &test_key("aaaa1")).await.unwrap());
1413 assert!(!archive.has_at(1, &test_key("aaaa2")).await.unwrap());
1414 assert!(archive.has_at(3, &test_key("cccc")).await.unwrap());
1415
1416 archive.destroy().await.unwrap();
1417 });
1418 }
1419
1420 #[test_traced]
1421 fn test_has_key() {
1422 let executor = deterministic::Runner::default();
1423 executor.start(|context| async move {
1424 let cfg = test_config(&context, NZU64!(2));
1425 let mut archive = Archive::init(context.child("storage"), cfg)
1426 .await
1427 .expect("Failed to initialize archive");
1428
1429 let key = test_key("aaaa1");
1431 assert!(!archive.has(Identifier::Key(&key)).await.unwrap());
1432
1433 archive.put(1, key.clone(), 10).await.unwrap();
1435 assert!(archive.has(Identifier::Key(&key)).await.unwrap());
1436
1437 let collision = test_key("aaaa2");
1440 assert!(!archive.has(Identifier::Key(&collision)).await.unwrap());
1441 archive.put(2, collision.clone(), 20).await.unwrap();
1442 assert!(archive.has(Identifier::Key(&collision)).await.unwrap());
1443
1444 archive.put(4, test_key("cccc"), 30).await.unwrap();
1448 archive.prune(4).await.unwrap();
1449 assert!(!archive.has(Identifier::Key(&key)).await.unwrap());
1450 assert!(!archive.has(Identifier::Key(&collision)).await.unwrap());
1451 assert!(archive
1452 .has(Identifier::Key(&test_key("cccc")))
1453 .await
1454 .unwrap());
1455
1456 archive.destroy().await.unwrap();
1457 });
1458 }
1459
1460 #[test_traced]
1461 fn test_put_multi_prune() {
1462 let executor = deterministic::Runner::default();
1463 executor.start(|context| async move {
1464 let cfg = Config {
1465 translator: FourCap,
1466 key_partition: "test-index".into(),
1467 key_page_cache: CacheRef::from_pooler(&context, PAGE_SIZE, PAGE_CACHE_SIZE),
1468 value_partition: "test-value".into(),
1469 codec_config: (),
1470 compression: None,
1471 key_write_buffer: NZUsize!(DEFAULT_WRITE_BUFFER),
1472 value_write_buffer: NZUsize!(DEFAULT_WRITE_BUFFER),
1473 replay_buffer: NZUsize!(DEFAULT_REPLAY_BUFFER),
1474 items_per_section: NZU64!(1),
1475 };
1476 let mut archive = Archive::init(context.child("storage"), cfg)
1477 .await
1478 .expect("Failed to initialize archive");
1479
1480 archive.put_multi(1, test_key("aaa"), 10).await.unwrap();
1482 archive.put_multi(1, test_key("bbb"), 20).await.unwrap();
1483 archive.put_multi(3, test_key("ccc"), 30).await.unwrap();
1484
1485 let buffer = context.encode();
1486 assert!(has_metric_value(&buffer, "items_tracked", 2));
1487
1488 archive.prune(3).await.unwrap();
1490
1491 assert_eq!(
1493 archive
1494 .get(Identifier::Key(&test_key("aaa")))
1495 .await
1496 .unwrap(),
1497 None
1498 );
1499 assert_eq!(
1500 archive
1501 .get(Identifier::Key(&test_key("bbb")))
1502 .await
1503 .unwrap(),
1504 None
1505 );
1506
1507 assert_eq!(
1509 archive
1510 .get(Identifier::Key(&test_key("ccc")))
1511 .await
1512 .unwrap(),
1513 Some(30)
1514 );
1515
1516 let buffer = context.encode();
1517 assert!(has_metric_value(&buffer, "items_tracked", 1));
1518 assert!(has_metric_value(&buffer, "indices_pruned_total", 1));
1519
1520 let result = archive.put_multi(2, test_key("ddd"), 40).await;
1522 assert!(matches!(result, Err(Error::AlreadyPrunedTo(3))));
1523 });
1524 }
1525}