1use super::manager::{AppendFactory, Config as ManagerConfig, Manager};
24use crate::journal::Error;
25use commonware_codec::{CodecFixed, CodecFixedShared, DecodeExt as _, ReadExt as _};
26use commonware_runtime::{
27 buffer::paged::{CacheRef, Replay},
28 Blob, Buf, Handle, Metrics, Storage,
29};
30use commonware_utils::NZUsize;
31use futures::{
32 stream::{self, Stream},
33 StreamExt,
34};
35use std::{marker::PhantomData, num::NonZeroUsize};
36use tracing::{trace, warn};
37
38struct ReplayState<B: Blob> {
40 section: u64,
41 replay: Replay<B>,
42 position: u64,
43 done: bool,
44}
45
46#[derive(Clone)]
48pub struct Config {
49 pub partition: String,
51
52 pub page_cache: CacheRef,
54
55 pub write_buffer: NonZeroUsize,
57}
58
59pub struct Journal<E: Storage + Metrics, A: CodecFixed> {
73 manager: Manager<E, AppendFactory>,
74 _array: PhantomData<A>,
75}
76
77impl<E: Storage + Metrics, A: CodecFixedShared> Journal<E, A> {
78 pub const CHUNK_SIZE: usize = A::SIZE;
80 const CHUNK_SIZE_U64: u64 = Self::CHUNK_SIZE as u64;
81
82 pub async fn init(context: E, cfg: Config) -> Result<Self, Error> {
87 let manager_cfg = ManagerConfig {
88 partition: cfg.partition,
89 factory: AppendFactory {
90 write_buffer: cfg.write_buffer,
91 page_cache_ref: cfg.page_cache,
92 },
93 };
94 let mut manager = Manager::init(context, manager_cfg).await?;
95
96 let sections: Vec<_> = manager.sections().collect();
98 for section in sections {
99 let size = manager.size(section)?;
100 if !size.is_multiple_of(Self::CHUNK_SIZE_U64) {
101 let valid_size = size - (size % Self::CHUNK_SIZE_U64);
102 warn!(
103 section,
104 invalid_size = size,
105 new_size = valid_size,
106 "trailing bytes detected: truncating"
107 );
108 manager.rewind_section(section, valid_size).await?;
109 manager.sync(section).await?;
112 }
113 }
114
115 Ok(Self {
116 manager,
117 _array: PhantomData,
118 })
119 }
120
121 pub async fn append(&mut self, section: u64, item: &A) -> Result<u64, Error> {
125 let blob = self.manager.get_or_create(section).await?;
126
127 let buf = item.encode_mut();
129 let offset = blob.append(&buf).await?;
130 if !offset.is_multiple_of(Self::CHUNK_SIZE_U64) {
131 return Err(Error::InvalidBlobSize(section, offset));
132 }
133 let position = offset / Self::CHUNK_SIZE_U64;
134 trace!(section, position, "appended item");
135
136 Ok(position)
137 }
138
139 pub async fn get(&self, section: u64, position: u64) -> Result<A, Error> {
147 let blob = self
148 .manager
149 .get(section)?
150 .ok_or(Error::SectionOutOfRange(section))?;
151
152 let offset = position
153 .checked_mul(Self::CHUNK_SIZE_U64)
154 .ok_or(Error::ItemOutOfRange(position))?;
155
156 let buf = blob
158 .read_at(offset, Self::CHUNK_SIZE)
159 .await
160 .map_err(|err| match err {
161 commonware_runtime::Error::BlobInsufficientLength
162 | commonware_runtime::Error::OffsetOverflow => Error::ItemOutOfRange(position),
163 err => Error::Runtime(err),
164 })?;
165 A::decode(buf.coalesce()).map_err(Error::Codec)
166 }
167
168 pub async fn get_many(
176 &self,
177 section: u64,
178 positions: &[u64],
179 buf: &mut [u8],
180 ) -> Result<(Vec<A>, usize), Error> {
181 assert!(
182 positions.is_sorted_by(|a, b| a < b),
183 "positions must be strictly increasing"
184 );
185 if positions.is_empty() {
186 return Ok((Vec::new(), 0));
187 }
188 assert!(
189 buf.len() >= positions.len() * Self::CHUNK_SIZE,
190 "get_many requires buf.len() >= positions.len() * CHUNK_SIZE"
191 );
192 let buf = &mut buf[..positions.len() * Self::CHUNK_SIZE];
193 let blob = self
194 .manager
195 .get(section)?
196 .ok_or(Error::SectionOutOfRange(section))?;
197
198 let offsets: Vec<u64> = positions
199 .iter()
200 .map(|&p| {
201 p.checked_mul(Self::CHUNK_SIZE_U64)
202 .ok_or(Error::ItemOutOfRange(p))
203 })
204 .collect::<Result<_, _>>()?;
205
206 let hits = blob
207 .read_many_into(buf, &offsets, NZUsize!(Self::CHUNK_SIZE))
208 .await?;
209
210 let mut items = Vec::with_capacity(positions.len());
211 for i in 0..positions.len() {
212 let slice = &buf[i * Self::CHUNK_SIZE..(i + 1) * Self::CHUNK_SIZE];
213 items.push(A::decode(slice).map_err(Error::Codec)?);
214 }
215 Ok((items, hits))
216 }
217
218 pub fn try_get_sync(&self, section: u64, position: u64) -> Option<A> {
220 let blob = self.manager.get(section).ok()??;
221 let offset = position.checked_mul(Self::CHUNK_SIZE_U64)?;
222 let remaining = blob.size().checked_sub(offset)?;
223 if remaining < Self::CHUNK_SIZE_U64 {
224 return None;
225 }
226 let mut buf = vec![0u8; Self::CHUNK_SIZE];
227 if !blob.try_read_sync_into(&mut buf, offset) {
228 return None;
229 }
230 A::decode(&buf[..]).ok()
231 }
232
233 pub async fn last(&self, section: u64) -> Result<Option<A>, Error> {
242 let blob = self
243 .manager
244 .get(section)?
245 .ok_or(Error::SectionOutOfRange(section))?;
246
247 let size = blob.size();
248 if size < Self::CHUNK_SIZE_U64 {
249 return Ok(None);
250 }
251
252 let last_position = (size / Self::CHUNK_SIZE_U64) - 1;
253 let offset = last_position * Self::CHUNK_SIZE_U64;
254 let buf = blob.read_at(offset, Self::CHUNK_SIZE).await?;
255 A::decode(buf.coalesce()).map_err(Error::Codec).map(Some)
256 }
257
258 pub async fn replay(
262 &mut self,
263 start_section: u64,
264 start_position: u64,
265 buffer: NonZeroUsize,
266 ) -> Result<impl Stream<Item = Result<(u64, u64, A), Error>> + Send + '_, Error> {
267 let mut blob_info = Vec::new();
270 for (§ion, blob) in self.manager.sections_from(start_section) {
271 let blob_size = blob.size();
272 let mut replay = blob.replay(buffer).await?;
273 let initial_position = if section == start_section {
275 let start = start_position
276 .checked_mul(Self::CHUNK_SIZE_U64)
277 .ok_or(Error::ItemOutOfRange(start_position))?;
278 if start > blob_size {
279 return Err(Error::ItemOutOfRange(start_position));
280 }
281 replay.seek_to(start)?;
282 start_position
283 } else {
284 0
285 };
286 blob_info.push((section, replay, initial_position));
287 }
288
289 Ok(
293 stream::iter(blob_info).flat_map(move |(section, replay, initial_position)| {
294 stream::unfold(
295 ReplayState {
296 section,
297 replay,
298 position: initial_position,
299 done: false,
300 },
301 move |mut state| async move {
302 if state.done {
303 return None;
304 }
305
306 let mut batch: Vec<Result<(u64, u64, A), Error>> = Vec::new();
307 loop {
308 match state.replay.ensure(Self::CHUNK_SIZE).await {
310 Ok(true) => {}
311 Ok(false) => {
312 state.done = true;
314 return if batch.is_empty() {
315 None
316 } else {
317 Some((batch, state))
318 };
319 }
320 Err(err) => {
321 batch.push(Err(Error::Runtime(err)));
322 state.done = true;
323 return Some((batch, state));
324 }
325 }
326
327 while state.replay.remaining() >= Self::CHUNK_SIZE {
329 match A::read(&mut state.replay) {
330 Ok(item) => {
331 batch.push(Ok((state.section, state.position, item)));
332 state.position += 1;
333 }
334 Err(err) => {
335 batch.push(Err(Error::Codec(err)));
336 state.done = true;
337 return Some((batch, state));
338 }
339 }
340 }
341
342 if !batch.is_empty() {
344 return Some((batch, state));
345 }
346 }
347 },
348 )
349 .flat_map(stream::iter)
350 }),
351 )
352 }
353
354 pub async fn sync(&mut self, sections: impl crate::Sections) -> Result<(), Error> {
356 self.manager.sync(sections).await
357 }
358
359 pub async fn start_sync(
361 &mut self,
362 sections: impl crate::Sections,
363 ) -> Result<Handle<()>, Error> {
364 self.manager.start_sync(sections).await
365 }
366
367 pub async fn sync_all(&mut self) -> Result<(), Error> {
369 self.manager.sync_all().await
370 }
371
372 pub async fn prune(&mut self, min: u64) -> Result<bool, Error> {
374 self.manager.prune(min).await
375 }
376
377 pub fn oldest_section(&self) -> Option<u64> {
379 self.manager.oldest_section()
380 }
381
382 pub fn newest_section(&self) -> Option<u64> {
384 self.manager.newest_section()
385 }
386
387 pub fn sections(&self) -> impl Iterator<Item = u64> + '_ {
389 self.manager.sections()
390 }
391
392 pub fn section_len(&self, section: u64) -> Result<u64, Error> {
394 let size = self.manager.size(section)?;
395 Ok(size / Self::CHUNK_SIZE_U64)
396 }
397
398 pub fn size(&self, section: u64) -> Result<u64, Error> {
400 self.manager.size(section)
401 }
402
403 pub async fn rewind(&mut self, section: u64, offset: u64) -> Result<(), Error> {
408 self.manager.rewind(section, offset).await
409 }
410
411 pub async fn rewind_section(&mut self, section: u64, size: u64) -> Result<(), Error> {
415 self.manager.rewind_section(section, size).await
416 }
417
418 pub async fn destroy(self) -> Result<(), Error> {
420 self.manager.destroy().await
421 }
422
423 pub async fn clear(&mut self) -> Result<(), Error> {
427 self.manager.clear().await
428 }
429}
430
431#[cfg(test)]
432mod tests {
433 use super::*;
434 use commonware_cryptography::{sha256::Digest, Hasher as _, Sha256};
435 use commonware_macros::test_traced;
436 use commonware_runtime::{
437 buffer::paged::CacheRef,
438 deterministic,
439 mocks::{fail_pending_syncs, release_pending_syncs, DelayedSyncContext, PendingSyncs},
440 BufferPooler, Error as RError, Runner, Spawner as _, Supervisor as _,
441 };
442 use commonware_utils::{NZUsize, NZU16};
443 use core::num::NonZeroU16;
444 use futures::{pin_mut, StreamExt};
445 use std::sync::{
446 atomic::{AtomicUsize, Ordering},
447 Arc,
448 };
449
450 const PAGE_SIZE: NonZeroU16 = NZU16!(44);
451 const PAGE_CACHE_SIZE: NonZeroUsize = NZUsize!(3);
452
453 fn test_digest(value: u64) -> Digest {
454 Sha256::hash(&value.to_be_bytes())
455 }
456
457 fn test_cfg(pooler: &impl BufferPooler) -> Config {
458 Config {
459 partition: "test-partition".into(),
460 page_cache: CacheRef::from_pooler(pooler, PAGE_SIZE, PAGE_CACHE_SIZE),
461 write_buffer: NZUsize!(2048),
462 }
463 }
464
465 #[test_traced]
466 fn test_segmented_fixed_append_and_get() {
467 let executor = deterministic::Runner::default();
468 executor.start(|context| async move {
469 let cfg = test_cfg(&context);
470 let mut journal = Journal::init(context.child("storage"), cfg.clone())
471 .await
472 .expect("failed to init");
473
474 let pos0 = journal
475 .append(1, &test_digest(0))
476 .await
477 .expect("failed to append");
478 assert_eq!(pos0, 0);
479
480 let pos1 = journal
481 .append(1, &test_digest(1))
482 .await
483 .expect("failed to append");
484 assert_eq!(pos1, 1);
485
486 let pos2 = journal
487 .append(2, &test_digest(2))
488 .await
489 .expect("failed to append");
490 assert_eq!(pos2, 0);
491
492 let item0 = journal.get(1, 0).await.expect("failed to get");
493 assert_eq!(item0, test_digest(0));
494
495 let item1 = journal.get(1, 1).await.expect("failed to get");
496 assert_eq!(item1, test_digest(1));
497
498 let item2 = journal.get(2, 0).await.expect("failed to get");
499 assert_eq!(item2, test_digest(2));
500
501 let err = journal.get(1, 2).await;
502 assert!(matches!(err, Err(Error::ItemOutOfRange(2))));
503
504 let err = journal.get(3, 0).await;
505 assert!(matches!(err, Err(Error::SectionOutOfRange(3))));
506
507 journal.destroy().await.expect("failed to destroy");
508 });
509 }
510
511 #[test_traced]
512 fn test_segmented_fixed_replay() {
513 let executor = deterministic::Runner::default();
514 executor.start(|context| async move {
515 let cfg = test_cfg(&context);
516 let mut journal = Journal::init(context.child("first"), cfg.clone())
517 .await
518 .expect("failed to init");
519
520 for i in 0u64..10 {
521 journal
522 .append(1, &test_digest(i))
523 .await
524 .expect("failed to append");
525 }
526 for i in 10u64..20 {
527 journal
528 .append(2, &test_digest(i))
529 .await
530 .expect("failed to append");
531 }
532
533 journal.sync_all().await.expect("failed to sync");
534 drop(journal);
535
536 let mut journal = Journal::<_, Digest>::init(context.child("second"), cfg.clone())
537 .await
538 .expect("failed to re-init");
539
540 let items = {
541 let stream = journal
542 .replay(0, 0, NZUsize!(1024))
543 .await
544 .expect("failed to replay");
545 pin_mut!(stream);
546
547 let mut items = Vec::new();
548 while let Some(result) = stream.next().await {
549 match result {
550 Ok((section, pos, item)) => items.push((section, pos, item)),
551 Err(err) => panic!("replay error: {err}"),
552 }
553 }
554 items
555 };
556
557 assert_eq!(items.len(), 20);
558 for (i, item) in items.iter().enumerate().take(10) {
559 assert_eq!(item.0, 1);
560 assert_eq!(item.1, i as u64);
561 assert_eq!(item.2, test_digest(i as u64));
562 }
563 for (i, item) in items.iter().enumerate().skip(10).take(10) {
564 assert_eq!(item.0, 2);
565 assert_eq!(item.1, (i - 10) as u64);
566 assert_eq!(item.2, test_digest(i as u64));
567 }
568
569 journal.destroy().await.expect("failed to destroy");
570 });
571 }
572
573 #[test_traced]
574 fn test_segmented_fixed_replay_with_start_offset() {
575 let executor = deterministic::Runner::default();
577 executor.start(|context| async move {
578 let cfg = test_cfg(&context);
579 let mut journal = Journal::init(context.child("first"), cfg.clone())
580 .await
581 .expect("failed to init");
582
583 for i in 0u64..10 {
585 journal
586 .append(1, &test_digest(i))
587 .await
588 .expect("failed to append");
589 }
590 for i in 10u64..15 {
592 journal
593 .append(2, &test_digest(i))
594 .await
595 .expect("failed to append");
596 }
597 journal.sync_all().await.expect("failed to sync");
598 drop(journal);
599
600 let mut journal = Journal::<_, Digest>::init(context.child("second"), cfg.clone())
601 .await
602 .expect("failed to re-init");
603
604 {
606 let stream = journal
607 .replay(1, 5, NZUsize!(1024))
608 .await
609 .expect("failed to replay");
610 pin_mut!(stream);
611
612 let mut items = Vec::new();
613 while let Some(result) = stream.next().await {
614 let (section, pos, item) = result.expect("replay error");
615 items.push((section, pos, item));
616 }
617
618 assert_eq!(
619 items.len(),
620 10,
621 "Should have 5 items from section 1 + 5 from section 2"
622 );
623
624 for (i, (section, pos, item)) in items.iter().enumerate().take(5) {
626 assert_eq!(*section, 1);
627 assert_eq!(*pos, (i + 5) as u64);
628 assert_eq!(*item, test_digest((i + 5) as u64));
629 }
630
631 for (i, (section, pos, item)) in items.iter().enumerate().skip(5) {
633 assert_eq!(*section, 2);
634 assert_eq!(*pos, (i - 5) as u64);
635 assert_eq!(*item, test_digest((i + 5) as u64));
636 }
637 }
638
639 {
641 let stream = journal
642 .replay(1, 9, NZUsize!(1024))
643 .await
644 .expect("failed to replay");
645 pin_mut!(stream);
646
647 let mut items = Vec::new();
648 while let Some(result) = stream.next().await {
649 let (section, pos, item) = result.expect("replay error");
650 items.push((section, pos, item));
651 }
652
653 assert_eq!(
654 items.len(),
655 6,
656 "Should have 1 item from section 1 + 5 from section 2"
657 );
658 assert_eq!(items[0], (1, 9, test_digest(9)));
659 for (i, (section, pos, item)) in items.iter().enumerate().skip(1) {
660 assert_eq!(*section, 2);
661 assert_eq!(*pos, (i - 1) as u64);
662 assert_eq!(*item, test_digest((i + 9) as u64));
663 }
664 }
665
666 {
668 let stream = journal
669 .replay(2, 3, NZUsize!(1024))
670 .await
671 .expect("failed to replay");
672 pin_mut!(stream);
673
674 let mut items = Vec::new();
675 while let Some(result) = stream.next().await {
676 let (section, pos, item) = result.expect("replay error");
677 items.push((section, pos, item));
678 }
679
680 assert_eq!(items.len(), 2, "Should have 2 items from section 2");
681 assert_eq!(items[0], (2, 3, test_digest(13)));
682 assert_eq!(items[1], (2, 4, test_digest(14)));
683 }
684
685 let result = journal.replay(1, 100, NZUsize!(1024)).await;
687 assert!(matches!(result, Err(Error::ItemOutOfRange(100))));
688 drop(result);
689
690 let result = journal.replay(1, u64::MAX, NZUsize!(1024)).await;
691 assert!(matches!(result, Err(Error::ItemOutOfRange(u64::MAX))));
692 drop(result);
693
694 journal.destroy().await.expect("failed to destroy");
695 });
696 }
697
698 #[test_traced]
699 fn test_segmented_fixed_prune() {
700 let executor = deterministic::Runner::default();
701 executor.start(|context| async move {
702 let cfg = test_cfg(&context);
703 let mut journal = Journal::init(context.child("storage"), cfg.clone())
704 .await
705 .expect("failed to init");
706
707 for section in 1u64..=5 {
708 journal
709 .append(section, &test_digest(section))
710 .await
711 .expect("failed to append");
712 }
713 journal.sync_all().await.expect("failed to sync");
714
715 journal.prune(3).await.expect("failed to prune");
716
717 let err = journal.get(1, 0).await;
718 assert!(matches!(err, Err(Error::AlreadyPrunedToSection(3))));
719
720 let err = journal.get(2, 0).await;
721 assert!(matches!(err, Err(Error::AlreadyPrunedToSection(3))));
722
723 let item = journal.get(3, 0).await.expect("should exist");
724 assert_eq!(item, test_digest(3));
725
726 journal.destroy().await.expect("failed to destroy");
727 });
728 }
729
730 #[test_traced]
731 fn test_segmented_fixed_rewind() {
732 let executor = deterministic::Runner::default();
733 executor.start(|context| async move {
734 let cfg = test_cfg(&context);
735 let mut journal = Journal::init(context.child("storage"), cfg.clone())
736 .await
737 .expect("failed to init");
738
739 for section in 1u64..=3 {
741 journal
742 .append(section, &test_digest(section))
743 .await
744 .expect("failed to append");
745 }
746 journal.sync_all().await.expect("failed to sync");
747
748 for section in 1u64..=3 {
750 let size = journal.size(section).expect("failed to get size");
751 assert!(size > 0, "section {section} should have data");
752 }
753
754 let size = journal.size(1).expect("failed to get size");
756 journal.rewind(1, size).await.expect("failed to rewind");
757
758 let size = journal.size(1).expect("failed to get size");
760 assert!(size > 0, "section 1 should still have data");
761
762 for section in 2u64..=3 {
764 let size = journal.size(section).expect("failed to get size");
765 assert_eq!(size, 0, "section {section} should be removed");
766 }
767
768 let item = journal.get(1, 0).await.expect("failed to get");
770 assert_eq!(item, test_digest(1));
771
772 journal.destroy().await.expect("failed to destroy");
773 });
774 }
775
776 #[test_traced]
777 fn test_segmented_fixed_rewind_max_section() {
778 let executor = deterministic::Runner::default();
779 executor.start(|context| async move {
780 let cfg = test_cfg(&context);
781 let mut journal = Journal::init(context.child("storage"), cfg.clone())
782 .await
783 .expect("failed to init");
784
785 journal
787 .append(u64::MAX, &test_digest(0))
788 .await
789 .expect("failed to append");
790 journal.sync_all().await.expect("failed to sync");
791
792 let size = journal.size(u64::MAX).expect("failed to get size");
794 journal
795 .rewind(u64::MAX, size)
796 .await
797 .expect("failed to rewind");
798
799 assert_eq!(journal.size(u64::MAX).expect("failed to get size"), size);
801 assert_eq!(journal.get(u64::MAX, 0).await.unwrap(), test_digest(0));
802
803 journal.destroy().await.expect("failed to destroy");
804 });
805 }
806
807 #[test_traced]
808 fn test_segmented_fixed_rewind_many_sections() {
809 let executor = deterministic::Runner::default();
810 executor.start(|context| async move {
811 let cfg = test_cfg(&context);
812 let mut journal = Journal::init(context.child("storage"), cfg.clone())
813 .await
814 .expect("failed to init");
815
816 for section in 1u64..=10 {
818 journal
819 .append(section, &test_digest(section))
820 .await
821 .expect("failed to append");
822 }
823 journal.sync_all().await.expect("failed to sync");
824
825 let size = journal.size(5).expect("failed to get size");
827 journal.rewind(5, size).await.expect("failed to rewind");
828
829 for section in 1u64..=5 {
831 let size = journal.size(section).expect("failed to get size");
832 assert!(size > 0, "section {section} should still have data");
833 }
834
835 for section in 6u64..=10 {
837 let size = journal.size(section).expect("failed to get size");
838 assert_eq!(size, 0, "section {section} should be removed");
839 }
840
841 {
843 let stream = journal
844 .replay(0, 0, NZUsize!(1024))
845 .await
846 .expect("failed to replay");
847 pin_mut!(stream);
848 let mut items = Vec::new();
849 while let Some(result) = stream.next().await {
850 let (section, _, item) = result.expect("failed to read");
851 items.push((section, item));
852 }
853 assert_eq!(items.len(), 5);
854 for (i, (section, item)) in items.iter().enumerate() {
855 assert_eq!(*section, (i + 1) as u64);
856 assert_eq!(*item, test_digest((i + 1) as u64));
857 }
858 }
859
860 journal.destroy().await.expect("failed to destroy");
861 });
862 }
863
864 #[test_traced]
865 fn test_segmented_fixed_rewind_persistence() {
866 let executor = deterministic::Runner::default();
867 executor.start(|context| async move {
868 let cfg = test_cfg(&context);
869
870 let mut journal = Journal::init(context.child("first"), cfg.clone())
872 .await
873 .expect("failed to init");
874 for section in 1u64..=5 {
875 journal
876 .append(section, &test_digest(section))
877 .await
878 .expect("failed to append");
879 }
880 journal.sync_all().await.expect("failed to sync");
881
882 let size = journal.size(2).expect("failed to get size");
884 journal.rewind(2, size).await.expect("failed to rewind");
885 journal.sync_all().await.expect("failed to sync");
886 drop(journal);
887
888 let journal = Journal::<_, Digest>::init(context.child("second"), cfg.clone())
890 .await
891 .expect("failed to re-init");
892
893 for section in 1u64..=2 {
895 let size = journal.size(section).expect("failed to get size");
896 assert!(size > 0, "section {section} should have data after restart");
897 }
898
899 for section in 3u64..=5 {
901 let size = journal.size(section).expect("failed to get size");
902 assert_eq!(size, 0, "section {section} should be gone after restart");
903 }
904
905 let item1 = journal.get(1, 0).await.expect("failed to get");
907 assert_eq!(item1, test_digest(1));
908 let item2 = journal.get(2, 0).await.expect("failed to get");
909 assert_eq!(item2, test_digest(2));
910
911 journal.destroy().await.expect("failed to destroy");
912 });
913 }
914
915 #[test_traced]
916 fn test_segmented_fixed_corruption_recovery() {
917 let executor = deterministic::Runner::default();
918 executor.start(|context| async move {
919 let cfg = test_cfg(&context);
920 let mut journal = Journal::init(context.child("first"), cfg.clone())
921 .await
922 .expect("failed to init");
923
924 for i in 0u64..5 {
925 journal
926 .append(1, &test_digest(i))
927 .await
928 .expect("failed to append");
929 }
930 journal.sync_all().await.expect("failed to sync");
931 drop(journal);
932
933 let (blob, size) = context
934 .open(&cfg.partition, &1u64.to_be_bytes())
935 .await
936 .expect("failed to open blob");
937 blob.resize(size - 1).await.expect("failed to truncate");
938 blob.sync().await.expect("failed to sync");
939
940 let mut journal = Journal::<_, Digest>::init(context.child("second"), cfg.clone())
941 .await
942 .expect("failed to re-init");
943
944 let count = {
945 let stream = journal
946 .replay(0, 0, NZUsize!(1024))
947 .await
948 .expect("failed to replay");
949 pin_mut!(stream);
950
951 let mut count = 0;
952 while let Some(result) = stream.next().await {
953 result.expect("should be ok");
954 count += 1;
955 }
956 count
957 };
958 assert_eq!(count, 4);
959
960 journal.destroy().await.expect("failed to destroy");
961 });
962 }
963
964 #[test_traced]
965 fn test_segmented_fixed_persistence() {
966 let executor = deterministic::Runner::default();
967 executor.start(|context| async move {
968 let cfg = test_cfg(&context);
969
970 let mut journal = Journal::init(context.child("first"), cfg.clone())
972 .await
973 .expect("failed to init");
974
975 for i in 0u64..5 {
976 journal
977 .append(1, &test_digest(i))
978 .await
979 .expect("failed to append");
980 }
981 journal.sync_all().await.expect("failed to sync");
982 drop(journal);
983
984 let journal = Journal::<_, Digest>::init(context.child("second"), cfg)
986 .await
987 .expect("failed to re-init");
988
989 for i in 0u64..5 {
990 let item = journal.get(1, i).await.expect("failed to get");
991 assert_eq!(item, test_digest(i));
992 }
993
994 journal.destroy().await.expect("failed to destroy");
995 });
996 }
997
998 #[test_traced]
999 fn test_segmented_fixed_sync() {
1000 let executor = deterministic::Runner::default();
1001 executor.start(|context| async move {
1002 let cfg = test_cfg(&context);
1003 let mut journal = Journal::init(context.child("first"), cfg.clone())
1004 .await
1005 .expect("failed to init");
1006
1007 for section in 1u64..=3 {
1009 journal
1010 .append(section, &test_digest(section))
1011 .await
1012 .expect("failed to append");
1013 }
1014
1015 journal
1017 .sync(&[1, 3, 99])
1018 .await
1019 .expect("failed to sync sections");
1020 drop(journal);
1021
1022 let journal = Journal::<_, Digest>::init(context.child("second"), cfg)
1024 .await
1025 .expect("failed to re-init");
1026 assert_eq!(
1027 journal.get(1, 0).await.expect("section 1 durable"),
1028 test_digest(1)
1029 );
1030 assert_eq!(
1031 journal.get(3, 0).await.expect("section 3 durable"),
1032 test_digest(3)
1033 );
1034 assert!(matches!(
1035 journal.get(2, 0).await,
1036 Err(Error::ItemOutOfRange(0)) | Err(Error::SectionOutOfRange(2))
1037 ));
1038
1039 journal.destroy().await.expect("failed to destroy");
1040 });
1041 }
1042
1043 #[test_traced]
1044 fn test_segmented_fixed_section_len() {
1045 let executor = deterministic::Runner::default();
1046 executor.start(|context| async move {
1047 let cfg = test_cfg(&context);
1048 let mut journal = Journal::init(context.child("storage"), cfg.clone())
1049 .await
1050 .expect("failed to init");
1051
1052 assert_eq!(journal.section_len(1).unwrap(), 0);
1053
1054 for i in 0u64..5 {
1055 journal
1056 .append(1, &test_digest(i))
1057 .await
1058 .expect("failed to append");
1059 }
1060
1061 assert_eq!(journal.section_len(1).unwrap(), 5);
1062 assert_eq!(journal.section_len(2).unwrap(), 0);
1063
1064 journal.destroy().await.expect("failed to destroy");
1065 });
1066 }
1067
1068 #[test_traced]
1069 fn test_segmented_fixed_non_contiguous_sections() {
1070 let executor = deterministic::Runner::default();
1073 executor.start(|context| async move {
1074 let cfg = test_cfg(&context);
1075 let mut journal = Journal::init(context.child("first"), cfg.clone())
1076 .await
1077 .expect("failed to init");
1078
1079 journal
1081 .append(1, &test_digest(100))
1082 .await
1083 .expect("failed to append");
1084 journal
1085 .append(5, &test_digest(500))
1086 .await
1087 .expect("failed to append");
1088 journal
1089 .append(10, &test_digest(1000))
1090 .await
1091 .expect("failed to append");
1092 journal.sync_all().await.expect("failed to sync");
1093
1094 assert_eq!(journal.get(1, 0).await.unwrap(), test_digest(100));
1096 assert_eq!(journal.get(5, 0).await.unwrap(), test_digest(500));
1097 assert_eq!(journal.get(10, 0).await.unwrap(), test_digest(1000));
1098
1099 for missing_section in [0u64, 2, 3, 4, 6, 7, 8, 9, 11] {
1101 let result = journal.get(missing_section, 0).await;
1102 assert!(
1103 matches!(result, Err(Error::SectionOutOfRange(_))),
1104 "Expected SectionOutOfRange for section {}, got {:?}",
1105 missing_section,
1106 result
1107 );
1108 }
1109
1110 drop(journal);
1112 let mut journal = Journal::<_, Digest>::init(context.child("second"), cfg.clone())
1113 .await
1114 .expect("failed to re-init");
1115
1116 {
1118 let stream = journal
1119 .replay(0, 0, NZUsize!(1024))
1120 .await
1121 .expect("failed to replay");
1122 pin_mut!(stream);
1123
1124 let mut items = Vec::new();
1125 while let Some(result) = stream.next().await {
1126 let (section, _, item) = result.expect("replay error");
1127 items.push((section, item));
1128 }
1129
1130 assert_eq!(items.len(), 3, "Should have 3 items");
1131 assert_eq!(items[0], (1, test_digest(100)));
1132 assert_eq!(items[1], (5, test_digest(500)));
1133 assert_eq!(items[2], (10, test_digest(1000)));
1134 }
1135
1136 {
1138 let stream = journal
1139 .replay(5, 0, NZUsize!(1024))
1140 .await
1141 .expect("failed to replay from section 5");
1142 pin_mut!(stream);
1143
1144 let mut items = Vec::new();
1145 while let Some(result) = stream.next().await {
1146 let (section, _, item) = result.expect("replay error");
1147 items.push((section, item));
1148 }
1149
1150 assert_eq!(items.len(), 2, "Should have 2 items from section 5 onwards");
1151 assert_eq!(items[0], (5, test_digest(500)));
1152 assert_eq!(items[1], (10, test_digest(1000)));
1153 }
1154
1155 journal.destroy().await.expect("failed to destroy");
1156 });
1157 }
1158
1159 #[test_traced]
1160 fn test_segmented_fixed_empty_section_in_middle() {
1161 let executor = deterministic::Runner::default();
1164 executor.start(|context| async move {
1165 let cfg = test_cfg(&context);
1166 let mut journal = Journal::init(context.child("first"), cfg.clone())
1167 .await
1168 .expect("failed to init");
1169
1170 journal
1172 .append(1, &test_digest(100))
1173 .await
1174 .expect("failed to append");
1175
1176 journal
1178 .append(2, &test_digest(200))
1179 .await
1180 .expect("failed to append");
1181 journal.sync(2).await.expect("failed to sync");
1182 journal
1183 .rewind_section(2, 0)
1184 .await
1185 .expect("failed to rewind");
1186
1187 journal
1189 .append(3, &test_digest(300))
1190 .await
1191 .expect("failed to append");
1192
1193 journal.sync_all().await.expect("failed to sync");
1194
1195 assert_eq!(journal.section_len(1).unwrap(), 1);
1197 assert_eq!(journal.section_len(2).unwrap(), 0);
1198 assert_eq!(journal.section_len(3).unwrap(), 1);
1199
1200 drop(journal);
1202 let mut journal = Journal::<_, Digest>::init(context.child("second"), cfg.clone())
1203 .await
1204 .expect("failed to re-init");
1205
1206 {
1208 let stream = journal
1209 .replay(0, 0, NZUsize!(1024))
1210 .await
1211 .expect("failed to replay");
1212 pin_mut!(stream);
1213
1214 let mut items = Vec::new();
1215 while let Some(result) = stream.next().await {
1216 let (section, _, item) = result.expect("replay error");
1217 items.push((section, item));
1218 }
1219
1220 assert_eq!(
1221 items.len(),
1222 2,
1223 "Should have 2 items (skipping empty section)"
1224 );
1225 assert_eq!(items[0], (1, test_digest(100)));
1226 assert_eq!(items[1], (3, test_digest(300)));
1227 }
1228
1229 {
1231 let stream = journal
1232 .replay(2, 0, NZUsize!(1024))
1233 .await
1234 .expect("failed to replay from section 2");
1235 pin_mut!(stream);
1236
1237 let mut items = Vec::new();
1238 while let Some(result) = stream.next().await {
1239 let (section, _, item) = result.expect("replay error");
1240 items.push((section, item));
1241 }
1242
1243 assert_eq!(items.len(), 1, "Should have 1 item from section 3");
1244 assert_eq!(items[0], (3, test_digest(300)));
1245 }
1246
1247 journal.destroy().await.expect("failed to destroy");
1248 });
1249 }
1250
1251 #[test_traced]
1252 fn test_segmented_fixed_truncation_recovery_across_page_boundary() {
1253 let executor = deterministic::Runner::default();
1265 executor.start(|context| async move {
1266 let cfg = test_cfg(&context);
1267 let mut journal = Journal::init(context.child("first"), cfg.clone())
1268 .await
1269 .expect("failed to init");
1270
1271 for i in 0u64..3 {
1273 journal
1274 .append(1, &test_digest(i))
1275 .await
1276 .expect("failed to append");
1277 }
1278 journal.sync_all().await.expect("failed to sync");
1279
1280 for i in 0u64..3 {
1282 let item = journal.get(1, i).await.expect("failed to get");
1283 assert_eq!(item, test_digest(i));
1284 }
1285 drop(journal);
1286
1287 let (blob, size) = context
1289 .open(&cfg.partition, &1u64.to_be_bytes())
1290 .await
1291 .expect("failed to open blob");
1292 blob.resize(size - 1).await.expect("failed to truncate");
1293 blob.sync().await.expect("failed to sync");
1294 drop(blob);
1295
1296 let journal = Journal::<_, Digest>::init(context.child("second"), cfg.clone())
1299 .await
1300 .expect("failed to re-init");
1301
1302 assert_eq!(journal.section_len(1).unwrap(), 2);
1304
1305 assert_eq!(journal.size(1).unwrap(), 64);
1308
1309 let item0 = journal.get(1, 0).await.expect("failed to get item 0");
1311 assert_eq!(item0, test_digest(0));
1312 let item1 = journal.get(1, 1).await.expect("failed to get item 1");
1313 assert_eq!(item1, test_digest(1));
1314
1315 let err = journal.get(1, 2).await;
1317 assert!(
1318 matches!(err, Err(Error::ItemOutOfRange(2))),
1319 "expected ItemOutOfRange(2), got {:?}",
1320 err
1321 );
1322
1323 journal.destroy().await.expect("failed to destroy");
1324 });
1325 }
1326
1327 #[test_traced]
1328 fn test_journal_clear() {
1329 let executor = deterministic::Runner::default();
1330 executor.start(|context| async move {
1331 let cfg = Config {
1332 partition: "clear-test".into(),
1333 page_cache: CacheRef::from_pooler(&context, PAGE_SIZE, PAGE_CACHE_SIZE),
1334 write_buffer: NZUsize!(1024),
1335 };
1336
1337 let mut journal: Journal<_, Digest> =
1338 Journal::init(context.child("journal"), cfg.clone())
1339 .await
1340 .expect("Failed to initialize journal");
1341
1342 for section in 0..5u64 {
1344 for i in 0..10u64 {
1345 journal
1346 .append(section, &test_digest(section * 1000 + i))
1347 .await
1348 .expect("Failed to append");
1349 }
1350 journal.sync(section).await.expect("Failed to sync");
1351 }
1352
1353 assert_eq!(journal.get(0, 0).await.unwrap(), test_digest(0));
1355 assert_eq!(journal.get(4, 0).await.unwrap(), test_digest(4000));
1356
1357 journal.clear().await.expect("Failed to clear");
1359
1360 for section in 0..5u64 {
1362 assert!(matches!(
1363 journal.get(section, 0).await,
1364 Err(Error::SectionOutOfRange(s)) if s == section
1365 ));
1366 }
1367
1368 for i in 0..5u64 {
1370 journal
1371 .append(10, &test_digest(i * 100))
1372 .await
1373 .expect("Failed to append after clear");
1374 }
1375 journal.sync(10).await.expect("Failed to sync after clear");
1376
1377 assert_eq!(journal.get(10, 0).await.unwrap(), test_digest(0));
1379
1380 assert!(matches!(
1382 journal.get(0, 0).await,
1383 Err(Error::SectionOutOfRange(0))
1384 ));
1385
1386 journal.destroy().await.unwrap();
1387 });
1388 }
1389
1390 #[test_traced]
1391 fn test_last_missing_section_returns_error() {
1392 let executor = deterministic::Runner::default();
1393 executor.start(|context| async move {
1394 let cfg = test_cfg(&context);
1395 let journal = Journal::<_, Digest>::init(context.child("storage"), cfg.clone())
1396 .await
1397 .expect("failed to init");
1398
1399 assert!(matches!(
1400 journal.last(0).await,
1401 Err(Error::SectionOutOfRange(0))
1402 ));
1403 assert!(matches!(
1404 journal.last(99).await,
1405 Err(Error::SectionOutOfRange(99))
1406 ));
1407
1408 journal.destroy().await.unwrap();
1409 });
1410 }
1411
1412 #[test_traced]
1413 fn test_last_after_rewind_to_zero() {
1414 let executor = deterministic::Runner::default();
1415 executor.start(|context| async move {
1416 let cfg = test_cfg(&context);
1417 let mut journal = Journal::init(context.child("storage"), cfg.clone())
1418 .await
1419 .expect("failed to init");
1420
1421 journal.append(0, &test_digest(0)).await.unwrap();
1422 journal.append(0, &test_digest(1)).await.unwrap();
1423 journal.sync(0).await.unwrap();
1424
1425 assert!(journal.last(0).await.unwrap().is_some());
1426
1427 journal.rewind(0, 0).await.unwrap();
1428 assert_eq!(journal.last(0).await.unwrap(), None);
1429
1430 journal.destroy().await.unwrap();
1431 });
1432 }
1433
1434 #[test_traced]
1435 fn test_last_pruned_section_returns_error() {
1436 let executor = deterministic::Runner::default();
1437 executor.start(|context| async move {
1438 let cfg = test_cfg(&context);
1439 let mut journal = Journal::<_, Digest>::init(context.child("storage"), cfg.clone())
1440 .await
1441 .expect("failed to init");
1442
1443 journal.append(0, &test_digest(0)).await.unwrap();
1444 journal.append(1, &test_digest(1)).await.unwrap();
1445 journal.sync_all().await.unwrap();
1446
1447 journal.prune(1).await.unwrap();
1448
1449 assert!(matches!(
1450 journal.last(0).await,
1451 Err(Error::AlreadyPrunedToSection(1))
1452 ));
1453 assert!(journal.last(1).await.unwrap().is_some());
1454
1455 journal.destroy().await.unwrap();
1456 });
1457 }
1458
1459 #[test_traced]
1460 fn test_get_many_empty() {
1461 let executor = deterministic::Runner::default();
1462 executor.start(|context| async move {
1463 let cfg = test_cfg(&context);
1464 let mut journal = Journal::init(context.child("storage"), cfg).await.unwrap();
1465 journal.append(0, &test_digest(0)).await.unwrap();
1466 assert_eq!(journal.section_len(0).unwrap(), 1);
1467
1468 let mut buf = [];
1469 let (items, hits) = journal.get_many(0, &[], &mut buf).await.unwrap();
1470 assert!(items.is_empty());
1471 assert_eq!(hits, 0);
1472
1473 journal.destroy().await.unwrap();
1474 });
1475 }
1476
1477 #[test_traced]
1478 fn test_get_many_single_section() {
1479 let executor = deterministic::Runner::default();
1480 executor.start(|context| async move {
1481 let cfg = test_cfg(&context);
1482 let mut journal = Journal::init(context.child("storage"), cfg).await.unwrap();
1483
1484 for i in 0..5 {
1485 journal.append(0, &test_digest(i)).await.unwrap();
1486 }
1487 assert_eq!(journal.section_len(0).unwrap(), 5);
1488
1489 let chunk = Journal::<deterministic::Context, Digest>::CHUNK_SIZE;
1492 let mut buf = vec![0u8; 6 * chunk];
1493 let (items, _) = journal
1494 .get_many(0, &[0, 1, 2, 3, 4], &mut buf)
1495 .await
1496 .unwrap();
1497
1498 for (i, item) in items.iter().enumerate() {
1499 assert_eq!(*item, test_digest(i as u64));
1500 }
1501
1502 journal.destroy().await.unwrap();
1503 });
1504 }
1505
1506 #[test_traced]
1507 fn test_get_many_subset() {
1508 let executor = deterministic::Runner::default();
1510 executor.start(|context| async move {
1511 let cfg = test_cfg(&context);
1512 let mut journal = Journal::init(context.child("storage"), cfg).await.unwrap();
1513
1514 for i in 0..10 {
1515 journal.append(0, &test_digest(i)).await.unwrap();
1516 }
1517 assert_eq!(journal.section_len(0).unwrap(), 10);
1518
1519 let chunk = Journal::<deterministic::Context, Digest>::CHUNK_SIZE;
1520 let positions = [1, 4, 7, 9];
1521 let mut buf = vec![0u8; positions.len() * chunk];
1522 let (items, _) = journal.get_many(0, &positions, &mut buf).await.unwrap();
1523
1524 for (i, &pos) in positions.iter().enumerate() {
1525 assert_eq!(items[i], test_digest(pos));
1526 }
1527
1528 journal.destroy().await.unwrap();
1529 });
1530 }
1531
1532 #[test_traced]
1533 fn test_get_many_bad_section() {
1534 let executor = deterministic::Runner::default();
1535 executor.start(|context| async move {
1536 let cfg = test_cfg(&context);
1537 let journal = Journal::<_, Digest>::init(context.child("storage"), cfg)
1538 .await
1539 .unwrap();
1540
1541 let mut buf = vec![0u8; 64];
1542 let err = journal.get_many(99, &[0], &mut buf).await.unwrap_err();
1543 assert!(matches!(err, Error::SectionOutOfRange(99)));
1544
1545 journal.destroy().await.unwrap();
1546 });
1547 }
1548
1549 #[test_traced]
1550 fn test_get_many_matches_get() {
1551 let executor = deterministic::Runner::default();
1553 executor.start(|context| async move {
1554 let cfg = test_cfg(&context);
1555 let mut journal = Journal::init(context.child("storage"), cfg).await.unwrap();
1556
1557 for i in 0..8 {
1558 journal.append(0, &test_digest(i)).await.unwrap();
1559 }
1560 assert_eq!(journal.section_len(0).unwrap(), 8);
1561 journal.sync_all().await.unwrap();
1562
1563 let chunk = Journal::<deterministic::Context, Digest>::CHUNK_SIZE;
1564 let positions: Vec<u64> = (0..8).collect();
1565 let mut buf = vec![0u8; positions.len() * chunk];
1566 let (batch, _) = journal.get_many(0, &positions, &mut buf).await.unwrap();
1567
1568 for pos in &positions {
1569 let single = journal.get(0, *pos).await.unwrap();
1570 assert_eq!(batch[*pos as usize], single);
1571 }
1572
1573 journal.destroy().await.unwrap();
1574 });
1575 }
1576
1577 #[test_traced]
1578 fn test_segmented_fixed_prune_waits_for_in_flight_start_sync() {
1579 let executor = deterministic::Runner::default();
1580 executor.start(|context| async move {
1581 let pending = PendingSyncs::default();
1582 let context = DelayedSyncContext {
1583 inner: context,
1584 pending: pending.clone(),
1585 };
1586 let cfg = test_cfg(&context);
1587 let mut journal = Journal::init(context.child("storage"), cfg)
1588 .await
1589 .expect("failed to init");
1590
1591 journal
1592 .append(1, &test_digest(0))
1593 .await
1594 .expect("failed to append");
1595 let handle = journal.start_sync(1).await.expect("failed to start sync");
1596 assert!(!pending.lock().is_empty());
1597
1598 let started = Arc::new(AtomicUsize::new(0));
1599 let completed = Arc::new(AtomicUsize::new(0));
1600 let started_clone = started.clone();
1601 let completed_clone = completed.clone();
1602 let waiter = context.inner.child("prune").spawn(|_| async move {
1603 started_clone.fetch_add(1, Ordering::Relaxed);
1604 assert!(journal.prune(2).await.expect("failed to prune"));
1605 completed_clone.fetch_add(1, Ordering::Relaxed);
1606 journal
1607 });
1608
1609 while started.load(Ordering::Relaxed) == 0 {
1610 commonware_runtime::reschedule().await;
1611 }
1612 commonware_runtime::reschedule().await;
1613 assert_eq!(
1614 completed.load(Ordering::Relaxed),
1615 0,
1616 "prune must wait for in-flight syncs on pruned sections"
1617 );
1618
1619 release_pending_syncs(&pending);
1620 handle
1621 .await
1622 .expect("sync handle should complete despite pruning");
1623 while completed.load(Ordering::Relaxed) == 0 {
1624 commonware_runtime::reschedule().await;
1625 }
1626 let journal = waiter.await.expect("prune task failed");
1627 assert_eq!(journal.oldest_section(), None);
1628 });
1629 }
1630
1631 #[test_traced]
1632 fn test_segmented_fixed_destroy_waits_for_in_flight_start_sync() {
1633 let executor = deterministic::Runner::default();
1634 executor.start(|context| async move {
1635 let pending = PendingSyncs::default();
1636 let context = DelayedSyncContext {
1637 inner: context,
1638 pending: pending.clone(),
1639 };
1640 let cfg = test_cfg(&context);
1641 let mut journal = Journal::init(context.child("storage"), cfg)
1642 .await
1643 .expect("failed to init");
1644
1645 journal
1646 .append(1, &test_digest(0))
1647 .await
1648 .expect("failed to append");
1649 let handle = journal.start_sync(1).await.expect("failed to start sync");
1650 assert!(!pending.lock().is_empty());
1651
1652 let started = Arc::new(AtomicUsize::new(0));
1653 let completed = Arc::new(AtomicUsize::new(0));
1654 let started_clone = started.clone();
1655 let completed_clone = completed.clone();
1656 let waiter = context.inner.child("destroy").spawn(|_| async move {
1657 started_clone.fetch_add(1, Ordering::Relaxed);
1658 journal.destroy().await.expect("failed to destroy");
1659 completed_clone.fetch_add(1, Ordering::Relaxed);
1660 });
1661
1662 while started.load(Ordering::Relaxed) == 0 {
1663 commonware_runtime::reschedule().await;
1664 }
1665 commonware_runtime::reschedule().await;
1666 assert_eq!(
1667 completed.load(Ordering::Relaxed),
1668 0,
1669 "destroy must wait for in-flight syncs"
1670 );
1671
1672 release_pending_syncs(&pending);
1673 handle
1674 .await
1675 .expect("sync handle should complete despite destruction");
1676 while completed.load(Ordering::Relaxed) == 0 {
1677 commonware_runtime::reschedule().await;
1678 }
1679 waiter.await.expect("destroy task failed");
1680 });
1681 }
1682
1683 #[test_traced]
1684 fn test_segmented_fixed_clear_waits_for_in_flight_start_sync() {
1685 let executor = deterministic::Runner::default();
1686 executor.start(|context| async move {
1687 let pending = PendingSyncs::default();
1688 let context = DelayedSyncContext {
1689 inner: context,
1690 pending: pending.clone(),
1691 };
1692 let cfg = test_cfg(&context);
1693 let mut journal = Journal::init(context.child("storage"), cfg)
1694 .await
1695 .expect("failed to init");
1696
1697 journal
1698 .append(1, &test_digest(0))
1699 .await
1700 .expect("failed to append");
1701 let handle = journal.start_sync(1).await.expect("failed to start sync");
1702 assert!(!pending.lock().is_empty());
1703
1704 let started = Arc::new(AtomicUsize::new(0));
1705 let completed = Arc::new(AtomicUsize::new(0));
1706 let started_clone = started.clone();
1707 let completed_clone = completed.clone();
1708 let waiter = context.inner.child("clear").spawn(|_| async move {
1709 started_clone.fetch_add(1, Ordering::Relaxed);
1710 journal.clear().await.expect("failed to clear");
1711 completed_clone.fetch_add(1, Ordering::Relaxed);
1712 journal
1713 });
1714
1715 while started.load(Ordering::Relaxed) == 0 {
1716 commonware_runtime::reschedule().await;
1717 }
1718 commonware_runtime::reschedule().await;
1719 assert_eq!(
1720 completed.load(Ordering::Relaxed),
1721 0,
1722 "clear must wait for in-flight syncs"
1723 );
1724
1725 release_pending_syncs(&pending);
1726 handle
1727 .await
1728 .expect("sync handle should complete despite clearing");
1729 while completed.load(Ordering::Relaxed) == 0 {
1730 commonware_runtime::reschedule().await;
1731 }
1732 let mut journal = waiter.await.expect("clear task failed");
1733
1734 assert_eq!(journal.oldest_section(), None);
1736 let position = journal
1737 .append(1, &test_digest(1))
1738 .await
1739 .expect("failed to append after clear");
1740 assert_eq!(position, 0);
1741 journal.destroy().await.expect("failed to destroy");
1742 });
1743 }
1744
1745 #[test_traced]
1746 fn test_segmented_fixed_rewind_waits_for_in_flight_start_sync() {
1747 let executor = deterministic::Runner::default();
1748 executor.start(|context| async move {
1749 let pending = PendingSyncs::default();
1750 let context = DelayedSyncContext {
1751 inner: context,
1752 pending: pending.clone(),
1753 };
1754 let cfg = test_cfg(&context);
1755 let mut journal = Journal::init(context.child("storage"), cfg)
1756 .await
1757 .expect("failed to init");
1758
1759 journal
1760 .append(1, &test_digest(0))
1761 .await
1762 .expect("failed to append");
1763 journal
1764 .append(2, &test_digest(1))
1765 .await
1766 .expect("failed to append");
1767 let handle = journal.start_sync(2).await.expect("failed to start sync");
1768 assert!(!pending.lock().is_empty());
1769
1770 let size = journal.size(1).expect("failed to get size");
1771 let started = Arc::new(AtomicUsize::new(0));
1772 let completed = Arc::new(AtomicUsize::new(0));
1773 let started_clone = started.clone();
1774 let completed_clone = completed.clone();
1775 let waiter = context.inner.child("rewind").spawn(move |_| async move {
1776 started_clone.fetch_add(1, Ordering::Relaxed);
1777 journal.rewind(1, size).await.expect("failed to rewind");
1778 completed_clone.fetch_add(1, Ordering::Relaxed);
1779 journal
1780 });
1781
1782 while started.load(Ordering::Relaxed) == 0 {
1783 commonware_runtime::reschedule().await;
1784 }
1785 commonware_runtime::reschedule().await;
1786 assert_eq!(
1787 completed.load(Ordering::Relaxed),
1788 0,
1789 "rewind must wait for in-flight syncs on removed sections"
1790 );
1791
1792 release_pending_syncs(&pending);
1793 handle
1794 .await
1795 .expect("sync handle should complete despite rewind");
1796 while completed.load(Ordering::Relaxed) == 0 {
1797 commonware_runtime::reschedule().await;
1798 }
1799 let journal = waiter.await.expect("rewind task failed");
1800 assert_eq!(journal.size(2).expect("failed to get size"), 0);
1801 journal.destroy().await.expect("failed to destroy");
1802 });
1803 }
1804
1805 #[test_traced]
1806 fn test_segmented_fixed_prune_surfaces_failed_in_flight_start_sync() {
1807 let executor = deterministic::Runner::default();
1808 executor.start(|context| async move {
1809 let pending = PendingSyncs::default();
1810 let context = DelayedSyncContext {
1811 inner: context,
1812 pending: pending.clone(),
1813 };
1814 let cfg = test_cfg(&context);
1815 let mut journal = Journal::init(context.child("storage"), cfg)
1816 .await
1817 .expect("failed to init");
1818
1819 journal
1820 .append(1, &test_digest(0))
1821 .await
1822 .expect("failed to append");
1823 let handle = journal.start_sync(1).await.expect("failed to start sync");
1824 fail_pending_syncs(&pending);
1825
1826 let err = journal
1827 .prune(2)
1828 .await
1829 .expect_err("prune must surface a failed in-flight sync");
1830 assert!(matches!(err, Error::Runtime(RError::Io(_))));
1831
1832 let err = handle.await.expect_err("sync handle should fail");
1833 assert!(matches!(err, RError::Io(_)));
1834 });
1835 }
1836}