1use crate::translator::Translator;
168use commonware_runtime::buffer::paged::CacheRef;
169use std::num::{NonZeroU64, NonZeroUsize};
170
171mod storage;
172pub use storage::Archive;
173
174#[derive(Clone)]
176pub struct Config<T: Translator, C> {
177 pub translator: T,
182
183 pub metadata_partition: String,
186
187 pub key_partition: String,
189
190 pub key_page_cache: CacheRef,
192
193 pub value_partition: String,
195
196 pub compression: Option<u8>,
198
199 pub codec_config: C,
201
202 pub items_per_section: NonZeroU64,
211
212 pub key_write_buffer: NonZeroUsize,
215
216 pub value_write_buffer: NonZeroUsize,
219
220 pub replay_buffer: NonZeroUsize,
222}
223
224#[cfg(test)]
225mod tests {
226 use super::*;
227 use crate::{
228 archive::{Archive as _, Error, Identifier, MultiArchive as _},
229 journal::{Error as JournalError, segmented::glob::corrupt_frame},
230 translator::{EightCap, FourCap, TwoCap},
231 };
232 use commonware_codec::{Error as CodecError, FixedSize};
233 use commonware_cryptography::Crc32;
234 use commonware_macros::{test_group, test_traced};
235 use commonware_runtime::{
236 Blob as _, Error as RError, Metrics as _, ReadOptions, Runner, Spawner as _, Storage as _,
237 Supervisor as _, WriteOptions, deterministic,
238 mocks::{
239 DelayedSyncContext, PendingSyncs, drive_pending_syncs, fail_pending_syncs,
240 release_next_pending_syncs, release_pending_syncs,
241 },
242 telemetry::metrics::has_metric_value,
243 };
244 use commonware_utils::{NZU16, NZU64, NZUsize, sequence::FixedBytes};
245 use rand::RngExt as _;
246 use std::{
247 collections::BTreeMap,
248 num::{NonZeroU16, NonZeroU64},
249 sync::{
250 Arc,
251 atomic::{AtomicUsize, Ordering},
252 },
253 };
254
255 fn test_key(key: &str) -> FixedBytes<64> {
256 let mut buf = [0u8; 64];
257 let key = key.as_bytes();
258 assert!(key.len() <= buf.len());
259 buf[..key.len()].copy_from_slice(key);
260 FixedBytes::new(buf)
261 }
262
263 const DEFAULT_ITEMS_PER_SECTION: u64 = 65536;
264 const DEFAULT_WRITE_BUFFER: usize = 1024;
265 const DEFAULT_REPLAY_BUFFER: usize = 4096;
266 const PAGE_SIZE: NonZeroU16 = NZU16!(1024);
267 const PAGE_CACHE_SIZE: NonZeroUsize = NZUsize!(10);
268
269 fn test_config<T: Translator>(
270 partition_prefix: &str,
271 items_per_section: NonZeroU64,
272 translator: T,
273 ) -> Config<T, ()> {
274 Config {
275 translator,
276 metadata_partition: format!("{partition_prefix}-metadata"),
277 key_partition: format!("{partition_prefix}-index"),
278 key_page_cache: CacheRef::new(PAGE_SIZE, PAGE_CACHE_SIZE),
279 value_partition: format!("{partition_prefix}-value"),
280 codec_config: (),
281 compression: None,
282 key_write_buffer: NZUsize!(DEFAULT_WRITE_BUFFER),
283 value_write_buffer: NZUsize!(DEFAULT_WRITE_BUFFER),
284 replay_buffer: NZUsize!(DEFAULT_REPLAY_BUFFER),
285 items_per_section,
286 }
287 }
288
289 const I32_VALUE_FRAME_SIZE: u64 =
292 (i32::SIZE + crate::journal::segmented::glob::CHECKSUM_SIZE) as u64;
293
294 #[test_traced]
295 fn test_put_after_start_sync_is_accepted_before_handle_completes() {
296 let executor = deterministic::Runner::default();
297 let (_, checkpoint) = executor.start_and_recover(|context| async move {
298 let pending = PendingSyncs::default();
299 let context = DelayedSyncContext {
300 inner: context,
301 pending: pending.clone(),
302 };
303 let cfg = test_config("test", NZU64!(DEFAULT_ITEMS_PER_SECTION), FourCap);
304 let archive = Archive::init(context.child("storage"), cfg)
305 .await
306 .expect("Failed to initialize archive");
307
308 let (mut archive, handle) = archive
309 .put_start_sync(1, test_key("aaa"), &10)
310 .await
311 .expect("Failed to start sync");
312 let pending_after_start = pending.lock().len();
313 assert!(
314 pending_after_start > 0,
315 "put_start_sync should return while the sync handle is still pending"
316 );
317
318 archive = archive
319 .put(2, test_key("bbb"), &20)
320 .await
321 .expect("archive should remain usable before sync completion");
322 assert_eq!(
323 pending.lock().len(),
324 pending_after_start,
325 "put should not issue a new storage sync while accepting later data"
326 );
327 assert_eq!(archive.get(Identifier::Index(1)).await.unwrap(), Some(10));
328
329 release_pending_syncs(&pending);
330 handle.await.expect("sync handle should complete");
331
332 let (_archive, follow_up) = archive
333 .start_sync()
334 .await
335 .expect("Failed to start next sync");
336 assert!(
337 !pending.lock().is_empty(),
338 "the later put must remain pending for a future sync"
339 );
340 release_pending_syncs(&pending);
341 follow_up.await.expect("follow-up sync should complete");
342 });
343
344 deterministic::Runner::from(checkpoint).start(|context| async move {
345 let cfg = test_config("test", NZU64!(DEFAULT_ITEMS_PER_SECTION), FourCap);
346 let archive = Archive::<_, _, FixedBytes<64>, i32>::init(context.child("reopen"), cfg)
347 .await
348 .expect("Failed to reopen archive");
349
350 assert_eq!(archive.get(Identifier::Index(1)).await.unwrap(), Some(10));
351 assert_eq!(archive.get(Identifier::Index(2)).await.unwrap(), Some(20));
352 });
353 }
354
355 #[test_traced]
356 fn test_duplicate_put_start_sync_observes_in_flight_sync() {
357 let executor = deterministic::Runner::default();
358 executor.start(|context| async move {
359 let pending = PendingSyncs::default();
360 let context = DelayedSyncContext {
361 inner: context,
362 pending: pending.clone(),
363 };
364 let cfg = test_config("test", NZU64!(DEFAULT_ITEMS_PER_SECTION), FourCap);
365 let archive = Archive::init(context.child("storage"), cfg)
366 .await
367 .expect("Failed to initialize archive");
368
369 let (archive, first) = archive
370 .put_start_sync(1, test_key("aaa"), &10)
371 .await
372 .expect("Failed to start sync");
373 assert_eq!(pending.lock().len(), 2);
374
375 let (archive, second) = archive
376 .put_start_sync(1, test_key("duplicate"), &99)
377 .await
378 .expect("Failed to start duplicate sync");
379 assert_eq!(
380 pending.lock().len(),
381 2,
382 "duplicate put_start_sync must not issue a new storage sync"
383 );
384
385 let started = Arc::new(AtomicUsize::new(0));
386 let completed = Arc::new(AtomicUsize::new(0));
387 let started_clone = started.clone();
388 let completed_clone = completed.clone();
389 let waiter = context.inner.child("duplicate").spawn(|_| async move {
390 started_clone.fetch_add(1, Ordering::Relaxed);
391 second.await.expect("duplicate sync handle should complete");
392 completed_clone.fetch_add(1, Ordering::Relaxed);
393 });
394
395 while started.load(Ordering::Relaxed) == 0 {
396 commonware_runtime::reschedule().await;
397 }
398 commonware_runtime::reschedule().await;
399 assert_eq!(
400 completed.load(Ordering::Relaxed),
401 0,
402 "duplicate put_start_sync must observe the original in-flight sync"
403 );
404
405 release_pending_syncs(&pending);
406 first.await.expect("first sync handle should complete");
407 while completed.load(Ordering::Relaxed) == 0 {
408 commonware_runtime::reschedule().await;
409 }
410 waiter.await.expect("duplicate waiter failed");
411
412 assert_eq!(archive.get(Identifier::Index(1)).await.unwrap(), Some(10));
413 });
414 }
415
416 #[test_traced]
417 fn test_below_floor_put_start_sync_covers_prior_pending_write() {
418 let executor = deterministic::Runner::default();
419 executor.start(|context| async move {
420 let pending = PendingSyncs::default();
421 let context = DelayedSyncContext {
422 inner: context,
423 pending: pending.clone(),
424 };
425 let cfg = test_config("test", NZU64!(1), FourCap);
426 let archive = Archive::init(context.child("storage"), cfg)
427 .await
428 .expect("Failed to initialize archive");
429
430 let archive = archive.prune(1).await.expect("Failed to set prune floor");
433 let archive = archive
434 .put(2, test_key("pending"), &20)
435 .await
436 .expect("Failed to buffer retained write");
437
438 assert!(pending.lock().is_empty());
442 let (archive, handle) = archive
443 .put_start_sync(0, test_key("pruned"), &0)
444 .await
445 .expect("Failed to request sync through below-floor put");
446 assert_eq!(
447 pending.lock().len(),
448 2,
449 "the sync combinator must cover writes accepted before its below-floor put"
450 );
451
452 release_pending_syncs(&pending);
455 handle.await.expect("covering sync should complete");
456 assert_eq!(archive.get(Identifier::Index(2)).await.unwrap(), Some(20));
457 assert_eq!(archive.get(Identifier::Index(0)).await.unwrap(), None);
458 });
459 }
460
461 #[test_traced]
462 fn test_below_floor_put_multi_sync_covers_prior_pending_write() {
463 let executor = deterministic::Runner::default();
464 executor.start(|context| async move {
465 let pending = PendingSyncs::default();
466 let context = DelayedSyncContext {
467 inner: context,
468 pending: pending.clone(),
469 };
470 let cfg = test_config("test", NZU64!(1), FourCap);
471 let archive = Archive::init(context.child("storage"), cfg)
472 .await
473 .expect("Failed to initialize archive");
474
475 let archive = archive.prune(1).await.expect("Failed to set prune floor");
478 let archive = archive
479 .put_multi(2, test_key("pending"), &20)
480 .await
481 .expect("Failed to buffer retained write");
482
483 pending.arm();
486 let completed = Arc::new(AtomicUsize::new(0));
487 let completed_clone = completed.clone();
488 let task = context.inner.child("put_multi_sync").spawn(|_| async move {
489 let result = archive.put_multi_sync(0, test_key("pruned"), &0).await;
490 completed_clone.store(1, Ordering::Relaxed);
491 result
492 });
493 while pending.calls() == 0 && completed.load(Ordering::Relaxed) == 0 {
494 commonware_runtime::reschedule().await;
495 }
496
497 assert_eq!(
500 completed.load(Ordering::Relaxed),
501 0,
502 "put_multi_sync must wait for writes accepted before its below-floor put"
503 );
504 assert!(pending.calls() > 0);
505 release_pending_syncs(&pending);
506 let archive = task
507 .await
508 .expect("put_multi_sync task failed")
509 .expect("put_multi_sync failed");
510
511 assert_eq!(archive.get_all(2).await.unwrap(), Some(vec![20]));
513 assert_eq!(archive.get_all(0).await.unwrap(), None);
514 });
515 }
516
517 #[test_traced]
518 fn test_overlapping_put_start_sync_waits_for_in_flight_sync() {
519 let executor = deterministic::Runner::default();
520 executor.start(|context| async move {
521 let pending = PendingSyncs::default();
522 let context = DelayedSyncContext {
523 inner: context,
524 pending: pending.clone(),
525 };
526 let cfg = test_config("test", NZU64!(DEFAULT_ITEMS_PER_SECTION), FourCap);
527 let archive = Archive::init(context.child("storage"), cfg)
528 .await
529 .expect("Failed to initialize archive");
530
531 let (archive, first) = archive
532 .put_start_sync(1, test_key("aaa"), &10)
533 .await
534 .expect("Failed to start sync");
535 let pending_after_first = pending.lock().len();
536 assert!(pending_after_first > 0);
537
538 let started = Arc::new(AtomicUsize::new(0));
539 let completed = Arc::new(AtomicUsize::new(0));
540 let started_clone = started.clone();
541 let completed_clone = completed.clone();
542 let waiter = context.inner.child("second").spawn(|_| async move {
543 started_clone.fetch_add(1, Ordering::Relaxed);
544 let (archive, second) = archive
545 .put_start_sync(2, test_key("bbb"), &20)
546 .await
547 .expect("Failed to start second sync");
548 completed_clone.fetch_add(1, Ordering::Relaxed);
549 (archive, second)
550 });
551
552 while started.load(Ordering::Relaxed) == 0 {
553 commonware_runtime::reschedule().await;
554 }
555 commonware_runtime::reschedule().await;
556 assert_eq!(completed.load(Ordering::Relaxed), 0);
557 assert_eq!(
558 pending.lock().len(),
559 pending_after_first,
560 "second put_start_sync must not start new syncs before the first completes"
561 );
562
563 release_pending_syncs(&pending);
564 first.await.expect("first sync handle should complete");
565 while completed.load(Ordering::Relaxed) == 0 {
566 commonware_runtime::reschedule().await;
567 }
568 let (archive, second) = waiter.await.expect("second put task failed");
569 assert!(!pending.lock().is_empty());
570 release_pending_syncs(&pending);
571 second.await.expect("second sync handle should complete");
572
573 assert_eq!(archive.get(Identifier::Index(1)).await.unwrap(), Some(10));
574 assert_eq!(archive.get(Identifier::Index(2)).await.unwrap(), Some(20));
575 });
576 }
577
578 #[test_traced]
579 fn test_sync_after_put_start_sync_waits_for_in_flight_sync() {
580 let executor = deterministic::Runner::default();
581 executor.start(|context| async move {
582 let pending = PendingSyncs::default();
583 let context = DelayedSyncContext {
584 inner: context,
585 pending: pending.clone(),
586 };
587 let cfg = test_config("test", NZU64!(DEFAULT_ITEMS_PER_SECTION), FourCap);
588 let archive = Archive::init(context.child("storage"), cfg)
589 .await
590 .expect("Failed to initialize archive");
591
592 let (archive, first) = archive
593 .put_start_sync(1, test_key("aaa"), &10)
594 .await
595 .expect("Failed to start sync");
596 assert!(!pending.lock().is_empty());
597
598 let started = Arc::new(AtomicUsize::new(0));
599 let completed = Arc::new(AtomicUsize::new(0));
600 let started_clone = started.clone();
601 let completed_clone = completed.clone();
602 let waiter = context.inner.child("sync").spawn(|_| async move {
603 started_clone.fetch_add(1, Ordering::Relaxed);
604 let archive = archive.sync().await.expect("sync should complete");
605 completed_clone.fetch_add(1, Ordering::Relaxed);
606 archive
607 });
608
609 while started.load(Ordering::Relaxed) == 0 {
610 commonware_runtime::reschedule().await;
611 }
612 commonware_runtime::reschedule().await;
613 assert_eq!(
614 completed.load(Ordering::Relaxed),
615 0,
616 "shutdown sync must wait for the in-flight put_start_sync handle"
617 );
618
619 release_pending_syncs(&pending);
620 first.await.expect("first sync handle should complete");
621 while completed.load(Ordering::Relaxed) == 0 {
622 commonware_runtime::reschedule().await;
623 }
624 let archive = waiter.await.expect("sync task failed");
625 assert_eq!(archive.get(Identifier::Index(1)).await.unwrap(), Some(10));
626 });
627 }
628
629 #[test_traced]
630 fn test_destroy_after_put_start_sync_waits_for_in_flight_sync() {
631 let executor = deterministic::Runner::default();
632 executor.start(|context| async move {
633 let pending = PendingSyncs::default();
634 let context = DelayedSyncContext {
635 inner: context,
636 pending: pending.clone(),
637 };
638 let cfg = test_config("test", NZU64!(DEFAULT_ITEMS_PER_SECTION), FourCap);
639 let archive = Archive::<_, _, FixedBytes<64>, i32>::init(context.child("storage"), cfg)
640 .await
641 .expect("Failed to initialize archive");
642
643 let (archive, first) = archive
644 .put_start_sync(1, test_key("aaa"), &10)
645 .await
646 .expect("Failed to start sync");
647 assert!(!pending.lock().is_empty());
648
649 let started = Arc::new(AtomicUsize::new(0));
650 let completed = Arc::new(AtomicUsize::new(0));
651 let started_clone = started.clone();
652 let completed_clone = completed.clone();
653 let waiter = context.inner.child("destroy").spawn(|_| async move {
654 started_clone.fetch_add(1, Ordering::Relaxed);
655 archive.destroy().await.expect("destroy should complete");
656 completed_clone.fetch_add(1, Ordering::Relaxed);
657 });
658
659 while started.load(Ordering::Relaxed) == 0 {
660 commonware_runtime::reschedule().await;
661 }
662 commonware_runtime::reschedule().await;
663 assert_eq!(
664 completed.load(Ordering::Relaxed),
665 0,
666 "destroy must wait for the in-flight put_start_sync handle"
667 );
668
669 release_pending_syncs(&pending);
670 first.await.expect("first sync handle should complete");
671 while completed.load(Ordering::Relaxed) == 0 {
672 commonware_runtime::reschedule().await;
673 }
674 waiter.await.expect("destroy task failed");
675 });
676 }
677
678 #[test_traced]
679 fn test_prune_after_put_start_sync_waits_for_in_flight_sync() {
680 let executor = deterministic::Runner::default();
681 executor.start(|context| async move {
682 let pending = PendingSyncs::default();
683 let context = DelayedSyncContext {
684 inner: context,
685 pending: pending.clone(),
686 };
687 let cfg = test_config("test", NZU64!(1), FourCap);
688 let archive = Archive::<_, _, FixedBytes<64>, i32>::init(context.child("storage"), cfg)
689 .await
690 .expect("Failed to initialize archive");
691
692 let (archive, first) = archive
693 .put_start_sync(1, test_key("aaa"), &10)
694 .await
695 .expect("Failed to start sync");
696 assert!(!pending.lock().is_empty());
697
698 let started = Arc::new(AtomicUsize::new(0));
699 let completed = Arc::new(AtomicUsize::new(0));
700 let started_clone = started.clone();
701 let completed_clone = completed.clone();
702 let waiter = context.inner.child("prune").spawn(|_| async move {
703 started_clone.fetch_add(1, Ordering::Relaxed);
704 let archive = archive.prune(2).await.expect("prune should complete");
705 completed_clone.fetch_add(1, Ordering::Relaxed);
706 archive
707 });
708
709 while started.load(Ordering::Relaxed) == 0 {
710 commonware_runtime::reschedule().await;
711 }
712 commonware_runtime::reschedule().await;
713 assert_eq!(
714 completed.load(Ordering::Relaxed),
715 0,
716 "prune must wait for in-flight syncs on pruned sections"
717 );
718
719 release_pending_syncs(&pending);
720 first
721 .await
722 .expect("sync handle should complete despite pruning");
723 while completed.load(Ordering::Relaxed) == 0 {
724 commonware_runtime::reschedule().await;
725 }
726 let archive = waiter.await.expect("prune task failed");
727 assert_eq!(archive.get(Identifier::Index(1)).await.unwrap(), None);
728 });
729 }
730
731 #[test_traced]
732 fn test_prune_surfaces_failed_in_flight_sync() {
733 let executor = deterministic::Runner::default();
734 executor.start(|context| async move {
735 let pending = PendingSyncs::default();
736 let context = DelayedSyncContext {
737 inner: context,
738 pending: pending.clone(),
739 };
740 let cfg = test_config("test", NZU64!(1), FourCap);
741 let archive = Archive::<_, _, FixedBytes<64>, i32>::init(context.child("storage"), cfg)
742 .await
743 .expect("Failed to initialize archive");
744
745 let (archive, first) = archive
746 .put_start_sync(1, test_key("aaa"), &10)
747 .await
748 .expect("Failed to start sync");
749 fail_pending_syncs(&pending);
750
751 let err = archive
752 .prune(2)
753 .await
754 .expect_err("prune must surface a failed in-flight sync");
755 assert!(matches!(
756 err,
757 Error::Journal(JournalError::Runtime(RError::Io(_)))
758 ));
759
760 let err = first.await.expect_err("first sync handle should fail");
761 assert!(matches!(err, RError::Io(_)));
762 });
763 }
764
765 #[test_traced]
766 fn test_put_start_sync_after_prune_drops_pruned_sync_requests() {
767 let executor = deterministic::Runner::default();
768 executor.start(|context| async move {
769 let pending = PendingSyncs::default();
770 let context = DelayedSyncContext {
771 inner: context,
772 pending: pending.clone(),
773 };
774 let cfg = test_config("test", NZU64!(1), FourCap);
775 let archive = Archive::<_, _, FixedBytes<64>, i32>::init(context.child("storage"), cfg)
776 .await
777 .expect("Failed to initialize archive");
778
779 let (archive, first) = archive
780 .put_start_sync(1, test_key("aaa"), &10)
781 .await
782 .expect("Failed to start sync");
783 release_pending_syncs(&pending);
784 first.await.expect("first sync handle should complete");
785
786 let archive = archive.prune(2).await.expect("Failed to prune");
787
788 let (archive, second) = archive
791 .put_start_sync(2, test_key("bbb"), &20)
792 .await
793 .expect("put_start_sync after prune should succeed");
794 release_pending_syncs(&pending);
795 second.await.expect("second sync handle should complete");
796 let archive = drive_pending_syncs(&pending, archive.sync())
797 .await
798 .expect("sync after prune should succeed");
799
800 assert_eq!(archive.get(Identifier::Index(1)).await.unwrap(), None);
801 assert_eq!(archive.get(Identifier::Index(2)).await.unwrap(), Some(20));
802 });
803 }
804
805 #[test_traced]
806 fn test_overlapping_put_start_sync_restarts_after_all_handles_complete() {
807 let executor = deterministic::Runner::default();
808 let (_, checkpoint) = executor.start_and_recover(|context| async move {
809 let pending = PendingSyncs::default();
810 let context = DelayedSyncContext {
811 inner: context,
812 pending: pending.clone(),
813 };
814 let cfg = test_config("test", NZU64!(1), FourCap);
815 let archive = Archive::<_, _, FixedBytes<64>, i32>::init(context.child("storage"), cfg)
816 .await
817 .expect("Failed to initialize archive");
818
819 let (archive, first) = archive
820 .put_start_sync(1, test_key("aaa"), &10)
821 .await
822 .expect("Failed to start first sync");
823 assert_eq!(pending.lock().len(), 2);
824
825 let (_archive, second) = archive
826 .put_start_sync(2, test_key("bbb"), &20)
827 .await
828 .expect("Failed to start second sync");
829 assert_eq!(
830 pending.lock().len(),
831 4,
832 "different sections should be able to have independent in-flight syncs"
833 );
834
835 release_pending_syncs(&pending);
836 first.await.expect("first sync handle should complete");
837 second.await.expect("second sync handle should complete");
838 });
839
840 deterministic::Runner::from(checkpoint).start(|context| async move {
841 let cfg = test_config("test", NZU64!(1), FourCap);
842 let archive = Archive::<_, _, FixedBytes<64>, i32>::init(context.child("reopen"), cfg)
843 .await
844 .expect("Failed to reopen archive");
845
846 assert_eq!(archive.get(Identifier::Index(1)).await.unwrap(), Some(10));
847 assert_eq!(archive.get(Identifier::Index(2)).await.unwrap(), Some(20));
848 });
849 }
850
851 #[test_traced]
852 fn test_overlapping_put_start_sync_restarts_only_completed_handles() {
853 let executor = deterministic::Runner::default();
854 let (_, checkpoint) = executor.start_and_recover(|context| async move {
855 let pending = PendingSyncs::default();
856 let context = DelayedSyncContext {
857 inner: context,
858 pending: pending.clone(),
859 };
860 let cfg = test_config("test", NZU64!(1), FourCap);
861 let archive = Archive::<_, _, FixedBytes<64>, i32>::init(context.child("storage"), cfg)
862 .await
863 .expect("Failed to initialize archive");
864
865 let (archive, first) = archive
866 .put_start_sync(1, test_key("aaa"), &10)
867 .await
868 .expect("Failed to start first sync");
869 let (archive, second) = archive
870 .put_start_sync(2, test_key("bbb"), &20)
871 .await
872 .expect("Failed to start second sync");
873 assert_eq!(pending.lock().len(), 4);
874
875 release_next_pending_syncs(&pending, 2);
876 first.await.expect("first sync handle should complete");
877
878 drop(second);
879 drop(archive);
880 });
881
882 deterministic::Runner::from(checkpoint).start(|context| async move {
883 let cfg = test_config("test", NZU64!(1), FourCap);
884 let archive = Archive::<_, _, FixedBytes<64>, i32>::init(context.child("reopen"), cfg)
885 .await
886 .expect("Failed to reopen archive");
887
888 assert_eq!(archive.get(Identifier::Index(1)).await.unwrap(), Some(10));
889 assert_eq!(archive.get(Identifier::Index(2)).await.unwrap(), None);
890 });
891 }
892
893 #[test_traced]
894 fn test_failed_start_sync_is_returned_by_next_start_sync() {
895 let executor = deterministic::Runner::default();
896 executor.start(|context| async move {
897 let pending = PendingSyncs::default();
898 let context = DelayedSyncContext {
899 inner: context,
900 pending: pending.clone(),
901 };
902 let cfg = test_config("test", NZU64!(DEFAULT_ITEMS_PER_SECTION), FourCap);
903 let archive = Archive::<_, _, FixedBytes<64>, i32>::init(context.child("storage"), cfg)
904 .await
905 .expect("Failed to initialize archive");
906
907 let (archive, first) = archive
908 .put_start_sync(1, test_key("aaa"), &10)
909 .await
910 .expect("Failed to start sync");
911 assert_eq!(pending.lock().len(), 2);
912 fail_pending_syncs(&pending);
913
914 let archive = archive
915 .put(2, test_key("bbb"), &20)
916 .await
917 .expect("write should be accepted before observing the failed sync");
918
919 let Err(err) = archive.start_sync().await else {
922 panic!("next start_sync should return the failed in-flight sync");
923 };
924 assert!(matches!(
925 err,
926 Error::Journal(JournalError::Runtime(RError::Io(_)))
927 ));
928
929 let err = first.await.expect_err("first sync handle should fail");
930 assert!(matches!(err, RError::Io(_)));
931 });
932 }
933
934 #[test_traced]
935 fn test_archive_truncates_at_first_invalid_value() {
936 deterministic::Runner::default().start(|context| async move {
937 for (name, bad_position, retained) in [("first", 0, 0), ("middle", 1, 1)] {
938 let cfg = test_config(&format!("invalid-{name}"), NZU64!(4), FourCap);
939
940 let mut archive = Archive::init(context.child(name), cfg.clone())
944 .await
945 .unwrap();
946 for (index, value) in [10, 20, 30].into_iter().enumerate() {
947 archive = archive
948 .put(index as u64, test_key(&format!("key-{index}")), &value)
949 .await
950 .unwrap();
951 }
952 archive = archive.sync().await.unwrap();
953 drop(archive);
954
955 corrupt_frame(
956 &context,
957 &cfg.value_partition,
958 &0u64.to_be_bytes(),
959 bad_position,
960 I32_VALUE_FRAME_SIZE,
961 )
962 .await;
963
964 let archive =
970 Archive::<_, _, FixedBytes<64>, i32>::init(context.child(name), cfg.clone())
971 .await
972 .unwrap();
973 assert_eq!(
974 archive.ranges().collect::<Vec<_>>(),
975 if retained == 0 {
976 Vec::new()
977 } else {
978 vec![(0, retained - 1)]
979 }
980 );
981 for (index, value) in [10, 20, 30].into_iter().enumerate() {
982 let expected = (index < retained as usize).then_some(value);
983 assert_eq!(
984 archive.get(Identifier::Index(index as u64)).await.unwrap(),
985 expected
986 );
987 }
988 drop(archive);
989
990 let archive = Archive::<_, _, FixedBytes<64>, i32>::init(context.child(name), cfg)
993 .await
994 .unwrap();
995 assert_eq!(archive.last_index(), retained.checked_sub(1));
996 archive.destroy().await.unwrap();
997 }
998 });
999 }
1000
1001 #[test_traced]
1002 fn test_archive_completes_interrupted_rewind_to_empty_section() {
1003 deterministic::Runner::default().start(|context| async move {
1004 let cfg = test_config("empty-rewind", NZU64!(4), FourCap);
1005
1006 let archive = Archive::init(context.child("seed"), cfg.clone())
1009 .await
1010 .unwrap();
1011 let archive = archive.put(0, test_key("zero"), &10).await.unwrap();
1012 let archive = archive.sync().await.unwrap();
1013 drop(archive);
1014 context.remove(&cfg.metadata_partition, None).await.unwrap();
1015
1016 let (index, _) = context
1023 .open(&cfg.key_partition, &0u64.to_be_bytes())
1024 .await
1025 .unwrap();
1026 index.resize(0).await.unwrap();
1027 index.sync().await.unwrap();
1028 drop(index);
1029 let (_, value_size) = context
1030 .open(&cfg.value_partition, &0u64.to_be_bytes())
1031 .await
1032 .unwrap();
1033 assert_eq!(value_size, I32_VALUE_FRAME_SIZE);
1034
1035 let archive =
1036 Archive::<_, _, FixedBytes<64>, i32>::init(context.child("repair"), cfg.clone())
1037 .await
1038 .unwrap();
1039 assert_eq!(archive.last_index(), None);
1040 drop(archive);
1041 let (_, value_size) = context
1042 .open(&cfg.value_partition, &0u64.to_be_bytes())
1043 .await
1044 .unwrap();
1045 assert_eq!(value_size, 0, "startup must finish the value truncation");
1046 context.remove(&cfg.metadata_partition, None).await.unwrap();
1047
1048 let pending = PendingSyncs::default();
1056 let delayed = DelayedSyncContext {
1057 inner: context.child("clean_restart"),
1058 pending: pending.clone(),
1059 };
1060 pending.arm();
1061 let completed = Arc::new(AtomicUsize::new(0));
1062 let completed_clone = completed.clone();
1063 let cfg_clone = cfg.clone();
1064 let task = context.child("clean_restart_task").spawn(|_| async move {
1065 let result = Archive::init(delayed.child("archive"), cfg_clone).await;
1066 completed_clone.store(1, Ordering::Relaxed);
1067 result
1068 });
1069 while pending.calls() == 0 && completed.load(Ordering::Relaxed) == 0 {
1070 commonware_runtime::reschedule().await;
1071 }
1072 if pending.calls() != 0 {
1073 pending.unblock();
1074 let _ = task.await;
1075 panic!("clean empty-section restart must not issue durability operations");
1076 }
1077 pending.unblock();
1078 let archive = task.await.unwrap().unwrap();
1079
1080 let archive = archive.put(0, test_key("new"), &20).await.unwrap();
1083 let archive = archive.sync().await.unwrap();
1084 drop(archive);
1085 let (_, value_size) = context
1086 .open(&cfg.value_partition, &0u64.to_be_bytes())
1087 .await
1088 .unwrap();
1089 assert_eq!(value_size, I32_VALUE_FRAME_SIZE);
1090
1091 let archive = Archive::<_, _, FixedBytes<64>, i32>::init(context.child("reopen"), cfg)
1092 .await
1093 .unwrap();
1094 assert_eq!(archive.get(Identifier::Index(0)).await.unwrap(), Some(20));
1095 archive.destroy().await.unwrap();
1096 });
1097 }
1098
1099 #[test_traced]
1100 fn test_validation_marker_skips_previously_validated_values() {
1101 let executor = deterministic::Runner::default();
1102 executor.start(|context| async move {
1103 let cfg = test_config("marker-skip", NZU64!(4), FourCap);
1104
1105 let mut archive = Archive::init(context.child("seed"), cfg.clone())
1107 .await
1108 .unwrap();
1109 archive = archive.put(0, test_key("zero"), &10).await.unwrap();
1110 archive = archive.put(1, test_key("one"), &20).await.unwrap();
1111 archive = archive.sync().await.unwrap();
1112 drop(archive);
1113
1114 context.remove(&cfg.metadata_partition, None).await.unwrap();
1120 let archive = Archive::<_, _, FixedBytes<64>, i32>::init(
1121 context.child("first_open"),
1122 cfg.clone(),
1123 )
1124 .await
1125 .unwrap();
1126 assert_eq!(archive.ranges().collect::<Vec<_>>(), vec![(0, 1)]);
1127 drop(archive);
1128
1129 let archive = Archive::<_, _, FixedBytes<64>, i32>::init(
1130 context.child("second_open"),
1131 cfg.clone(),
1132 )
1133 .await
1134 .unwrap();
1135 assert_eq!(archive.get(Identifier::Index(0)).await.unwrap(), Some(10));
1136 assert_eq!(archive.get(Identifier::Index(1)).await.unwrap(), Some(20));
1137
1138 let archive = archive.put(2, test_key("two"), &30).await.unwrap();
1141 let archive = archive.sync().await.unwrap();
1142 drop(archive);
1143
1144 let archive =
1145 Archive::<_, _, FixedBytes<64>, i32>::init(context.child("third_open"), cfg)
1146 .await
1147 .unwrap();
1148 assert_eq!(archive.ranges().collect::<Vec<_>>(), vec![(0, 2)]);
1149 assert_eq!(archive.get(Identifier::Index(0)).await.unwrap(), Some(10));
1150 assert_eq!(archive.get(Identifier::Index(1)).await.unwrap(), Some(20));
1151 assert_eq!(archive.get(Identifier::Index(2)).await.unwrap(), Some(30));
1152 });
1153 }
1154
1155 #[test_traced]
1156 fn test_validation_marker_skips_covered_interior_values() {
1157 deterministic::Runner::default().start(|context| async move {
1158 let cfg = test_config("marker-covered-interior", NZU64!(4), FourCap);
1159
1160 let archive = Archive::init(context.child("seed"), cfg.clone())
1163 .await
1164 .unwrap();
1165 let archive = archive.put(0, test_key("zero"), &10).await.unwrap();
1166 let archive = archive.put(1, test_key("one"), &20).await.unwrap();
1167
1168 let archive = archive.sync().await.unwrap();
1170 let archive = archive.sync().await.unwrap();
1171 drop(archive);
1172
1173 corrupt_frame(
1176 &context,
1177 &cfg.value_partition,
1178 &0u64.to_be_bytes(),
1179 0,
1180 I32_VALUE_FRAME_SIZE,
1181 )
1182 .await;
1183 let archive = Archive::<_, _, FixedBytes<64>, i32>::init(context.child("reopen"), cfg)
1184 .await
1185 .unwrap();
1186 assert!(archive.get(Identifier::Index(0)).await.is_err());
1187 assert_eq!(archive.get(Identifier::Index(1)).await.unwrap(), Some(20));
1188 });
1189 }
1190
1191 #[test_traced]
1192 fn test_validation_marker_damage_never_mutates() {
1193 #[derive(Clone, Copy)]
1194 enum Damage {
1195 MissingIndex,
1196 MissingValues,
1197 TruncatedIndex,
1198 TruncatedValues,
1199 CorruptIndex,
1200 CorruptValues,
1201 }
1202
1203 deterministic::Runner::default().start(|context| async move {
1204 for (name, damage) in [
1208 ("missing_index", Damage::MissingIndex),
1209 ("missing_values", Damage::MissingValues),
1210 ("truncated_index", Damage::TruncatedIndex),
1211 ("truncated_values", Damage::TruncatedValues),
1212 ("corrupt_index", Damage::CorruptIndex),
1213 ("corrupt_values", Damage::CorruptValues),
1214 ] {
1215 let case = context.child(name);
1216 let cfg = test_config(name, NZU64!(4), FourCap);
1217
1218 let archive = Archive::init(case.child("seed"), cfg.clone())
1221 .await
1222 .unwrap();
1223 let archive = archive.put_sync(0, test_key("zero"), &10).await.unwrap();
1224 let archive = archive.sync().await.unwrap();
1225 drop(archive);
1226
1227 let (_, index_size) = context
1236 .open(&cfg.key_partition, &0u64.to_be_bytes())
1237 .await
1238 .unwrap();
1239 let (_, value_size) = context
1240 .open(&cfg.value_partition, &0u64.to_be_bytes())
1241 .await
1242 .unwrap();
1243 let damage_index = matches!(
1244 damage,
1245 Damage::MissingIndex | Damage::TruncatedIndex | Damage::CorruptIndex
1246 );
1247 let damaged_partition = if damage_index {
1248 &cfg.key_partition
1249 } else {
1250 &cfg.value_partition
1251 };
1252 let damaged_size = match damage {
1253 Damage::MissingIndex | Damage::MissingValues => {
1254 context
1255 .remove(damaged_partition, Some(&0u64.to_be_bytes()))
1256 .await
1257 .unwrap();
1258 None
1259 }
1260 Damage::TruncatedIndex | Damage::TruncatedValues => {
1261 let (blob, size) = context
1262 .open(damaged_partition, &0u64.to_be_bytes())
1263 .await
1264 .unwrap();
1265 let size = if damage_index { size - 1 } else { 0 };
1266 blob.resize(size).await.unwrap();
1267 blob.sync().await.unwrap();
1268 Some(size)
1269 }
1270 Damage::CorruptIndex | Damage::CorruptValues => {
1271 let (blob, size) = context
1272 .open(damaged_partition, &0u64.to_be_bytes())
1273 .await
1274 .unwrap();
1275 let byte = blob
1276 .read_at(0, 1, ReadOptions::default())
1277 .await
1278 .unwrap()
1279 .coalesce();
1280 let byte = byte.as_ref()[0];
1281 blob.write_at(0, vec![byte ^ 0xFF], WriteOptions::SYNC)
1282 .await
1283 .unwrap();
1284 Some(size)
1285 }
1286 };
1287
1288 if matches!(damage, Damage::CorruptValues) {
1291 for child in ["first", "second"] {
1292 let archive = Archive::<_, _, FixedBytes<64>, i32>::init(
1293 case.child(child),
1294 cfg.clone(),
1295 )
1296 .await
1297 .expect("marked value damage must not fail startup");
1298 assert!(archive.get(Identifier::Index(0)).await.is_err());
1299 drop(archive);
1300 let (_, size) = context
1301 .open(&cfg.value_partition, &0u64.to_be_bytes())
1302 .await
1303 .unwrap();
1304 assert_eq!(size, value_size, "adoption must preserve the damaged frame");
1305 }
1306 continue;
1307 }
1308
1309 for child in ["first", "second"] {
1312 let result =
1313 Archive::<_, _, FixedBytes<64>, i32>::init(case.child(child), cfg.clone())
1314 .await;
1315 assert!(
1316 matches!(result, Err(Error::Journal(JournalError::Corruption(_)))),
1317 "damaged marked section must remain visible as corruption"
1318 );
1319
1320 let (surviving_partition, surviving_size) = if damage_index {
1321 (&cfg.value_partition, value_size)
1322 } else {
1323 (&cfg.key_partition, index_size)
1324 };
1325 let (_, size) = context
1326 .open(surviving_partition, &0u64.to_be_bytes())
1327 .await
1328 .unwrap();
1329 assert_eq!(
1330 size, surviving_size,
1331 "failed startup must preserve the surviving journal section"
1332 );
1333 if let Some(damaged_size) = damaged_size {
1334 let (_, size) = context
1335 .open(damaged_partition, &0u64.to_be_bytes())
1336 .await
1337 .unwrap();
1338 assert_eq!(
1339 size, damaged_size,
1340 "failed startup must not normalize the damaged journal section"
1341 );
1342 }
1343 }
1344 }
1345 });
1346 }
1347
1348 #[test_traced]
1349 fn test_validation_floor_rejection_precedes_index_suffix_repair() {
1350 deterministic::Runner::default().start(|context| async move {
1351 let cfg = test_config("floor-order", NZU64!(4), FourCap);
1352
1353 let archive =
1356 Archive::<_, _, FixedBytes<64>, i32>::init(context.child("seed"), cfg.clone())
1357 .await
1358 .unwrap();
1359 let archive = archive.put_sync(0, test_key("zero"), &10).await.unwrap();
1360 let archive = archive.sync().await.unwrap();
1361 drop(archive);
1362
1363 let (index, index_size) = context
1366 .open(&cfg.key_partition, &0u64.to_be_bytes())
1367 .await
1368 .unwrap();
1369 index
1370 .write_at(index_size, vec![0xA5; 7], WriteOptions::SYNC)
1371 .await
1372 .unwrap();
1373 let expected_size = index_size + 7;
1374
1375 let byte = index
1378 .read_at(0, 1, ReadOptions::default())
1379 .await
1380 .unwrap()
1381 .coalesce();
1382 index
1383 .write_at(0, vec![byte.as_ref()[0] ^ 0xFF], WriteOptions::SYNC)
1384 .await
1385 .unwrap();
1386 let expected = index
1387 .read_at(0, expected_size as usize, ReadOptions::default())
1388 .await
1389 .unwrap()
1390 .coalesce();
1391 drop(index);
1392
1393 for child in ["first", "second"] {
1396 let result =
1397 Archive::<_, _, FixedBytes<64>, i32>::init(context.child(child), cfg.clone())
1398 .await;
1399 assert!(matches!(
1400 result,
1401 Err(Error::Journal(JournalError::Corruption(_)))
1402 ));
1403
1404 let (index, actual_size) = context
1405 .open(&cfg.key_partition, &0u64.to_be_bytes())
1406 .await
1407 .unwrap();
1408 assert_eq!(actual_size, expected_size);
1409 let actual = index
1410 .read_at(0, actual_size as usize, ReadOptions::default())
1411 .await
1412 .unwrap()
1413 .coalesce();
1414 assert_eq!(actual.as_ref(), expected.as_ref());
1415 }
1416 });
1417 }
1418
1419 #[test_traced]
1420 fn test_validation_marker_survives_torn_index_tail_rewrite() {
1421 let executor = deterministic::Runner::default();
1422 let (_, checkpoint) = executor.start_and_recover(|context| async move {
1423 let cfg = test_config("marker-torn-tail", NZU64!(4), FourCap);
1424 let archive = Archive::init(context.child("seed"), cfg.clone())
1425 .await
1426 .unwrap();
1427
1428 let archive = archive.put_sync(0, test_key("zero"), &10).await.unwrap();
1430 let archive = archive.sync().await.unwrap();
1431
1432 let page_size = usize::from(PAGE_SIZE.get());
1440 let physical_page_size = page_size + 12;
1441 let record_size = u64::SIZE + FixedBytes::<64>::SIZE + u64::SIZE + u32::SIZE;
1442 assert!(record_size < page_size);
1443 let old_page = context
1444 .logical_blob(&cfg.key_partition, &0u64.to_be_bytes())
1445 .unwrap();
1446 assert_eq!(old_page.len(), physical_page_size);
1447 let old_len =
1448 u16::from_be_bytes(old_page[page_size..page_size + 2].try_into().unwrap()) as usize;
1449 let old_crc =
1450 u32::from_be_bytes(old_page[page_size + 2..page_size + 6].try_into().unwrap());
1451 assert_eq!(old_len, record_size);
1452 assert_eq!(old_crc, Crc32::checksum(&old_page[..old_len]));
1453
1454 let archive = archive.put_sync(1, test_key("one"), &20).await.unwrap();
1458 drop(archive);
1459 let (index, size) = context
1460 .open(&cfg.key_partition, &0u64.to_be_bytes())
1461 .await
1462 .unwrap();
1463 assert_eq!(size, physical_page_size as u64);
1464 let new_page = index
1465 .read_at(0, physical_page_size, ReadOptions::default())
1466 .await
1467 .unwrap()
1468 .coalesce();
1469 let new_page = new_page.as_ref().to_vec();
1470 assert_eq!(
1471 &new_page[page_size..page_size + 6],
1472 &old_page[page_size..page_size + 6],
1473 );
1474 let new_len =
1475 u16::from_be_bytes(new_page[page_size + 6..page_size + 8].try_into().unwrap())
1476 as usize;
1477 let new_crc =
1478 u32::from_be_bytes(new_page[page_size + 8..page_size + 12].try_into().unwrap());
1479 assert_eq!(new_len, 2 * record_size);
1480 assert_eq!(new_crc, Crc32::checksum(&new_page[..new_len]));
1481
1482 index
1489 .write_at(0, old_page.clone(), WriteOptions::SYNC)
1490 .await
1491 .unwrap();
1492 let torn_prefix = page_size + 6 + 2;
1493 index
1494 .write_at(0, new_page[..torn_prefix].to_vec(), WriteOptions::SYNC)
1495 .await
1496 .unwrap();
1497 let torn_page = index
1498 .read_at(0, physical_page_size, ReadOptions::default())
1499 .await
1500 .unwrap()
1501 .coalesce();
1502 let torn_page = torn_page.as_ref();
1503 assert_eq!(
1504 &torn_page[page_size..page_size + 6],
1505 &old_page[page_size..page_size + 6],
1506 );
1507 assert_eq!(
1508 u16::from_be_bytes(torn_page[page_size + 6..page_size + 8].try_into().unwrap(),)
1509 as usize,
1510 new_len,
1511 );
1512 let torn_crc =
1513 u32::from_be_bytes(torn_page[page_size + 8..page_size + 12].try_into().unwrap());
1514 assert_ne!(torn_crc, Crc32::checksum(&torn_page[..new_len]));
1515 drop(index);
1516
1517 let (_, value_size) = context
1520 .open(&cfg.value_partition, &0u64.to_be_bytes())
1521 .await
1522 .unwrap();
1523 assert_eq!(value_size, 2 * I32_VALUE_FRAME_SIZE);
1524 });
1525
1526 deterministic::Runner::from(checkpoint).start(|context| async move {
1527 let pending = PendingSyncs::default();
1528 let delayed = DelayedSyncContext {
1529 inner: context.child("delayed"),
1530 pending: pending.clone(),
1531 };
1532 pending.arm();
1533 let cfg = test_config("marker-torn-tail", NZU64!(4), FourCap);
1534
1535 let archive = drive_pending_syncs(
1538 &pending,
1539 Archive::<_, _, FixedBytes<64>, i32>::init(delayed.child("reopen"), cfg.clone()),
1540 )
1541 .await
1542 .unwrap();
1543 assert_eq!(pending.calls(), 1);
1544 assert_eq!(archive.last_index(), Some(0));
1545 assert_eq!(archive.ranges().collect::<Vec<_>>(), vec![(0, 0)]);
1546 assert_eq!(archive.get(Identifier::Index(0)).await.unwrap(), Some(10));
1547 assert_eq!(archive.get(Identifier::Index(1)).await.unwrap(), None);
1548 drop(archive);
1549
1550 let (_, value_size) = context
1551 .open(&cfg.value_partition, &0u64.to_be_bytes())
1552 .await
1553 .unwrap();
1554 assert_eq!(value_size, I32_VALUE_FRAME_SIZE);
1555 });
1556 }
1557
1558 #[test_traced]
1559 fn test_startup_publishes_validated_marker_without_data_resync() {
1560 deterministic::Runner::default().start(|context| async move {
1561 let cfg = test_config("startup-order", NZU64!(4), FourCap);
1562
1563 let archive = Archive::init(context.child("seed"), cfg.clone())
1566 .await
1567 .unwrap();
1568 let archive = archive.put(0, test_key("zero"), &10).await.unwrap();
1569 let archive = archive.sync().await.unwrap();
1570 drop(archive);
1571
1572 let pending = PendingSyncs::default();
1575 let delayed = DelayedSyncContext {
1576 inner: context.child("delayed"),
1577 pending: pending.clone(),
1578 };
1579 pending.arm();
1580 let completed = Arc::new(AtomicUsize::new(0));
1581 let completed_clone = completed.clone();
1582 let reopen_cfg = cfg.clone();
1583 let task = context.child("startup").spawn(|_| async move {
1584 let result =
1585 Archive::<_, _, FixedBytes<64>, i32>::init(delayed.child("reopen"), reopen_cfg)
1586 .await;
1587 completed_clone.store(1, Ordering::Relaxed);
1588 result
1589 });
1590
1591 while pending.calls() == 0 && completed.load(Ordering::Relaxed) == 0 {
1592 commonware_runtime::reschedule().await;
1593 }
1594 commonware_runtime::reschedule().await;
1595
1596 let calls = pending.calls();
1599 let finished = completed.load(Ordering::Relaxed);
1600 if calls != 1 || finished != 1 {
1601 pending.unblock();
1602 let _ = task.await;
1603 panic!(
1604 "clean startup must return after starting one marker sync, calls={calls}, \
1605 finished={finished}"
1606 );
1607 }
1608
1609 pending.unblock();
1610 let archive = task.await.unwrap().unwrap().sync().await.unwrap();
1611 assert_eq!(archive.get(Identifier::Index(0)).await.unwrap(), Some(10));
1612 drop(archive);
1613
1614 Archive::<_, _, FixedBytes<64>, i32>::init(context.child("marker_reopen"), cfg)
1616 .await
1617 .unwrap()
1618 .destroy()
1619 .await
1620 .unwrap();
1621 });
1622 }
1623
1624 #[test_traced]
1625 fn test_startup_marker_failure_fails_next_sync() {
1626 deterministic::Runner::default().start(|context| async move {
1627 let cfg = test_config("startup-marker-failure", NZU64!(4), FourCap);
1628
1629 let archive = Archive::init(context.child("seed"), cfg.clone())
1631 .await
1632 .unwrap();
1633 let archive = archive.put(0, test_key("zero"), &10).await.unwrap();
1634 let archive = archive.sync().await.unwrap();
1635 drop(archive);
1636
1637 let pending = PendingSyncs::default();
1640 let delayed = DelayedSyncContext {
1641 inner: context.child("delayed"),
1642 pending: pending.clone(),
1643 };
1644 pending.arm();
1645 let archive = Archive::<_, _, FixedBytes<64>, i32>::init(delayed.child("reopen"), cfg)
1646 .await
1647 .unwrap();
1648 assert_eq!(pending.lock().len(), 1);
1649
1650 fail_pending_syncs(&pending);
1653 assert!(matches!(archive.sync().await, Err(Error::Metadata(_))));
1654 });
1655 }
1656
1657 #[test_traced]
1658 fn test_start_sync_publishes_closed_section_boundary() {
1659 let executor = deterministic::Runner::default();
1660 executor.start(|context| async move {
1661 let cfg = test_config("marker-lag", NZU64!(4), FourCap);
1662
1663 let pending = PendingSyncs::default();
1664 let delayed = DelayedSyncContext {
1665 inner: context.child("delayed"),
1666 pending: pending.clone(),
1667 };
1668 let archive = Archive::init(delayed.child("archive"), cfg.clone())
1669 .await
1670 .unwrap();
1671
1672 let (archive, first) = archive
1679 .put_start_sync(0, test_key("zero"), &10)
1680 .await
1681 .unwrap();
1682 assert_eq!(pending.lock().len(), 2);
1683 release_pending_syncs(&pending);
1684 first.await.unwrap();
1685
1686 let archive = archive.put(4, test_key("four"), &40).await.unwrap();
1687 let (archive, second) = archive.start_sync().await.unwrap();
1688 assert_eq!(pending.lock().len(), 3);
1689 release_pending_syncs(&pending);
1690 second.await.unwrap();
1691 drop(archive);
1692
1693 let archive = Archive::<_, _, FixedBytes<64>, i32>::init(
1696 context.child("first_reopen"),
1697 cfg.clone(),
1698 )
1699 .await
1700 .unwrap();
1701 assert_eq!(archive.ranges().collect::<Vec<_>>(), vec![(0, 0), (4, 4)]);
1702 drop(archive);
1703
1704 Archive::<_, _, FixedBytes<64>, i32>::init(context.child("second_reopen"), cfg)
1705 .await
1706 .unwrap()
1707 .destroy()
1708 .await
1709 .unwrap();
1710 });
1711 }
1712
1713 #[test_traced]
1714 fn test_sync_publishes_closed_section_boundary() {
1715 let executor = deterministic::Runner::default();
1716 executor.start(|context| async move {
1717 let cfg = test_config("blocking-marker-lag", NZU64!(4), FourCap);
1718
1719 let pending = PendingSyncs::default();
1720 let delayed = DelayedSyncContext {
1721 inner: context.child("delayed"),
1722 pending: pending.clone(),
1723 };
1724 let archive = Archive::init(delayed.child("archive"), cfg.clone())
1725 .await
1726 .unwrap();
1727
1728 let (archive, first) = archive
1730 .put_start_sync(0, test_key("zero"), &10)
1731 .await
1732 .unwrap();
1733 assert_eq!(pending.lock().len(), 2);
1734 release_pending_syncs(&pending);
1735 first.await.unwrap();
1736
1737 let archive = archive.put(4, test_key("four"), &40).await.unwrap();
1739 pending.arm();
1740 let completed = Arc::new(AtomicUsize::new(0));
1741 let completed_clone = completed.clone();
1742 let task = delayed.inner.child("sync").spawn(|_| async move {
1743 let archive = archive.sync().await.unwrap();
1744 completed_clone.store(1, Ordering::Relaxed);
1745 archive
1746 });
1747
1748 while pending.calls() < 2 {
1749 commonware_runtime::reschedule().await;
1750 }
1751 commonware_runtime::reschedule().await;
1752
1753 let parked_syncs = pending.lock().len();
1759 if parked_syncs != 3 {
1760 pending.unblock();
1763 let _ = task.await;
1764 panic!(
1765 "blocking sync must not serialize the derived marker behind data: \
1766 parked {parked_syncs} durability operations"
1767 );
1768 }
1769
1770 let metadata = pending.lock().remove(2);
1775 metadata.release.send(Ok(())).unwrap();
1776 commonware_runtime::reschedule().await;
1777 assert_eq!(
1778 completed.load(Ordering::Relaxed),
1779 0,
1780 "publishing the previous marker must not complete the current data sync"
1781 );
1782
1783 release_pending_syncs(&pending);
1784 let archive = task.await.unwrap();
1785 drop(archive);
1786
1787 let archive = Archive::<_, _, FixedBytes<64>, i32>::init(
1790 delayed.inner.child("first_reopen"),
1791 cfg.clone(),
1792 )
1793 .await
1794 .unwrap();
1795 assert_eq!(archive.get(Identifier::Index(0)).await.unwrap(), Some(10));
1796 assert_eq!(archive.get(Identifier::Index(4)).await.unwrap(), Some(40));
1797 drop(archive);
1798
1799 Archive::<_, _, FixedBytes<64>, i32>::init(delayed.inner.child("second_reopen"), cfg)
1800 .await
1801 .unwrap()
1802 .destroy()
1803 .await
1804 .unwrap();
1805 });
1806 }
1807
1808 #[test_traced]
1809 fn test_sync_delays_immediately_ready_durable_boundary() {
1810 let executor = deterministic::Runner::default();
1811 executor.start(|context| async move {
1812 let pending = PendingSyncs::default();
1814 pending.unblock();
1815 let immediate = DelayedSyncContext {
1816 inner: context,
1817 pending: pending.clone(),
1818 };
1819 let cfg = test_config("ready-marker-lag", NZU64!(4), FourCap);
1820
1821 let archive = Archive::init(immediate.child("archive"), cfg)
1822 .await
1823 .unwrap();
1824 let initial_starts = pending.starts();
1825 let archive = archive.put_sync(0, test_key("zero"), &10).await.unwrap();
1826
1827 assert_eq!(pending.starts() - initial_starts, 2);
1835
1836 let archive = archive.sync().await.unwrap();
1837 assert_eq!(pending.starts() - initial_starts, 3);
1838 archive.destroy().await.unwrap();
1839 });
1840 }
1841
1842 #[test_traced]
1843 fn test_sync_batches_markers_by_active_section() {
1844 let executor = deterministic::Runner::default();
1845 executor.start(|context| async move {
1846 let pending = PendingSyncs::default();
1848 pending.unblock();
1849 let immediate = DelayedSyncContext {
1850 inner: context,
1851 pending: pending.clone(),
1852 };
1853 let cfg = test_config("section-marker-batch", NZU64!(4), FourCap);
1854 let archive = Archive::init(immediate.child("archive"), cfg)
1855 .await
1856 .unwrap();
1857 let initial_starts = pending.starts();
1858
1859 let archive = archive.put_sync(0, test_key("zero"), &10).await.unwrap();
1862 let archive = archive.put_sync(1, test_key("one"), &20).await.unwrap();
1863 assert_eq!(pending.starts() - initial_starts, 4);
1864
1865 let archive = archive.put_sync(4, test_key("four"), &40).await.unwrap();
1869 assert_eq!(pending.starts() - initial_starts, 7);
1870 let archive = archive.sync().await.unwrap();
1871 assert_eq!(pending.starts() - initial_starts, 8);
1872 let archive = archive.sync().await.unwrap();
1873 assert_eq!(pending.starts() - initial_starts, 8);
1874 archive.destroy().await.unwrap();
1875 });
1876 }
1877
1878 #[test_traced]
1879 fn test_start_sync_withholds_marker_for_unproven_boundary() {
1880 let executor = deterministic::Runner::default();
1881 executor.start(|context| async move {
1882 let cfg = test_config("marker-unproven-boundary", NZU64!(4), FourCap);
1883
1884 let pending = PendingSyncs::default();
1885 let delayed = DelayedSyncContext {
1886 inner: context.child("delayed"),
1887 pending: pending.clone(),
1888 };
1889 let archive = Archive::init(delayed.child("archive"), cfg.clone())
1890 .await
1891 .unwrap();
1892
1893 let (archive, first) = archive
1895 .put_start_sync(0, test_key("zero"), &10)
1896 .await
1897 .unwrap();
1898 release_pending_syncs(&pending);
1899 first.await.unwrap();
1900
1901 let archive = archive.put(4, test_key("four"), &40).await.unwrap();
1903 let (archive, second) = archive.start_sync().await.unwrap();
1904 assert_eq!(pending.lock().len(), 3);
1905
1906 let marker = pending.lock().pop().expect("marker sync parked");
1910 marker
1911 .release
1912 .send(Ok(()))
1913 .expect("marker sync receiver dropped");
1914
1915 let archive = archive.put(8, test_key("eight"), &80).await.unwrap();
1921 let before = pending.starts();
1922 let (archive, third) = archive.start_sync().await.unwrap();
1923 assert_eq!(pending.starts() - before, 2);
1924
1925 release_pending_syncs(&pending);
1926 second.await.unwrap();
1927 third.await.unwrap();
1928
1929 let archive = drive_pending_syncs(&pending, archive.sync()).await.unwrap();
1932 drop(archive);
1933 let archive = Archive::<_, _, FixedBytes<64>, i32>::init(context.child("reopen"), cfg)
1934 .await
1935 .unwrap();
1936 assert_eq!(
1937 archive.ranges().collect::<Vec<_>>(),
1938 vec![(0, 0), (4, 4), (8, 8)]
1939 );
1940 archive.destroy().await.unwrap();
1941 });
1942 }
1943
1944 #[test_traced]
1945 fn test_sync_publishes_previous_durable_sections_across_section_changes() {
1946 let executor = deterministic::Runner::default();
1947 executor.start(|context| async move {
1948 let cfg = test_config("cross-section-marker-lag", NZU64!(1), FourCap);
1949
1950 let pending = PendingSyncs::default();
1951 let delayed = DelayedSyncContext {
1952 inner: context.child("delayed"),
1953 pending: pending.clone(),
1954 };
1955 let archive = Archive::init(delayed.child("archive"), cfg.clone())
1956 .await
1957 .unwrap();
1958
1959 let archive = drive_pending_syncs(&pending, archive.put_sync(0, test_key("zero"), &10))
1962 .await
1963 .unwrap();
1964 let archive = drive_pending_syncs(&pending, archive.put_sync(1, test_key("one"), &20))
1965 .await
1966 .unwrap();
1967 let archive = drive_pending_syncs(&pending, archive.put_sync(2, test_key("two"), &30))
1968 .await
1969 .unwrap();
1970 drop(archive);
1971
1972 let archive = Archive::<_, _, FixedBytes<64>, i32>::init(
1982 context.child("first_reopen"),
1983 cfg.clone(),
1984 )
1985 .await
1986 .unwrap();
1987 assert_eq!(archive.get(Identifier::Index(0)).await.unwrap(), Some(10));
1988 assert_eq!(archive.get(Identifier::Index(1)).await.unwrap(), Some(20));
1989 assert_eq!(archive.get(Identifier::Index(2)).await.unwrap(), Some(30));
1990 drop(archive);
1991
1992 Archive::<_, _, FixedBytes<64>, i32>::init(context.child("second_reopen"), cfg)
1993 .await
1994 .unwrap()
1995 .destroy()
1996 .await
1997 .unwrap();
1998 });
1999 }
2000
2001 #[test_traced]
2002 fn test_empty_sync_publishes_final_durable_boundary() {
2003 let executor = deterministic::Runner::default();
2004 executor.start(|context| async move {
2005 let cfg = test_config("empty-sync-marker", NZU64!(1), FourCap);
2006
2007 let pending = PendingSyncs::default();
2008 let delayed = DelayedSyncContext {
2009 inner: context.child("delayed"),
2010 pending: pending.clone(),
2011 };
2012 let archive = Archive::init(delayed.child("archive"), cfg.clone())
2013 .await
2014 .unwrap();
2015
2016 let archive = drive_pending_syncs(&pending, archive.put_sync(0, test_key("zero"), &10))
2018 .await
2019 .unwrap();
2020 assert_eq!(pending.starts(), 2);
2021
2022 let archive = drive_pending_syncs(&pending, archive.sync()).await.unwrap();
2028 assert_eq!(pending.starts(), 3);
2029
2030 let archive = drive_pending_syncs(&pending, archive.sync()).await.unwrap();
2032 assert_eq!(pending.starts(), 3);
2033 drop(archive);
2034
2035 let archive = Archive::<_, _, FixedBytes<64>, i32>::init(context.child("reopen"), cfg)
2036 .await
2037 .unwrap();
2038 assert_eq!(archive.get(Identifier::Index(0)).await.unwrap(), Some(10));
2039 });
2040 }
2041
2042 #[test_traced]
2043 fn test_sync_recreates_settled_section_barrier() {
2044 let executor = deterministic::Runner::default();
2045 executor.start(|context| async move {
2046 let cfg = test_config("marker-recreate", NZU64!(2), FourCap);
2047
2048 let pending = PendingSyncs::default();
2049 let delayed = DelayedSyncContext {
2050 inner: context.child("delayed"),
2051 pending: pending.clone(),
2052 };
2053 let archive = Archive::init(delayed.child("archive"), cfg.clone())
2054 .await
2055 .unwrap();
2056
2057 let archive = drive_pending_syncs(&pending, archive.put_sync(0, test_key("zero"), &10))
2061 .await
2062 .unwrap();
2063 assert_eq!(pending.starts(), 2);
2064 let archive = drive_pending_syncs(&pending, archive.put_sync(2, test_key("two"), &30))
2065 .await
2066 .unwrap();
2067 assert_eq!(pending.starts(), 5);
2068 let archive = drive_pending_syncs(&pending, archive.put_sync(1, test_key("one"), &20))
2069 .await
2070 .unwrap();
2071 assert_eq!(pending.starts(), 8);
2072
2073 let archive = drive_pending_syncs(&pending, archive.sync()).await.unwrap();
2075 assert_eq!(pending.starts(), 9);
2076 let archive = drive_pending_syncs(&pending, archive.sync()).await.unwrap();
2077 assert_eq!(pending.starts(), 9);
2078 drop(archive);
2079
2080 let archive = Archive::<_, _, FixedBytes<64>, i32>::init(context.child("reopen"), cfg)
2083 .await
2084 .unwrap();
2085 assert_eq!(archive.get(Identifier::Index(0)).await.unwrap(), Some(10));
2086 assert_eq!(archive.get(Identifier::Index(1)).await.unwrap(), Some(20));
2087 assert_eq!(archive.get(Identifier::Index(2)).await.unwrap(), Some(30));
2088 });
2089 }
2090
2091 #[test_traced]
2092 fn test_prune_clears_validation_marker_before_section_reuse() {
2093 let executor = deterministic::Runner::default();
2094 executor.start(|context| async move {
2095 let cfg = test_config("marker-reuse", NZU64!(2), FourCap);
2096
2097 let archive = Archive::init(context.child("seed"), cfg.clone())
2102 .await
2103 .unwrap();
2104 let archive = archive.put_sync(0, test_key("old"), &10).await.unwrap();
2105 let archive = archive.sync().await.unwrap();
2106 let archive = archive.prune(2).await.unwrap();
2107 drop(archive);
2108
2109 let pending = PendingSyncs::default();
2118 let delayed = DelayedSyncContext {
2119 inner: context.child("delayed"),
2120 pending: pending.clone(),
2121 };
2122 let archive = Archive::init(delayed.child("reuse"), cfg.clone())
2123 .await
2124 .unwrap();
2125 let archive = archive.put(0, test_key("new"), &20).await.unwrap();
2126 let (archive, handle) = archive.start_sync().await.unwrap();
2127 assert_eq!(pending.lock().len(), 2);
2128 release_pending_syncs(&pending);
2129 handle.await.unwrap();
2130 drop(archive);
2131
2132 let archive = Archive::<_, _, FixedBytes<64>, i32>::init(context.child("reopen"), cfg)
2133 .await
2134 .unwrap();
2135 assert_eq!(archive.get(Identifier::Index(0)).await.unwrap(), Some(20));
2136 });
2137 }
2138
2139 #[test_traced]
2140 fn test_archive_compression_then_none() {
2141 let executor = deterministic::Runner::default();
2143 executor.start(|context| async move {
2144 let cfg = Config {
2146 translator: FourCap,
2147 metadata_partition: "test-metadata".into(),
2148 key_partition: "test-index".into(),
2149 key_page_cache: CacheRef::new(PAGE_SIZE, PAGE_CACHE_SIZE),
2150 value_partition: "test-value".into(),
2151 codec_config: (),
2152 compression: Some(3),
2153 key_write_buffer: NZUsize!(DEFAULT_WRITE_BUFFER),
2154 value_write_buffer: NZUsize!(DEFAULT_WRITE_BUFFER),
2155 replay_buffer: NZUsize!(DEFAULT_REPLAY_BUFFER),
2156 items_per_section: NZU64!(DEFAULT_ITEMS_PER_SECTION),
2157 };
2158 let mut archive = Archive::init(context.child("first"), cfg.clone())
2159 .await
2160 .expect("Failed to initialize archive");
2161
2162 let index = 1u64;
2164 let key = test_key("testkey");
2165 let data = 1;
2166 archive = archive
2167 .put(index, key.clone(), &data)
2168 .await
2169 .expect("Failed to put data");
2170
2171 let archive = archive.sync().await.expect("Failed to sync archive");
2173 drop(archive);
2174
2175 let cfg = Config {
2178 translator: FourCap,
2179 metadata_partition: "test-metadata".into(),
2180 key_partition: "test-index".into(),
2181 key_page_cache: CacheRef::new(PAGE_SIZE, PAGE_CACHE_SIZE),
2182 value_partition: "test-value".into(),
2183 codec_config: (),
2184 compression: None,
2185 key_write_buffer: NZUsize!(DEFAULT_WRITE_BUFFER),
2186 value_write_buffer: NZUsize!(DEFAULT_WRITE_BUFFER),
2187 replay_buffer: NZUsize!(DEFAULT_REPLAY_BUFFER),
2188 items_per_section: NZU64!(DEFAULT_ITEMS_PER_SECTION),
2189 };
2190 let archive =
2191 Archive::<_, _, FixedBytes<64>, i32>::init(context.child("second"), cfg.clone())
2192 .await
2193 .unwrap();
2194
2195 let result: Result<Option<i32>, _> = archive.get(Identifier::Index(index)).await;
2199 assert!(matches!(
2200 result,
2201 Err(Error::Journal(JournalError::Codec(CodecError::ExtraData(
2202 _
2203 ))))
2204 ));
2205 });
2206 }
2207
2208 #[test_traced]
2209 fn test_archive_overlapping_key_basic() {
2210 let executor = deterministic::Runner::default();
2212 executor.start(|context| async move {
2213 let cfg = Config {
2215 translator: FourCap,
2216 metadata_partition: "test-metadata".into(),
2217 key_partition: "test-index".into(),
2218 key_page_cache: CacheRef::new(PAGE_SIZE, PAGE_CACHE_SIZE),
2219 value_partition: "test-value".into(),
2220 codec_config: (),
2221 compression: None,
2222 key_write_buffer: NZUsize!(DEFAULT_WRITE_BUFFER),
2223 value_write_buffer: NZUsize!(DEFAULT_WRITE_BUFFER),
2224 replay_buffer: NZUsize!(DEFAULT_REPLAY_BUFFER),
2225 items_per_section: NZU64!(DEFAULT_ITEMS_PER_SECTION),
2226 };
2227 let mut archive = Archive::init(context.child("storage"), cfg.clone())
2228 .await
2229 .expect("Failed to initialize archive");
2230
2231 let index1 = 1u64;
2232 let key1 = test_key("keys1");
2233 let data1 = 1;
2234 let index2 = 2u64;
2235 let key2 = test_key("keys2");
2236 let data2 = 2;
2237
2238 archive = archive
2240 .put(index1, key1.clone(), &data1)
2241 .await
2242 .expect("Failed to put data");
2243
2244 archive = archive
2246 .put(index2, key2.clone(), &data2)
2247 .await
2248 .expect("Failed to put data");
2249
2250 let retrieved = archive
2252 .get(Identifier::Key(&key1))
2253 .await
2254 .expect("Failed to get data")
2255 .expect("Data not found");
2256 assert_eq!(retrieved, data1);
2257
2258 let retrieved = archive
2260 .get(Identifier::Key(&key2))
2261 .await
2262 .expect("Failed to get data")
2263 .expect("Data not found");
2264 assert_eq!(retrieved, data2);
2265
2266 let buffer = context.encode();
2268 assert!(has_metric_value(&buffer, "items_tracked", 2));
2269 assert!(buffer.contains("unnecessary_reads_total 1"));
2270 assert!(buffer.contains("gets_total 2"));
2271 });
2272 }
2273
2274 #[test_traced]
2275 fn test_archive_overlapping_key_multiple_sections() {
2276 let executor = deterministic::Runner::default();
2278 executor.start(|context| async move {
2279 let cfg = Config {
2281 translator: FourCap,
2282 metadata_partition: "test-metadata".into(),
2283 key_partition: "test-index".into(),
2284 key_page_cache: CacheRef::new(PAGE_SIZE, PAGE_CACHE_SIZE),
2285 value_partition: "test-value".into(),
2286 codec_config: (),
2287 compression: None,
2288 key_write_buffer: NZUsize!(DEFAULT_WRITE_BUFFER),
2289 value_write_buffer: NZUsize!(DEFAULT_WRITE_BUFFER),
2290 replay_buffer: NZUsize!(DEFAULT_REPLAY_BUFFER),
2291 items_per_section: NZU64!(DEFAULT_ITEMS_PER_SECTION),
2292 };
2293 let mut archive = Archive::init(context.child("storage"), cfg.clone())
2294 .await
2295 .expect("Failed to initialize archive");
2296
2297 let index1 = 1u64;
2298 let key1 = test_key("keys1");
2299 let data1 = 1;
2300 let index2 = 2_000_000u64;
2301 let key2 = test_key("keys2");
2302 let data2 = 2;
2303
2304 archive = archive
2306 .put(index1, key1.clone(), &data1)
2307 .await
2308 .expect("Failed to put data");
2309
2310 archive = archive
2312 .put(index2, key2.clone(), &data2)
2313 .await
2314 .expect("Failed to put data");
2315
2316 let retrieved = archive
2318 .get(Identifier::Key(&key1))
2319 .await
2320 .expect("Failed to get data")
2321 .expect("Data not found");
2322 assert_eq!(retrieved, data1);
2323
2324 let retrieved = archive
2326 .get(Identifier::Key(&key2))
2327 .await
2328 .expect("Failed to get data")
2329 .expect("Data not found");
2330 assert_eq!(retrieved, data2);
2331 });
2332 }
2333
2334 #[test_traced]
2335 fn test_archive_prune_keys() {
2336 let executor = deterministic::Runner::default();
2338 executor.start(|context| async move {
2339 let cfg = Config {
2341 translator: FourCap,
2342 metadata_partition: "test-metadata".into(),
2343 key_partition: "test-index".into(),
2344 key_page_cache: CacheRef::new(PAGE_SIZE, PAGE_CACHE_SIZE),
2345 value_partition: "test-value".into(),
2346 codec_config: (),
2347 compression: None,
2348 key_write_buffer: NZUsize!(DEFAULT_WRITE_BUFFER),
2349 value_write_buffer: NZUsize!(DEFAULT_WRITE_BUFFER),
2350 replay_buffer: NZUsize!(DEFAULT_REPLAY_BUFFER),
2351 items_per_section: NZU64!(1), };
2353 let mut archive = Archive::init(context.child("storage"), cfg.clone())
2354 .await
2355 .expect("Failed to initialize archive");
2356
2357 let keys = vec![
2359 (1u64, test_key("key1-blah"), 1),
2360 (2u64, test_key("key2-blah"), 2),
2361 (3u64, test_key("key3-blah"), 3),
2362 (4u64, test_key("key3-bleh"), 3),
2363 (5u64, test_key("key4-blah"), 4),
2364 ];
2365
2366 for (index, key, data) in &keys {
2367 archive = archive
2368 .put(*index, key.clone(), data)
2369 .await
2370 .expect("Failed to put data");
2371 }
2372
2373 let buffer = context.encode();
2375 assert!(has_metric_value(&buffer, "items_tracked", 5));
2376
2377 archive = archive.prune(3).await.expect("Failed to prune");
2379
2380 for (index, key, data) in keys {
2382 let retrieved = archive
2383 .get(Identifier::Key(&key))
2384 .await
2385 .expect("Failed to get data");
2386 if index < 3 {
2387 assert!(retrieved.is_none());
2388 } else {
2389 assert_eq!(retrieved.expect("Data not found"), data);
2390 }
2391 }
2392
2393 let buffer = context.encode();
2395 assert!(has_metric_value(&buffer, "items_tracked", 3));
2396 assert!(has_metric_value(&buffer, "indices_pruned_total", 2));
2397 assert!(has_metric_value(&buffer, "keys_tracked", 3));
2398
2399 archive = archive.prune(2).await.expect("Failed to prune");
2401
2402 archive = archive.prune(3).await.expect("Failed to prune");
2404
2405 archive = archive
2407 .put(6, test_key("key2-blfh"), &5)
2408 .await
2409 .expect("Failed to put data");
2410
2411 let buffer = context.encode();
2413 assert!(has_metric_value(&buffer, "items_tracked", 4));
2414 assert!(has_metric_value(&buffer, "indices_pruned_total", 2));
2415 assert!(has_metric_value(&buffer, "keys_tracked", 4));
2416
2417 let archive = archive
2419 .put(1, test_key("key1-blah"), &1)
2420 .await
2421 .expect("Failed to put below floor");
2422 assert_eq!(
2423 archive
2424 .get(Identifier::Key(&test_key("key1-blah")))
2425 .await
2426 .expect("Failed to get data"),
2427 None
2428 );
2429
2430 let (archive, handle) = archive
2433 .put_start_sync(1, test_key("key1-blah"), &1)
2434 .await
2435 .expect("Failed to put_start_sync below floor");
2436 handle.await.expect("handle must resolve");
2437 let archive = archive
2438 .put_sync(2, test_key("key2-blfh"), &2)
2439 .await
2440 .expect("Failed to put_sync below floor");
2441 assert_eq!(archive.get(Identifier::Index(1)).await.unwrap(), None);
2442 assert_eq!(archive.get(Identifier::Index(2)).await.unwrap(), None);
2443 });
2444 }
2445
2446 fn test_archive_keys_and_restart(num_keys: usize) -> String {
2447 let executor = deterministic::Runner::default();
2449 executor.start(|mut context| async move {
2450 let items_per_section = 256u64;
2452 let cfg = Config {
2453 translator: TwoCap,
2454 metadata_partition: "test-metadata".into(),
2455 key_partition: "test-index".into(),
2456 key_page_cache: CacheRef::new(PAGE_SIZE, PAGE_CACHE_SIZE),
2457 value_partition: "test-value".into(),
2458 codec_config: (),
2459 compression: None,
2460 key_write_buffer: NZUsize!(DEFAULT_WRITE_BUFFER),
2461 value_write_buffer: NZUsize!(DEFAULT_WRITE_BUFFER),
2462 replay_buffer: NZUsize!(DEFAULT_REPLAY_BUFFER),
2463 items_per_section: NZU64!(items_per_section),
2464 };
2465 let mut archive = Archive::init(
2466 context.child("init").with_attribute("index", 1),
2467 cfg.clone(),
2468 )
2469 .await
2470 .expect("Failed to initialize archive");
2471
2472 let mut keys = BTreeMap::new();
2474 while keys.len() < num_keys {
2475 let index = keys.len() as u64;
2476 let mut key = [0u8; 64];
2477 context.fill(&mut key);
2478 let key = FixedBytes::new(key);
2479 let mut data = [0u8; 1024];
2480 context.fill(&mut data);
2481 let data = FixedBytes::new(data);
2482
2483 archive = archive
2484 .put(index, key.clone(), &data)
2485 .await
2486 .expect("Failed to put data");
2487 keys.insert(key, (index, data));
2488 }
2489
2490 for (key, (index, data)) in &keys {
2492 let retrieved = archive
2493 .get(Identifier::Index(*index))
2494 .await
2495 .expect("Failed to get data")
2496 .expect("Data not found");
2497 assert_eq!(&retrieved, data);
2498 let retrieved = archive
2499 .get(Identifier::Key(key))
2500 .await
2501 .expect("Failed to get data")
2502 .expect("Data not found");
2503 assert_eq!(&retrieved, data);
2504 }
2505
2506 let buffer = context.encode();
2508 assert!(has_metric_value(&buffer, "items_tracked", num_keys));
2509 assert!(has_metric_value(&buffer, "keys_tracked", num_keys));
2510
2511 let archive = archive.sync().await.expect("Failed to sync archive");
2513 drop(archive);
2514
2515 let cfg = Config {
2517 translator: TwoCap,
2518 metadata_partition: "test-metadata".into(),
2519 key_partition: "test-index".into(),
2520 key_page_cache: CacheRef::new(PAGE_SIZE, PAGE_CACHE_SIZE),
2521 value_partition: "test-value".into(),
2522 codec_config: (),
2523 compression: None,
2524 key_write_buffer: NZUsize!(DEFAULT_WRITE_BUFFER),
2525 value_write_buffer: NZUsize!(DEFAULT_WRITE_BUFFER),
2526 replay_buffer: NZUsize!(DEFAULT_REPLAY_BUFFER),
2527 items_per_section: NZU64!(items_per_section),
2528 };
2529 let mut archive = Archive::<_, _, _, FixedBytes<1024>>::init(
2530 context.child("init").with_attribute("index", 2),
2531 cfg.clone(),
2532 )
2533 .await
2534 .expect("Failed to initialize archive");
2535
2536 for (key, (index, data)) in &keys {
2538 let retrieved = archive
2539 .get(Identifier::Index(*index))
2540 .await
2541 .expect("Failed to get data")
2542 .expect("Data not found");
2543 assert_eq!(&retrieved, data);
2544 let retrieved = archive
2545 .get(Identifier::Key(key))
2546 .await
2547 .expect("Failed to get data")
2548 .expect("Data not found");
2549 assert_eq!(&retrieved, data);
2550 }
2551
2552 let min = (keys.len() / 2) as u64;
2554 archive = archive.prune(min).await.expect("Failed to prune");
2555
2556 let min = (min / items_per_section) * items_per_section;
2558 let mut removed = 0;
2559 for (key, (index, data)) in keys {
2560 if index >= min {
2561 let retrieved = archive
2562 .get(Identifier::Key(&key))
2563 .await
2564 .expect("Failed to get data")
2565 .expect("Data not found");
2566 assert_eq!(retrieved, data);
2567
2568 let (current_end, start_next) = archive.next_gap(index);
2570 assert_eq!(current_end.unwrap(), num_keys as u64 - 1);
2571 assert!(start_next.is_none());
2572 } else {
2573 let retrieved = archive
2574 .get(Identifier::Key(&key))
2575 .await
2576 .expect("Failed to get data");
2577 assert!(retrieved.is_none());
2578 removed += 1;
2579
2580 let (current_end, start_next) = archive.next_gap(index);
2582 assert!(current_end.is_none());
2583 assert_eq!(start_next.unwrap(), min);
2584 }
2585 }
2586
2587 let buffer = context.encode();
2589 assert!(has_metric_value(
2590 &buffer,
2591 "items_tracked",
2592 num_keys - removed
2593 ));
2594 assert!(has_metric_value(&buffer, "indices_pruned_total", removed));
2595 assert!(has_metric_value(
2596 &buffer,
2597 "keys_tracked",
2598 num_keys - removed
2599 ));
2600
2601 context.auditor().state()
2602 })
2603 }
2604
2605 #[test_group("slow")]
2606 #[test_traced]
2607 fn test_archive_many_keys_and_restart() {
2608 test_archive_keys_and_restart(100_000);
2609 }
2610
2611 #[test_group("slow")]
2612 #[test_traced]
2613 fn test_determinism() {
2614 let state1 = test_archive_keys_and_restart(5_000);
2615 let state2 = test_archive_keys_and_restart(5_000);
2616 assert_eq!(state1, state2);
2617 }
2618
2619 #[test_traced]
2626 fn test_archive_key_lookup_skips_pruned_duplicates() {
2627 let executor = deterministic::Runner::default();
2628 executor.start(|context| async move {
2629 let cfg = Config {
2630 translator: FourCap,
2631 metadata_partition: "test-metadata".into(),
2632 key_partition: "test-index".into(),
2633 key_page_cache: CacheRef::new(PAGE_SIZE, PAGE_CACHE_SIZE),
2634 value_partition: "test-value".into(),
2635 codec_config: (),
2636 compression: None,
2637 key_write_buffer: NZUsize!(DEFAULT_WRITE_BUFFER),
2638 value_write_buffer: NZUsize!(DEFAULT_WRITE_BUFFER),
2639 replay_buffer: NZUsize!(DEFAULT_REPLAY_BUFFER),
2640 items_per_section: NZU64!(1),
2641 };
2642 let mut archive = Archive::init(context.child("storage"), cfg)
2643 .await
2644 .expect("Failed to initialize archive");
2645
2646 let key = test_key("dupe-key");
2650 archive = archive.put(2, key.clone(), &20).await.unwrap();
2651 archive = archive.put(5, key.clone(), &50).await.unwrap();
2652
2653 assert!(archive.get(Identifier::Key(&key)).await.unwrap().is_some());
2657 assert!(archive.has(Identifier::Key(&key)).await.unwrap());
2658
2659 archive = archive.prune(3).await.unwrap();
2662 let got = archive.get(Identifier::Key(&key)).await.unwrap();
2663 assert_eq!(
2664 got,
2665 Some(50),
2666 "key lookup must skip the pruned entry and return the surviving one"
2667 );
2668 assert!(archive.has(Identifier::Key(&key)).await.unwrap());
2669
2670 let archive = archive.prune(6).await.unwrap();
2672 assert_eq!(archive.get(Identifier::Key(&key)).await.unwrap(), None);
2673 assert!(!archive.has(Identifier::Key(&key)).await.unwrap());
2674 });
2675 }
2676
2677 #[test_traced]
2678 fn test_get_all_after_prune() {
2679 let executor = deterministic::Runner::default();
2680 executor.start(|context| async move {
2681 let cfg = Config {
2682 translator: FourCap,
2683 metadata_partition: "test-metadata".into(),
2684 key_partition: "test-index".into(),
2685 key_page_cache: CacheRef::new(PAGE_SIZE, PAGE_CACHE_SIZE),
2686 value_partition: "test-value".into(),
2687 codec_config: (),
2688 compression: None,
2689 key_write_buffer: NZUsize!(DEFAULT_WRITE_BUFFER),
2690 value_write_buffer: NZUsize!(DEFAULT_WRITE_BUFFER),
2691 replay_buffer: NZUsize!(DEFAULT_REPLAY_BUFFER),
2692 items_per_section: NZU64!(1),
2693 };
2694 let mut archive = Archive::init(context.child("storage"), cfg)
2695 .await
2696 .expect("Failed to initialize archive");
2697
2698 archive = archive.put_multi(1, test_key("aaa"), &10).await.unwrap();
2699 archive = archive.put_multi(1, test_key("bbb"), &20).await.unwrap();
2700 archive = archive.put_multi(3, test_key("ccc"), &30).await.unwrap();
2701
2702 let archive = archive.prune(3).await.unwrap();
2704
2705 let all = archive.get_all(1).await.unwrap();
2707 assert_eq!(all, None);
2708
2709 let all = archive.get_all(3).await.unwrap();
2711 assert_eq!(all, Some(vec![30]));
2712 });
2713 }
2714
2715 #[test_traced]
2716 fn test_has_at() {
2717 let executor = deterministic::Runner::default();
2718 let (_, checkpoint) = executor.start_and_recover(|context| async move {
2719 let cfg = test_config("test", NZU64!(2), FourCap);
2720 let mut archive = Archive::init(context.child("storage"), cfg)
2721 .await
2722 .expect("Failed to initialize archive");
2723
2724 assert!(!archive.has_at(1, &test_key("aaaa1")).await.unwrap());
2726
2727 archive = archive.put_multi(1, test_key("aaaa1"), &10).await.unwrap();
2729 assert!(archive.has_at(1, &test_key("aaaa1")).await.unwrap());
2730
2731 assert!(!archive.has_at(2, &test_key("aaaa1")).await.unwrap());
2733
2734 assert!(!archive.has_at(1, &test_key("aaaa2")).await.unwrap());
2737
2738 archive = archive.put_multi(1, test_key("aaaa2"), &20).await.unwrap();
2740 assert!(archive.has_at(1, &test_key("aaaa1")).await.unwrap());
2741 assert!(archive.has_at(1, &test_key("aaaa2")).await.unwrap());
2742
2743 assert!(!archive.has_at(1, &test_key("bbbb")).await.unwrap());
2745
2746 archive = archive.put_multi(3, test_key("cccc"), &30).await.unwrap();
2747 archive.sync().await.unwrap();
2748 });
2749
2750 deterministic::Runner::from(checkpoint).start(|context| async move {
2751 let cfg = test_config("test", NZU64!(2), FourCap);
2752 let archive = Archive::<_, _, FixedBytes<64>, i32>::init(context.child("reopen"), cfg)
2753 .await
2754 .expect("Failed to reopen archive");
2755
2756 assert!(archive.has_at(1, &test_key("aaaa1")).await.unwrap());
2758 assert!(archive.has_at(1, &test_key("aaaa2")).await.unwrap());
2759 assert!(!archive.has_at(1, &test_key("bbbb")).await.unwrap());
2760
2761 let archive = archive.prune(2).await.unwrap();
2763 assert!(!archive.has_at(1, &test_key("aaaa1")).await.unwrap());
2764 assert!(!archive.has_at(1, &test_key("aaaa2")).await.unwrap());
2765 assert!(archive.has_at(3, &test_key("cccc")).await.unwrap());
2766
2767 archive.destroy().await.unwrap();
2768 });
2769 }
2770
2771 #[test_traced]
2772 fn test_has_key() {
2773 let executor = deterministic::Runner::default();
2774 executor.start(|context| async move {
2775 let cfg = test_config("test", NZU64!(2), FourCap);
2776 let mut archive = Archive::init(context.child("storage"), cfg)
2777 .await
2778 .expect("Failed to initialize archive");
2779
2780 let key = test_key("aaaa1");
2782 assert!(!archive.has(Identifier::Key(&key)).await.unwrap());
2783
2784 archive = archive.put(1, key.clone(), &10).await.unwrap();
2786 assert!(archive.has(Identifier::Key(&key)).await.unwrap());
2787
2788 let collision = test_key("aaaa2");
2791 assert!(!archive.has(Identifier::Key(&collision)).await.unwrap());
2792 archive = archive.put(2, collision.clone(), &20).await.unwrap();
2793 assert!(archive.has(Identifier::Key(&collision)).await.unwrap());
2794
2795 archive = archive.put(4, test_key("cccc"), &30).await.unwrap();
2799 let archive = archive.prune(4).await.unwrap();
2800 assert!(!archive.has(Identifier::Key(&key)).await.unwrap());
2801 assert!(!archive.has(Identifier::Key(&collision)).await.unwrap());
2802 assert!(
2803 archive
2804 .has(Identifier::Key(&test_key("cccc")))
2805 .await
2806 .unwrap()
2807 );
2808
2809 archive.destroy().await.unwrap();
2810 });
2811 }
2812
2813 #[test_traced]
2814 fn test_put_multi_prune() {
2815 let executor = deterministic::Runner::default();
2816 executor.start(|context| async move {
2817 let cfg = Config {
2818 translator: FourCap,
2819 metadata_partition: "test-metadata".into(),
2820 key_partition: "test-index".into(),
2821 key_page_cache: CacheRef::new(PAGE_SIZE, PAGE_CACHE_SIZE),
2822 value_partition: "test-value".into(),
2823 codec_config: (),
2824 compression: None,
2825 key_write_buffer: NZUsize!(DEFAULT_WRITE_BUFFER),
2826 value_write_buffer: NZUsize!(DEFAULT_WRITE_BUFFER),
2827 replay_buffer: NZUsize!(DEFAULT_REPLAY_BUFFER),
2828 items_per_section: NZU64!(1),
2829 };
2830 let mut archive = Archive::init(context.child("storage"), cfg)
2831 .await
2832 .expect("Failed to initialize archive");
2833
2834 archive = archive.put_multi(1, test_key("aaa"), &10).await.unwrap();
2836 archive = archive.put_multi(1, test_key("bbb"), &20).await.unwrap();
2837 archive = archive.put_multi(3, test_key("ccc"), &30).await.unwrap();
2838
2839 let buffer = context.encode();
2840 assert!(has_metric_value(&buffer, "items_tracked", 2));
2841
2842 let archive = archive.prune(3).await.unwrap();
2844
2845 assert_eq!(
2847 archive
2848 .get(Identifier::Key(&test_key("aaa")))
2849 .await
2850 .unwrap(),
2851 None
2852 );
2853 assert_eq!(
2854 archive
2855 .get(Identifier::Key(&test_key("bbb")))
2856 .await
2857 .unwrap(),
2858 None
2859 );
2860
2861 assert_eq!(
2863 archive
2864 .get(Identifier::Key(&test_key("ccc")))
2865 .await
2866 .unwrap(),
2867 Some(30)
2868 );
2869
2870 let buffer = context.encode();
2871 assert!(has_metric_value(&buffer, "items_tracked", 1));
2872 assert!(has_metric_value(&buffer, "indices_pruned_total", 1));
2873
2874 let archive = archive
2876 .put_multi(2, test_key("ddd"), &40)
2877 .await
2878 .expect("Failed to put below floor");
2879 assert_eq!(
2880 archive
2881 .get(Identifier::Key(&test_key("ddd")))
2882 .await
2883 .expect("Failed to get data"),
2884 None
2885 );
2886
2887 let (archive, handle) = archive
2890 .put_multi_start_sync(2, test_key("ddd"), &41)
2891 .await
2892 .expect("Failed to put_multi_start_sync below floor");
2893 handle.await.expect("handle must resolve");
2894 assert_eq!(archive.get_all(2).await.expect("Failed to get data"), None);
2895
2896 let archive = archive
2898 .put_multi_sync(2, test_key("ddd"), &42)
2899 .await
2900 .expect("Failed to put_multi_sync below floor");
2901 assert_eq!(archive.get_all(2).await.expect("Failed to get data"), None);
2902 });
2903 }
2904
2905 #[test_traced]
2906 fn test_prune_retires_keys() {
2907 let executor = deterministic::Runner::default();
2908 executor.start(|context| async move {
2909 let cfg = test_config("test", NZU64!(4), EightCap);
2912 let mut archive = Archive::init(context.child("storage"), cfg.clone())
2913 .await
2914 .expect("Failed to initialize archive");
2915 let key = |index: u64| test_key(&format!("{index:04x}"));
2916 for batch in 0..32u64 {
2917 let first = batch * 16;
2918 for index in first..first + 16 {
2919 archive = archive
2920 .put(index, key(index), &(index as i32))
2921 .await
2922 .expect("Failed to put data");
2923 }
2924 archive = archive.prune(first).await.expect("Failed to prune");
2925
2926 let buffer = context.encode();
2928 assert!(has_metric_value(&buffer, "items_tracked", 16));
2929 assert!(has_metric_value(&buffer, "keys_tracked", 16));
2930 assert_eq!(
2931 archive.get(Identifier::Key(&key(first))).await.unwrap(),
2932 Some(first as i32)
2933 );
2934 if first > 0 {
2935 assert_eq!(
2936 archive.get(Identifier::Key(&key(first - 1))).await.unwrap(),
2937 None
2938 );
2939 }
2940
2941 archive = archive.prune(first + 1).await.expect("Failed to prune");
2943 assert!(has_metric_value(&context.encode(), "keys_tracked", 16));
2944 }
2945
2946 archive.sync().await.expect("Failed to sync archive");
2948 let archive = Archive::init(context.child("reopened"), cfg)
2949 .await
2950 .expect("Failed to initialize archive");
2951 let buffer = context.encode();
2952 assert!(has_metric_value(&buffer, "reopened_keys_tracked", 16));
2953 assert_eq!(
2954 archive.get(Identifier::Key(&key(511))).await.unwrap(),
2955 Some(511)
2956 );
2957 assert_eq!(archive.get(Identifier::Key(&key(495))).await.unwrap(), None);
2958 let archive = archive.prune(512).await.expect("Failed to prune");
2959 let buffer = context.encode();
2960 assert!(has_metric_value(&buffer, "reopened_items_tracked", 0));
2961 assert!(has_metric_value(&buffer, "reopened_keys_tracked", 0));
2962 assert_eq!(archive.get(Identifier::Key(&key(511))).await.unwrap(), None);
2963 });
2964 }
2965
2966 #[test_traced]
2967 fn test_prune_keeps_colliding_retained_keys() {
2968 let executor = deterministic::Runner::default();
2969 executor.start(|context| async move {
2970 let cfg = test_config("test", NZU64!(4), FourCap);
2973 let mut archive = Archive::init(context.child("storage"), cfg.clone())
2974 .await
2975 .expect("Failed to initialize archive");
2976 archive = archive.put(1, test_key("head-pruned"), &10).await.unwrap();
2977 archive = archive.put(5, test_key("head-kept"), &50).await.unwrap();
2978 archive = archive
2979 .put_multi(5, test_key("tail-kept"), &51)
2980 .await
2981 .unwrap();
2982 archive = archive.put(2, test_key("tail-pruned"), &20).await.unwrap();
2983 archive = archive.put_multi(1, test_key("same"), &60).await.unwrap();
2984 archive = archive.put_multi(5, test_key("same"), &61).await.unwrap();
2985 archive = archive.prune(7).await.expect("Failed to prune");
2986
2987 let expected = [
2988 ("head-kept", Some(50)),
2989 ("tail-kept", Some(51)),
2990 ("same", Some(61)),
2991 ("head-pruned", None),
2992 ("tail-pruned", None),
2993 ];
2994 assert_eq!(archive.get_all(5).await.unwrap(), Some(vec![50, 51, 61]));
2995 for (key, value) in expected {
2996 assert_eq!(
2997 archive.get(Identifier::Key(&test_key(key))).await.unwrap(),
2998 value,
2999 "{key}"
3000 );
3001 }
3002 let buffer = context.encode();
3003 assert!(has_metric_value(&buffer, "items_tracked", 1));
3004 assert!(has_metric_value(&buffer, "keys_tracked", 3));
3005 assert!(has_metric_value(&buffer, "indices_pruned_total", 2));
3006
3007 archive.sync().await.expect("Failed to sync archive");
3009 let archive = Archive::init(context.child("reopened"), cfg)
3010 .await
3011 .expect("Failed to initialize archive");
3012 assert_eq!(archive.get_all(5).await.unwrap(), Some(vec![50, 51, 61]));
3013 for (key, value) in expected {
3014 assert_eq!(
3015 archive.get(Identifier::Key(&test_key(key))).await.unwrap(),
3016 value,
3017 "{key}"
3018 );
3019 }
3020 assert!(has_metric_value(
3021 &context.encode(),
3022 "reopened_keys_tracked",
3023 3
3024 ));
3025 });
3026 }
3027
3028 #[test_traced]
3029 fn test_positions_match_model() {
3030 let executor = deterministic::Runner::default();
3031 executor.start(|context| async move {
3032 let mut rng = context;
3035 let mut positions = storage::Positions::new();
3036 let mut model = BTreeMap::new();
3037 for step in 0..4_000u64 {
3038 let offset = match step {
3039 0..=99 => step,
3040 100..=199 => rng.random_range(0..100_000),
3041 _ => rng.random_range(0..2_000),
3042 };
3043 let inserted = positions.insert(offset, step);
3044 assert_eq!(inserted, !model.contains_key(&offset));
3045 if inserted {
3046 model.insert(offset, step);
3047 }
3048 assert_eq!(positions.len(), model.len() as u64);
3049 for probe in [offset, offset + 1, rng.random_range(0..100_000)] {
3050 assert_eq!(positions.get(probe), model.get(&probe).copied(), "{probe}");
3051 }
3052 }
3053 });
3054 }
3055
3056 #[test_traced]
3057 fn test_out_of_order_section() {
3058 let executor = deterministic::Runner::default();
3059 executor.start(|context| async move {
3060 let cfg = test_config("test", NZU64!(1 << 20), FourCap);
3063 let mut archive = Archive::init(context.child("storage"), cfg.clone())
3064 .await
3065 .expect("Failed to initialize archive");
3066 let indices = [0u64, 1, 2, 10, 5_000, 3, 7, 4];
3067 for index in indices {
3068 archive = archive
3069 .put(index, test_key(&format!("k{index}")), &(index as i32))
3070 .await
3071 .expect("Failed to put data");
3072 }
3073 archive = archive
3074 .put_multi(7, test_key("k7-dup"), &70)
3075 .await
3076 .expect("Failed to put_multi");
3077 archive = archive
3078 .put(6, test_key("k6"), &6)
3079 .await
3080 .expect("Failed to put data");
3081
3082 let check = |archive: Archive<_, _, _, i32>| async move {
3083 for index in indices.into_iter().chain([6]) {
3084 assert_eq!(
3085 archive.get(Identifier::Index(index)).await.unwrap(),
3086 Some(index as i32),
3087 "{index}"
3088 );
3089 assert_eq!(
3090 archive
3091 .get(Identifier::Key(&test_key(&format!("k{index}"))))
3092 .await
3093 .unwrap(),
3094 Some(index as i32),
3095 "{index}"
3096 );
3097 }
3098 for index in [5, 8, 9, 11, 4_999, 5_001] {
3099 assert_eq!(archive.get(Identifier::Index(index)).await.unwrap(), None);
3100 assert!(!archive.has(Identifier::Index(index)).await.unwrap());
3101 }
3102 assert_eq!(archive.get_all(7).await.unwrap(), Some(vec![7, 70]));
3103 assert!(archive.has_at(7, &test_key("k7-dup")).await.unwrap());
3104 assert!(!archive.has_at(7, &test_key("k6")).await.unwrap());
3105 assert!(!archive.has_at(8, &test_key("k7")).await.unwrap());
3106 assert_eq!(archive.next_gap(0), (Some(4), Some(6)));
3107 assert_eq!(archive.next_gap(6), (Some(7), Some(10)));
3108 archive
3109 };
3110 let archive = check(archive).await;
3111 let buffer = context.encode();
3112 assert!(has_metric_value(&buffer, "items_tracked", 9));
3113 assert!(has_metric_value(&buffer, "keys_tracked", 10));
3114
3115 archive.sync().await.expect("Failed to sync archive");
3117 let archive = Archive::init(context.child("reopened"), cfg)
3118 .await
3119 .expect("Failed to initialize archive");
3120 let buffer = context.encode();
3121 assert!(has_metric_value(&buffer, "reopened_items_tracked", 9));
3122 assert!(has_metric_value(&buffer, "reopened_keys_tracked", 10));
3123 check(archive).await;
3124 });
3125 }
3126
3127 #[test_traced]
3128 fn test_init_rejects_changed_items_per_section() {
3129 let executor = deterministic::Runner::default();
3130 executor.start(|context| async move {
3131 let mut archive = Archive::init(
3133 context.child("storage"),
3134 test_config("test", NZU64!(16), FourCap),
3135 )
3136 .await
3137 .expect("Failed to initialize archive");
3138 for index in 0..40u64 {
3139 archive = archive
3140 .put(index, test_key(&format!("k{index}")), &(index as i32))
3141 .await
3142 .expect("Failed to put data");
3143 }
3144 archive.sync().await.expect("Failed to sync archive");
3145
3146 let result = Archive::<_, _, FixedBytes<64>, i32>::init(
3147 context.child("reopened"),
3148 test_config("test", NZU64!(32), FourCap),
3149 )
3150 .await;
3151 assert!(matches!(result, Err(Error::RecordCorrupted)));
3152 });
3153 }
3154}