1use super::manager::{AppendFactory, Config as ManagerConfig, Manager};
84use crate::journal::{
85 frame::{
86 decode_item, decode_length_prefix, encode_frame_into, find_frame, read_frame_at, FrameInfo,
87 },
88 Error,
89};
90use commonware_codec::{varint::MAX_U32_VARINT_SIZE, Codec, CodecShared};
91use commonware_runtime::{
92 buffer::paged::{CacheRef, Replay, Writer},
93 Blob, Buf, Handle, IoBuf, Metrics, Storage,
94};
95use futures::stream::{self, Stream, StreamExt};
96use std::{io::Cursor, num::NonZeroUsize};
97use tracing::{trace, warn};
98
99#[derive(Clone)]
101pub struct Config<C> {
102 pub partition: String,
105
106 pub compression: Option<u8>,
108
109 pub codec_config: C,
111
112 pub page_cache: CacheRef,
114
115 pub write_buffer: NonZeroUsize,
117}
118
119struct ReplayState<'a, B: Blob, C> {
121 section: u64,
122 blob: &'a mut Writer<B>,
124 replay: Replay<B>,
125 skip_bytes: u64,
126 offset: u64,
127 valid_offset: u64,
128 codec_config: C,
129 compressed: bool,
130 done: bool,
131}
132
133pub struct Journal<E: Storage + Metrics, V: Codec> {
147 manager: Manager<E, AppendFactory>,
148
149 compression: Option<u8>,
151
152 codec_config: V::Cfg,
154}
155
156impl<E: Storage + Metrics, V: CodecShared> Journal<E, V> {
157 pub async fn init(context: E, cfg: Config<V::Cfg>) -> Result<Self, Error> {
163 let manager_cfg = ManagerConfig {
164 partition: cfg.partition,
165 factory: AppendFactory {
166 write_buffer: cfg.write_buffer,
167 page_cache_ref: cfg.page_cache,
168 },
169 };
170 let manager = Manager::init(context, manager_cfg).await?;
171
172 Ok(Self {
173 manager,
174 compression: cfg.compression,
175 codec_config: cfg.codec_config,
176 })
177 }
178
179 async fn read(
181 compressed: bool,
182 cfg: &V::Cfg,
183 blob: &Writer<E::Blob>,
184 offset: u64,
185 ) -> Result<(u64, u32, V), Error> {
186 read_frame_at(blob, offset, cfg, compressed).await
187 }
188
189 pub async fn replay(
193 &mut self,
194 start_section: u64,
195 mut start_offset: u64,
196 buffer: NonZeroUsize,
197 ) -> Result<impl Stream<Item = Result<(u64, u64, u32, V), Error>> + Send + '_, Error> {
198 let codec_config = self.codec_config.clone();
201 let compressed = self.compression.is_some();
202 let mut blobs = Vec::new();
203 for (§ion, blob) in self.manager.sections_from(start_section) {
204 if section == start_section && start_offset > blob.size() {
205 return Err(Error::ItemOutOfRange(start_offset));
206 }
207 let replay = blob.replay(buffer).await?;
208 blobs.push((section, blob, replay, codec_config.clone(), compressed));
209 }
210
211 Ok(stream::iter(blobs).flat_map(
213 move |(section, blob, replay, codec_config, compressed)| {
214 let skip_bytes = if section == start_section {
216 start_offset
217 } else {
218 start_offset = 0;
219 0
220 };
221
222 stream::unfold(
223 ReplayState {
224 section,
225 blob,
226 replay,
227 skip_bytes,
228 offset: 0,
229 valid_offset: skip_bytes,
230 codec_config,
231 compressed,
232 done: false,
233 },
234 move |mut state| async move {
235 if state.done {
236 return None;
237 }
238
239 let blob_size = state.replay.blob_size();
240 let mut batch: Vec<Result<(u64, u64, u32, V), Error>> = Vec::new();
241 loop {
242 match state.replay.ensure(MAX_U32_VARINT_SIZE).await {
246 Ok(true) => {}
247 Ok(false) => {
248 if state.replay.remaining() == 0 {
250 state.done = true;
251 return if batch.is_empty() {
252 None
253 } else {
254 Some((batch, state))
255 };
256 }
257 }
259 Err(err) => {
260 batch.push(Err(err.into()));
261 state.done = true;
262 return Some((batch, state));
263 }
264 }
265
266 if state.skip_bytes > 0 {
268 let to_skip =
269 state.skip_bytes.min(state.replay.remaining() as u64) as usize;
270 state.replay.advance(to_skip);
271 state.skip_bytes -= to_skip as u64;
272 state.offset += to_skip as u64;
273 continue;
274 }
275
276 let before_remaining = state.replay.remaining();
278 let (item_size, varint_len) =
279 match decode_length_prefix(&mut state.replay) {
280 Ok(result) => result,
281 Err(err) => {
282 if state.replay.is_exhausted()
284 || before_remaining < MAX_U32_VARINT_SIZE
285 {
286 if state.valid_offset < blob_size
288 && state.offset < blob_size
289 {
290 warn!(
291 blob = state.section,
292 bad_offset = state.offset,
293 new_size = state.valid_offset,
294 "trailing bytes detected: truncating"
295 );
296 if let Err(err) =
300 state.blob.resize(state.valid_offset).await
301 {
302 batch.push(Err(err.into()));
303 state.done = true;
304 return Some((batch, state));
305 }
306 if let Err(err) = state.blob.sync().await {
307 batch.push(Err(err.into()));
308 state.done = true;
309 return Some((batch, state));
310 }
311 }
312 state.done = true;
313 return if batch.is_empty() {
314 None
315 } else {
316 Some((batch, state))
317 };
318 }
319 batch.push(Err(err));
320 state.done = true;
321 return Some((batch, state));
322 }
323 };
324
325 match state.replay.ensure(item_size).await {
327 Ok(true) => {}
328 Ok(false) => {
329 warn!(
331 blob = state.section,
332 bad_offset = state.offset,
333 new_size = state.valid_offset,
334 "incomplete item at end: truncating"
335 );
336 if let Err(err) = state.blob.resize(state.valid_offset).await {
337 batch.push(Err(err.into()));
338 state.done = true;
339 return Some((batch, state));
340 }
341 if let Err(err) = state.blob.sync().await {
342 batch.push(Err(err.into()));
343 state.done = true;
344 return Some((batch, state));
345 }
346 state.done = true;
347 return if batch.is_empty() {
348 None
349 } else {
350 Some((batch, state))
351 };
352 }
353 Err(err) => {
354 batch.push(Err(err.into()));
355 state.done = true;
356 return Some((batch, state));
357 }
358 }
359
360 let item_offset = state.offset;
362 let next_offset = match state
363 .offset
364 .checked_add(varint_len as u64)
365 .and_then(|o| o.checked_add(item_size as u64))
366 {
367 Some(o) => o,
368 None => {
369 batch.push(Err(Error::OffsetOverflow));
370 state.done = true;
371 return Some((batch, state));
372 }
373 };
374 match decode_item::<V>(
375 (&mut state.replay).take(item_size),
376 &state.codec_config,
377 state.compressed,
378 ) {
379 Ok(decoded) => {
380 batch.push(Ok((
381 state.section,
382 item_offset,
383 item_size as u32,
384 decoded,
385 )));
386 state.valid_offset = next_offset;
387 state.offset = next_offset;
388 }
389 Err(err) => {
390 batch.push(Err(err));
391 state.done = true;
392 return Some((batch, state));
393 }
394 }
395
396 if !batch.is_empty() && state.replay.remaining() < MAX_U32_VARINT_SIZE {
398 return Some((batch, state));
399 }
400 }
401 },
402 )
403 .flat_map(stream::iter)
404 },
405 ))
406 }
407
408 fn encode_item(compression: Option<u8>, item: &V) -> Result<(Vec<u8>, u32), Error> {
413 let mut buf = Vec::new();
414 let item_len = encode_frame_into(compression, item, &mut buf)?;
415 Ok((buf, item_len))
416 }
417
418 pub async fn append(&mut self, section: u64, item: &V) -> Result<(u64, u32), Error> {
422 let (buf, item_len) = Self::encode_item(self.compression, item)?;
423 self.append_raw(section, IoBuf::from(buf))
424 .await
425 .map(|offset| (offset, item_len))
426 }
427
428 async fn append_raw(&mut self, section: u64, buf: IoBuf) -> Result<u64, Error> {
433 let blob = self.manager.get_or_create(section).await?;
434 let offset = blob.append_owned(buf).await?;
435 trace!(blob = section, offset, "appended item");
436 Ok(offset)
437 }
438
439 pub async fn get(&self, section: u64, offset: u64) -> Result<V, Error> {
450 let blob = self
451 .manager
452 .get(section)?
453 .ok_or(Error::SectionOutOfRange(section))?;
454
455 let (_, _, item) =
457 Self::read(self.compression.is_some(), &self.codec_config, blob, offset).await?;
458 Ok(item)
459 }
460
461 pub async fn get_many(&self, section: u64, offsets: &[u64]) -> Result<Vec<V>, Error> {
465 if offsets.is_empty() {
466 return Ok(Vec::new());
467 }
468 let blob = self
469 .manager
470 .get(section)?
471 .ok_or(Error::SectionOutOfRange(section))?;
472
473 let compressed = self.compression.is_some();
474 let cfg = &self.codec_config;
475 let mut items = Vec::with_capacity(offsets.len());
476 for &offset in offsets {
477 let (_, _, item) = Self::read(compressed, cfg, blob, offset).await?;
478 items.push(item);
479 }
480 Ok(items)
481 }
482
483 pub fn try_get_sync(&self, section: u64, offset: u64) -> Option<V> {
485 let blob = self.manager.get(section).ok()??;
486 let remaining = blob.size().checked_sub(offset)?;
487 let header_len = usize::try_from(remaining.min(MAX_U32_VARINT_SIZE as u64)).ok()?;
488 if header_len == 0 {
489 return None;
490 }
491
492 let mut header = [0u8; MAX_U32_VARINT_SIZE];
494 if !blob.try_read_sync_into(&mut header[..header_len], offset) {
495 return None;
496 }
497 let mut cursor = Cursor::new(&header[..header_len]);
498 let (_, frame_info) = find_frame(&mut cursor, offset).ok()?;
499 let (varint_len, data_len) = match frame_info {
500 FrameInfo::Complete {
501 varint_len,
502 data_len,
503 } => (varint_len, data_len),
504 FrameInfo::Incomplete {
505 varint_len,
506 total_len,
507 ..
508 } => (varint_len, total_len),
509 };
510 let item_len = varint_len.checked_add(data_len)?;
511 if item_len > usize::try_from(remaining).ok()? {
512 return None;
513 }
514
515 let compressed = self.compression.is_some();
517 if item_len <= header_len {
518 return decode_item::<V>(
519 &header[varint_len..varint_len + data_len],
520 &self.codec_config,
521 compressed,
522 )
523 .ok();
524 }
525
526 let mut buf = vec![0u8; item_len];
528 if !blob.try_read_sync_into(&mut buf, offset) {
529 return None;
530 }
531 decode_item::<V>(
532 &buf[varint_len..varint_len + data_len],
533 &self.codec_config,
534 compressed,
535 )
536 .ok()
537 }
538
539 pub fn size(&self, section: u64) -> Result<u64, Error> {
543 self.manager.size(section)
544 }
545
546 pub async fn rewind(&mut self, section: u64, size: u64) -> Result<(), Error> {
556 self.manager.rewind(section, size).await
557 }
558
559 pub async fn rewind_section(&mut self, section: u64, size: u64) -> Result<(), Error> {
567 self.manager.rewind_section(section, size).await
568 }
569
570 pub async fn sync(&mut self, sections: impl crate::Sections) -> Result<(), Error> {
574 self.manager.sync(sections).await
575 }
576
577 pub async fn start_sync(
579 &mut self,
580 sections: impl crate::Sections,
581 ) -> Result<Handle<()>, Error> {
582 self.manager.start_sync(sections).await
583 }
584
585 pub async fn sync_all(&mut self) -> Result<(), Error> {
587 self.manager.sync_all().await
588 }
589
590 pub async fn prune(&mut self, min: u64) -> Result<bool, Error> {
592 self.manager.prune(min).await
593 }
594
595 pub fn oldest_section(&self) -> Option<u64> {
597 self.manager.oldest_section()
598 }
599
600 pub fn newest_section(&self) -> Option<u64> {
602 self.manager.newest_section()
603 }
604
605 pub fn is_empty(&self) -> bool {
607 self.manager.is_empty()
608 }
609
610 pub fn num_sections(&self) -> usize {
612 self.manager.num_sections()
613 }
614
615 pub async fn destroy(self) -> Result<(), Error> {
617 self.manager.destroy().await
618 }
619
620 pub async fn clear(&mut self) -> Result<(), Error> {
624 self.manager.clear().await
625 }
626}
627
628#[cfg(test)]
629mod tests {
630 use super::*;
631 use commonware_codec::{varint::UInt, EncodeSize, Write as _};
632 use commonware_macros::test_traced;
633 use commonware_runtime::{deterministic, Blob, BufMut, Runner, Storage, Supervisor as _};
634 use commonware_utils::{NZUsize, NZU16};
635 use futures::{pin_mut, StreamExt};
636 use std::num::NonZeroU16;
637
638 const PAGE_SIZE: NonZeroU16 = NZU16!(1024);
639 const PAGE_CACHE_SIZE: NonZeroUsize = NZUsize!(10);
640
641 #[test_traced]
642 fn test_journal_append_and_read() {
643 let executor = deterministic::Runner::default();
645
646 executor.start(|context| async move {
648 let cfg = Config {
650 partition: "test-partition".into(),
651 compression: None,
652 codec_config: (),
653 page_cache: CacheRef::from_pooler(&context, PAGE_SIZE, PAGE_CACHE_SIZE),
654 write_buffer: NZUsize!(1024),
655 };
656 let index = 1u64;
657 let data = 10;
658 let mut journal = Journal::init(context.child("first"), cfg.clone())
659 .await
660 .expect("Failed to initialize journal");
661
662 journal
664 .append(index, &data)
665 .await
666 .expect("Failed to append data");
667
668 let buffer = context.encode();
670 assert!(buffer.contains("first_tracked 1"));
671
672 journal.sync(index).await.expect("Failed to sync journal");
674 drop(journal);
675 let mut journal = Journal::<_, i32>::init(context.child("second"), cfg)
676 .await
677 .expect("Failed to re-initialize journal");
678
679 let mut items = Vec::new();
681 let stream = journal
682 .replay(0, 0, NZUsize!(1024))
683 .await
684 .expect("unable to setup replay");
685 pin_mut!(stream);
686 while let Some(result) = stream.next().await {
687 match result {
688 Ok((blob_index, _, _, item)) => items.push((blob_index, item)),
689 Err(err) => panic!("Failed to read item: {err}"),
690 }
691 }
692
693 assert_eq!(items.len(), 1);
695 assert_eq!(items[0].0, index);
696 assert_eq!(items[0].1, data);
697
698 let buffer = context.encode();
700 assert!(buffer.contains("second_tracked 1"));
701 });
702 }
703
704 #[test_traced]
705 fn test_journal_multiple_appends_and_reads() {
706 let executor = deterministic::Runner::default();
708
709 executor.start(|context| async move {
711 let cfg = Config {
713 partition: "test-partition".into(),
714 compression: None,
715 codec_config: (),
716 page_cache: CacheRef::from_pooler(&context, PAGE_SIZE, PAGE_CACHE_SIZE),
717 write_buffer: NZUsize!(1024),
718 };
719
720 let mut journal = Journal::init(context.child("first"), cfg.clone())
722 .await
723 .expect("Failed to initialize journal");
724
725 let data_items = vec![(1u64, 1), (1u64, 2), (2u64, 3), (3u64, 4)];
727 for (index, data) in &data_items {
728 journal
729 .append(*index, data)
730 .await
731 .expect("Failed to append data");
732 journal.sync(*index).await.expect("Failed to sync blob");
733 }
734
735 let buffer = context.encode();
737 assert!(buffer.contains("first_tracked 3"));
738 assert!(buffer.contains("first_synced_total 4"));
739
740 drop(journal);
742 let mut journal = Journal::init(context.child("second"), cfg)
743 .await
744 .expect("Failed to re-initialize journal");
745
746 let mut items = Vec::<(u64, u32)>::new();
748 {
749 let stream = journal
750 .replay(0, 0, NZUsize!(1024))
751 .await
752 .expect("unable to setup replay");
753 pin_mut!(stream);
754 while let Some(result) = stream.next().await {
755 match result {
756 Ok((blob_index, _, _, item)) => items.push((blob_index, item)),
757 Err(err) => panic!("Failed to read item: {err}"),
758 }
759 }
760 }
761
762 assert_eq!(items.len(), data_items.len());
764 for ((expected_index, expected_data), (actual_index, actual_data)) in
765 data_items.iter().zip(items.iter())
766 {
767 assert_eq!(actual_index, expected_index);
768 assert_eq!(actual_data, expected_data);
769 }
770
771 journal.destroy().await.expect("Failed to destroy journal");
773 });
774 }
775
776 #[test_traced]
777 fn test_journal_prune_blobs() {
778 let executor = deterministic::Runner::default();
780
781 executor.start(|context| async move {
783 let cfg = Config {
785 partition: "test-partition".into(),
786 compression: None,
787 codec_config: (),
788 page_cache: CacheRef::from_pooler(&context, PAGE_SIZE, PAGE_CACHE_SIZE),
789 write_buffer: NZUsize!(1024),
790 };
791
792 let mut journal = Journal::init(context.child("first"), cfg.clone())
794 .await
795 .expect("Failed to initialize journal");
796
797 for index in 1u64..=5u64 {
799 journal
800 .append(index, &index)
801 .await
802 .expect("Failed to append data");
803 journal.sync(index).await.expect("Failed to sync blob");
804 }
805
806 let data = 99;
808 journal
809 .append(2u64, &data)
810 .await
811 .expect("Failed to append data");
812 journal.sync(2u64).await.expect("Failed to sync blob");
813
814 journal.prune(3).await.expect("Failed to prune blobs");
816
817 let buffer = context.encode();
819 assert!(buffer.contains("first_pruned_total 2"));
820
821 journal.prune(2).await.expect("Failed to no-op prune");
823 let buffer = context.encode();
824 assert!(buffer.contains("first_pruned_total 2"));
825
826 drop(journal);
828 let mut journal = Journal::init(context.child("second"), cfg.clone())
829 .await
830 .expect("Failed to re-initialize journal");
831
832 let mut items = Vec::<(u64, u64)>::new();
834 {
835 let stream = journal
836 .replay(0, 0, NZUsize!(1024))
837 .await
838 .expect("unable to setup replay");
839 pin_mut!(stream);
840 while let Some(result) = stream.next().await {
841 match result {
842 Ok((blob_index, _, _, item)) => items.push((blob_index, item)),
843 Err(err) => panic!("Failed to read item: {err}"),
844 }
845 }
846 }
847
848 assert_eq!(items.len(), 3);
850 let expected_indices = [3u64, 4u64, 5u64];
851 for (item, expected_index) in items.iter().zip(expected_indices.iter()) {
852 assert_eq!(item.0, *expected_index);
853 }
854
855 journal.prune(6).await.expect("Failed to prune blobs");
857
858 drop(journal);
860
861 assert!(context
866 .scan(&cfg.partition)
867 .await
868 .expect("Failed to list blobs")
869 .is_empty());
870 });
871 }
872
873 #[test_traced]
874 fn test_journal_prune_guard() {
875 let executor = deterministic::Runner::default();
876
877 executor.start(|context| async move {
878 let cfg = Config {
879 partition: "test-partition".into(),
880 compression: None,
881 codec_config: (),
882 page_cache: CacheRef::from_pooler(&context, PAGE_SIZE, PAGE_CACHE_SIZE),
883 write_buffer: NZUsize!(1024),
884 };
885
886 let mut journal = Journal::init(context.child("storage"), cfg.clone())
887 .await
888 .expect("Failed to initialize journal");
889
890 for section in 1u64..=5u64 {
892 journal
893 .append(section, &(section as i32))
894 .await
895 .expect("Failed to append data");
896 journal.sync(section).await.expect("Failed to sync");
897 }
898
899 journal.prune(3).await.expect("Failed to prune");
901
902 match journal.append(1, &100).await {
906 Err(Error::AlreadyPrunedToSection(3)) => {}
907 other => panic!("Expected AlreadyPrunedToSection(3), got {other:?}"),
908 }
909
910 match journal.append(2, &100).await {
911 Err(Error::AlreadyPrunedToSection(3)) => {}
912 other => panic!("Expected AlreadyPrunedToSection(3), got {other:?}"),
913 }
914
915 match journal.get(1, 0).await {
917 Err(Error::AlreadyPrunedToSection(3)) => {}
918 other => panic!("Expected AlreadyPrunedToSection(3), got {other:?}"),
919 }
920
921 match journal.size(1) {
923 Err(Error::AlreadyPrunedToSection(3)) => {}
924 other => panic!("Expected AlreadyPrunedToSection(3), got {other:?}"),
925 }
926
927 match journal.rewind(2, 0).await {
929 Err(Error::AlreadyPrunedToSection(3)) => {}
930 other => panic!("Expected AlreadyPrunedToSection(3), got {other:?}"),
931 }
932
933 match journal.rewind_section(1, 0).await {
935 Err(Error::AlreadyPrunedToSection(3)) => {}
936 other => panic!("Expected AlreadyPrunedToSection(3), got {other:?}"),
937 }
938
939 match journal.sync(2).await {
941 Err(Error::AlreadyPrunedToSection(3)) => {}
942 other => panic!("Expected AlreadyPrunedToSection(3), got {other:?}"),
943 }
944
945 assert!(journal.get(3, 0).await.is_ok());
947 assert!(journal.get(4, 0).await.is_ok());
948 assert!(journal.get(5, 0).await.is_ok());
949 assert!(journal.size(3).is_ok());
950 assert!(journal.sync(4).await.is_ok());
951
952 journal
954 .append(3, &999)
955 .await
956 .expect("Should be able to append to section 3");
957
958 journal.prune(5).await.expect("Failed to prune");
960
961 match journal.get(3, 0).await {
963 Err(Error::AlreadyPrunedToSection(5)) => {}
964 other => panic!("Expected AlreadyPrunedToSection(5), got {other:?}"),
965 }
966
967 match journal.get(4, 0).await {
968 Err(Error::AlreadyPrunedToSection(5)) => {}
969 other => panic!("Expected AlreadyPrunedToSection(5), got {other:?}"),
970 }
971
972 assert!(journal.get(5, 0).await.is_ok());
974 });
975 }
976
977 #[test_traced]
978 fn test_journal_prune_guard_across_restart() {
979 let executor = deterministic::Runner::default();
980
981 executor.start(|context| async move {
982 let cfg = Config {
983 partition: "test-partition".into(),
984 compression: None,
985 codec_config: (),
986 page_cache: CacheRef::from_pooler(&context, PAGE_SIZE, PAGE_CACHE_SIZE),
987 write_buffer: NZUsize!(1024),
988 };
989
990 {
992 let mut journal = Journal::init(context.child("first"), cfg.clone())
993 .await
994 .expect("Failed to initialize journal");
995
996 for section in 1u64..=5u64 {
997 journal
998 .append(section, &(section as i32))
999 .await
1000 .expect("Failed to append data");
1001 journal.sync(section).await.expect("Failed to sync");
1002 }
1003
1004 journal.prune(3).await.expect("Failed to prune");
1005 }
1006
1007 {
1009 let journal = Journal::<_, i32>::init(context.child("second"), cfg.clone())
1010 .await
1011 .expect("Failed to re-initialize journal");
1012
1013 match journal.get(1, 0).await {
1016 Err(Error::SectionOutOfRange(1)) => {}
1017 other => panic!("Expected SectionOutOfRange(1), got {other:?}"),
1018 }
1019
1020 match journal.get(2, 0).await {
1021 Err(Error::SectionOutOfRange(2)) => {}
1022 other => panic!("Expected SectionOutOfRange(2), got {other:?}"),
1023 }
1024
1025 assert!(journal.get(3, 0).await.is_ok());
1027 assert!(journal.get(4, 0).await.is_ok());
1028 assert!(journal.get(5, 0).await.is_ok());
1029 }
1030 });
1031 }
1032
1033 #[test_traced]
1034 fn test_journal_with_invalid_blob_name() {
1035 let executor = deterministic::Runner::default();
1037
1038 executor.start(|context| async move {
1040 let cfg = Config {
1042 partition: "test-partition".into(),
1043 compression: None,
1044 codec_config: (),
1045 page_cache: CacheRef::from_pooler(&context, PAGE_SIZE, PAGE_CACHE_SIZE),
1046 write_buffer: NZUsize!(1024),
1047 };
1048
1049 let invalid_blob_name = b"invalid"; let (blob, _) = context
1052 .open(&cfg.partition, invalid_blob_name)
1053 .await
1054 .expect("Failed to create blob with invalid name");
1055 blob.sync().await.expect("Failed to sync blob");
1056
1057 let result = Journal::<_, u64>::init(context, cfg).await;
1059
1060 assert!(matches!(result, Err(Error::InvalidBlobName(_))));
1062 });
1063 }
1064
1065 #[test_traced]
1066 fn test_journal_read_size_missing() {
1067 let executor = deterministic::Runner::default();
1069
1070 executor.start(|context| async move {
1072 let cfg = Config {
1074 partition: "test-partition".into(),
1075 compression: None,
1076 codec_config: (),
1077 page_cache: CacheRef::from_pooler(&context, PAGE_SIZE, PAGE_CACHE_SIZE),
1078 write_buffer: NZUsize!(1024),
1079 };
1080
1081 let section = 1u64;
1083 let blob_name = section.to_be_bytes();
1084 let (blob, _) = context
1085 .open(&cfg.partition, &blob_name)
1086 .await
1087 .expect("Failed to create blob");
1088
1089 let mut incomplete_data = Vec::new();
1091 UInt(u32::MAX).write(&mut incomplete_data);
1092 incomplete_data.truncate(1);
1093 blob.write_at_sync(0, incomplete_data)
1094 .await
1095 .expect("Failed to write incomplete data");
1096
1097 let mut journal = Journal::init(context, cfg)
1099 .await
1100 .expect("Failed to initialize journal");
1101
1102 let stream = journal
1104 .replay(0, 0, NZUsize!(1024))
1105 .await
1106 .expect("unable to setup replay");
1107 pin_mut!(stream);
1108 let mut items = Vec::<(u64, u64)>::new();
1109 while let Some(result) = stream.next().await {
1110 match result {
1111 Ok((blob_index, _, _, item)) => items.push((blob_index, item)),
1112 Err(err) => panic!("Failed to read item: {err}"),
1113 }
1114 }
1115 assert!(items.is_empty());
1116 });
1117 }
1118
1119 #[test_traced]
1120 fn test_journal_replay_reports_resize_error_on_trailing_bytes() {
1121 let executor = deterministic::Runner::default();
1122 executor.start(|context| async move {
1123 let cfg = Config {
1124 partition: "test-partition".into(),
1125 compression: None,
1126 codec_config: (),
1127 page_cache: CacheRef::from_pooler(&context, PAGE_SIZE, PAGE_CACHE_SIZE),
1128 write_buffer: NZUsize!(1024),
1129 };
1130
1131 let section = 1u64;
1134 let item = [10u8; 1021];
1135 let item_record_size =
1136 UInt(item.encode_size() as u32).encode_size() + item.encode_size();
1137 assert_eq!(item_record_size, PAGE_SIZE.get() as usize - 1);
1138
1139 let mut journal = Journal::init(context.child("first"), cfg.clone())
1140 .await
1141 .expect("Failed to initialize journal");
1142 journal
1143 .append(section, &item)
1144 .await
1145 .expect("Failed to append item");
1146 journal
1147 .append_raw(section, IoBuf::copy_from_slice(&[0xFF, 0xFF]))
1148 .await
1149 .expect("Failed to append trailing bytes");
1150 journal.sync(section).await.expect("Failed to sync journal");
1151 drop(journal);
1152
1153 let mut journal = Journal::init(context.child("second"), cfg)
1154 .await
1155 .expect("Failed to re-initialize journal");
1156 *context.storage_fault_config().write() = deterministic::FaultConfig {
1157 resize_rate: Some(1.0),
1158 ..Default::default()
1159 };
1160
1161 let stream = journal
1162 .replay(0, 0, NZUsize!(1024))
1163 .await
1164 .expect("unable to setup replay");
1165 pin_mut!(stream);
1166
1167 let first = stream
1168 .next()
1169 .await
1170 .expect("expected item before trailing bytes")
1171 .expect("failed to replay valid item");
1172 assert_eq!(first, (section, 0, item.encode_size() as u32, item));
1173
1174 match stream.next().await {
1176 Some(Err(_)) => {}
1177 other => {
1178 panic!("expected resize error while repairing trailing bytes, got {other:?}")
1179 }
1180 }
1181 assert!(stream.next().await.is_none());
1182 });
1183 }
1184
1185 #[test_traced]
1186 fn test_journal_read_item_missing() {
1187 let executor = deterministic::Runner::default();
1189
1190 executor.start(|context| async move {
1192 let cfg = Config {
1194 partition: "test-partition".into(),
1195 compression: None,
1196 codec_config: (),
1197 page_cache: CacheRef::from_pooler(&context, PAGE_SIZE, PAGE_CACHE_SIZE),
1198 write_buffer: NZUsize!(1024),
1199 };
1200
1201 let section = 1u64;
1203 let blob_name = section.to_be_bytes();
1204 let (blob, _) = context
1205 .open(&cfg.partition, &blob_name)
1206 .await
1207 .expect("Failed to create blob");
1208
1209 let item_size: u32 = 10; let mut buf = Vec::new();
1212 UInt(item_size).write(&mut buf); let data = [2u8; 5];
1214 BufMut::put_slice(&mut buf, &data);
1215 blob.write_at_sync(0, buf)
1216 .await
1217 .expect("Failed to write incomplete item");
1218
1219 let mut journal = Journal::init(context, cfg)
1221 .await
1222 .expect("Failed to initialize journal");
1223
1224 let stream = journal
1226 .replay(0, 0, NZUsize!(1024))
1227 .await
1228 .expect("unable to setup replay");
1229 pin_mut!(stream);
1230 let mut items = Vec::<(u64, u64)>::new();
1231 while let Some(result) = stream.next().await {
1232 match result {
1233 Ok((blob_index, _, _, item)) => items.push((blob_index, item)),
1234 Err(err) => panic!("Failed to read item: {err}"),
1235 }
1236 }
1237 assert!(items.is_empty());
1238 });
1239 }
1240
1241 #[test_traced]
1242 fn test_journal_read_checksum_missing() {
1243 let executor = deterministic::Runner::default();
1245
1246 executor.start(|context| async move {
1248 let cfg = Config {
1250 partition: "test-partition".into(),
1251 compression: None,
1252 codec_config: (),
1253 page_cache: CacheRef::from_pooler(&context, PAGE_SIZE, PAGE_CACHE_SIZE),
1254 write_buffer: NZUsize!(1024),
1255 };
1256
1257 let section = 1u64;
1259 let blob_name = section.to_be_bytes();
1260 let (blob, _) = context
1261 .open(&cfg.partition, &blob_name)
1262 .await
1263 .expect("Failed to create blob");
1264
1265 let item_data = b"Test data";
1267 let item_size = item_data.len() as u32;
1268
1269 let mut buf = Vec::new();
1271 UInt(item_size).write(&mut buf);
1272 BufMut::put_slice(&mut buf, item_data);
1273 blob.write_at_sync(0, buf)
1274 .await
1275 .expect("Failed to write item without checksum");
1276
1277 let mut journal = Journal::init(context, cfg)
1279 .await
1280 .expect("Failed to initialize journal");
1281
1282 let stream = journal
1286 .replay(0, 0, NZUsize!(1024))
1287 .await
1288 .expect("unable to setup replay");
1289 pin_mut!(stream);
1290 let mut items = Vec::<(u64, u64)>::new();
1291 while let Some(result) = stream.next().await {
1292 match result {
1293 Ok((blob_index, _, _, item)) => items.push((blob_index, item)),
1294 Err(err) => panic!("Failed to read item: {err}"),
1295 }
1296 }
1297 assert!(items.is_empty());
1298 });
1299 }
1300
1301 #[test_traced]
1302 fn test_journal_read_checksum_mismatch() {
1303 let executor = deterministic::Runner::default();
1305
1306 executor.start(|context| async move {
1308 let cfg = Config {
1310 partition: "test-partition".into(),
1311 compression: None,
1312 codec_config: (),
1313 page_cache: CacheRef::from_pooler(&context, PAGE_SIZE, PAGE_CACHE_SIZE),
1314 write_buffer: NZUsize!(1024),
1315 };
1316
1317 let section = 1u64;
1319 let blob_name = section.to_be_bytes();
1320 let (blob, _) = context
1321 .open(&cfg.partition, &blob_name)
1322 .await
1323 .expect("Failed to create blob");
1324
1325 let item_data = b"Test data";
1327 let item_size = item_data.len() as u32;
1328 let incorrect_checksum: u32 = 0xDEADBEEF;
1329
1330 let mut buf = Vec::new();
1332 UInt(item_size).write(&mut buf);
1333 BufMut::put_slice(&mut buf, item_data);
1334 buf.put_u32(incorrect_checksum);
1335 blob.write_at_sync(0, buf)
1336 .await
1337 .expect("Failed to write item with bad checksum");
1338
1339 let mut journal = Journal::init(context.child("storage"), cfg.clone())
1341 .await
1342 .expect("Failed to initialize journal");
1343
1344 {
1346 let stream = journal
1347 .replay(0, 0, NZUsize!(1024))
1348 .await
1349 .expect("unable to setup replay");
1350 pin_mut!(stream);
1351 let mut items = Vec::<(u64, u64)>::new();
1352 while let Some(result) = stream.next().await {
1353 match result {
1354 Ok((blob_index, _, _, item)) => items.push((blob_index, item)),
1355 Err(err) => panic!("Failed to read item: {err}"),
1356 }
1357 }
1358 assert!(items.is_empty());
1359 }
1360 drop(journal);
1361
1362 let (_, blob_size) = context
1364 .open(&cfg.partition, §ion.to_be_bytes())
1365 .await
1366 .expect("Failed to open blob");
1367 assert_eq!(blob_size, 0);
1368 });
1369 }
1370
1371 #[test_traced]
1372 fn test_journal_truncation_recovery() {
1373 let executor = deterministic::Runner::default();
1375
1376 executor.start(|context| async move {
1378 let cfg = Config {
1380 partition: "test-partition".into(),
1381 compression: None,
1382 codec_config: (),
1383 page_cache: CacheRef::from_pooler(&context, PAGE_SIZE, PAGE_CACHE_SIZE),
1384 write_buffer: NZUsize!(1024),
1385 };
1386
1387 let mut journal = Journal::init(context.child("first"), cfg.clone())
1389 .await
1390 .expect("Failed to initialize journal");
1391
1392 journal.append(1, &1).await.expect("Failed to append data");
1394
1395 let data_items = vec![(2u64, 2), (2u64, 3), (2u64, 4)];
1397 for (index, data) in &data_items {
1398 journal
1399 .append(*index, data)
1400 .await
1401 .expect("Failed to append data");
1402 journal.sync(*index).await.expect("Failed to sync blob");
1403 }
1404
1405 journal.sync_all().await.expect("Failed to sync");
1407 drop(journal);
1408
1409 let (blob, blob_size) = context
1411 .open(&cfg.partition, &2u64.to_be_bytes())
1412 .await
1413 .expect("Failed to open blob");
1414 blob.resize(blob_size - 4)
1415 .await
1416 .expect("Failed to corrupt blob");
1417 blob.sync().await.expect("Failed to sync blob");
1418
1419 let mut journal = Journal::init(context.child("second"), cfg.clone())
1421 .await
1422 .expect("Failed to re-initialize journal");
1423
1424 let mut items = Vec::<(u64, u32)>::new();
1426 {
1427 let stream = journal
1428 .replay(0, 0, NZUsize!(1024))
1429 .await
1430 .expect("unable to setup replay");
1431 pin_mut!(stream);
1432 while let Some(result) = stream.next().await {
1433 match result {
1434 Ok((blob_index, _, _, item)) => items.push((blob_index, item)),
1435 Err(err) => panic!("Failed to read item: {err}"),
1436 }
1437 }
1438 }
1439 drop(journal);
1440
1441 assert_eq!(items.len(), 1);
1443 assert_eq!(items[0].0, 1);
1444 assert_eq!(items[0].1, 1);
1445
1446 let (_, blob_size) = context
1448 .open(&cfg.partition, &2u64.to_be_bytes())
1449 .await
1450 .expect("Failed to open blob");
1451 assert_eq!(blob_size, 0);
1452
1453 let mut journal = Journal::init(context.child("third"), cfg.clone())
1455 .await
1456 .expect("Failed to re-initialize journal");
1457
1458 let mut items = Vec::<(u64, u32)>::new();
1460 {
1461 let stream = journal
1462 .replay(0, 0, NZUsize!(1024))
1463 .await
1464 .expect("unable to setup replay");
1465 pin_mut!(stream);
1466 while let Some(result) = stream.next().await {
1467 match result {
1468 Ok((blob_index, _, _, item)) => items.push((blob_index, item)),
1469 Err(err) => panic!("Failed to read item: {err}"),
1470 }
1471 }
1472 }
1473
1474 assert_eq!(items.len(), 1);
1476 assert_eq!(items[0].0, 1);
1477 assert_eq!(items[0].1, 1);
1478
1479 let (_offset, _) = journal.append(2, &5).await.expect("Failed to append data");
1481 journal.sync(2).await.expect("Failed to sync blob");
1482
1483 let item = journal.get(2, 0).await.expect("Failed to get item");
1485 assert_eq!(item, 5);
1486
1487 drop(journal);
1489
1490 let mut journal = Journal::init(context.child("storage"), cfg.clone())
1492 .await
1493 .expect("Failed to re-initialize journal");
1494
1495 let mut items = Vec::<(u64, u32)>::new();
1497 {
1498 let stream = journal
1499 .replay(0, 0, NZUsize!(1024))
1500 .await
1501 .expect("unable to setup replay");
1502 pin_mut!(stream);
1503 while let Some(result) = stream.next().await {
1504 match result {
1505 Ok((blob_index, _, _, item)) => items.push((blob_index, item)),
1506 Err(err) => panic!("Failed to read item: {err}"),
1507 }
1508 }
1509 }
1510
1511 assert_eq!(items.len(), 2);
1513 assert_eq!(items[0].0, 1);
1514 assert_eq!(items[0].1, 1);
1515 assert_eq!(items[1].0, 2);
1516 assert_eq!(items[1].1, 5);
1517 });
1518 }
1519
1520 #[test_traced]
1521 fn test_journal_handling_extra_data() {
1522 let executor = deterministic::Runner::default();
1524
1525 executor.start(|context| async move {
1527 let cfg = Config {
1529 partition: "test-partition".into(),
1530 compression: None,
1531 codec_config: (),
1532 page_cache: CacheRef::from_pooler(&context, PAGE_SIZE, PAGE_CACHE_SIZE),
1533 write_buffer: NZUsize!(1024),
1534 };
1535
1536 let mut journal = Journal::init(context.child("first"), cfg.clone())
1538 .await
1539 .expect("Failed to initialize journal");
1540
1541 journal.append(1, &1).await.expect("Failed to append data");
1543
1544 let data_items = vec![(2u64, 2), (2u64, 3), (2u64, 4)];
1546 for (index, data) in &data_items {
1547 journal
1548 .append(*index, data)
1549 .await
1550 .expect("Failed to append data");
1551 journal.sync(*index).await.expect("Failed to sync blob");
1552 }
1553
1554 journal.sync_all().await.expect("Failed to sync");
1556 drop(journal);
1557
1558 let (blob, blob_size) = context
1560 .open(&cfg.partition, &2u64.to_be_bytes())
1561 .await
1562 .expect("Failed to open blob");
1563 blob.write_at_sync(blob_size, vec![0u8; 16])
1564 .await
1565 .expect("Failed to add extra data");
1566
1567 let mut journal = Journal::init(context.child("second"), cfg)
1569 .await
1570 .expect("Failed to re-initialize journal");
1571
1572 let mut items = Vec::<(u64, i32)>::new();
1574 let stream = journal
1575 .replay(0, 0, NZUsize!(1024))
1576 .await
1577 .expect("unable to setup replay");
1578 pin_mut!(stream);
1579 while let Some(result) = stream.next().await {
1580 match result {
1581 Ok((blob_index, _, _, item)) => items.push((blob_index, item)),
1582 Err(err) => panic!("Failed to read item: {err}"),
1583 }
1584 }
1585 });
1586 }
1587
1588 #[test_traced]
1589 fn test_journal_rewind() {
1590 let executor = deterministic::Runner::default();
1592 executor.start(|context| async move {
1593 let cfg = Config {
1595 partition: "test-partition".into(),
1596 compression: None,
1597 codec_config: (),
1598 page_cache: CacheRef::from_pooler(&context, PAGE_SIZE, PAGE_CACHE_SIZE),
1599 write_buffer: NZUsize!(1024),
1600 };
1601 let mut journal = Journal::init(context, cfg).await.unwrap();
1602
1603 let size = journal.size(1).unwrap();
1605 assert_eq!(size, 0);
1606
1607 journal.append(1, &42i32).await.unwrap();
1609
1610 let size = journal.size(1).unwrap();
1612 assert!(size > 0);
1613
1614 journal.append(1, &43i32).await.unwrap();
1616 let new_size = journal.size(1).unwrap();
1617 assert!(new_size > size);
1618
1619 let size = journal.size(2).unwrap();
1621 assert_eq!(size, 0);
1622
1623 journal.append(2, &44i32).await.unwrap();
1625
1626 let size = journal.size(2).unwrap();
1628 assert!(size > 0);
1629
1630 journal.rewind(1, 0).await.unwrap();
1632
1633 let size = journal.size(1).unwrap();
1635 assert_eq!(size, 0);
1636
1637 let size = journal.size(2).unwrap();
1639 assert_eq!(size, 0);
1640 });
1641 }
1642
1643 #[test_traced]
1644 fn test_journal_rewind_max_section() {
1645 let executor = deterministic::Runner::default();
1646 executor.start(|context| async move {
1647 let cfg = Config {
1648 partition: "test-partition".into(),
1649 compression: None,
1650 codec_config: (),
1651 page_cache: CacheRef::from_pooler(&context, PAGE_SIZE, PAGE_CACHE_SIZE),
1652 write_buffer: NZUsize!(1024),
1653 };
1654 let mut journal = Journal::init(context, cfg).await.unwrap();
1655
1656 let (offset, _) = journal.append(u64::MAX, &42i32).await.unwrap();
1658 let size = journal.size(u64::MAX).unwrap();
1659 assert!(size > 0);
1660
1661 journal.rewind(u64::MAX, size).await.unwrap();
1663
1664 assert_eq!(journal.size(u64::MAX).unwrap(), size);
1666 assert_eq!(journal.get(u64::MAX, offset).await.unwrap(), 42i32);
1667 });
1668 }
1669
1670 #[test_traced]
1671 fn test_journal_rewind_section() {
1672 let executor = deterministic::Runner::default();
1674 executor.start(|context| async move {
1675 let cfg = Config {
1677 partition: "test-partition".into(),
1678 compression: None,
1679 codec_config: (),
1680 page_cache: CacheRef::from_pooler(&context, PAGE_SIZE, PAGE_CACHE_SIZE),
1681 write_buffer: NZUsize!(1024),
1682 };
1683 let mut journal = Journal::init(context, cfg).await.unwrap();
1684
1685 let size = journal.size(1).unwrap();
1687 assert_eq!(size, 0);
1688
1689 journal.append(1, &42i32).await.unwrap();
1691
1692 let size = journal.size(1).unwrap();
1694 assert!(size > 0);
1695
1696 journal.append(1, &43i32).await.unwrap();
1698 let new_size = journal.size(1).unwrap();
1699 assert!(new_size > size);
1700
1701 let size = journal.size(2).unwrap();
1703 assert_eq!(size, 0);
1704
1705 journal.append(2, &44i32).await.unwrap();
1707
1708 let size = journal.size(2).unwrap();
1710 assert!(size > 0);
1711
1712 journal.rewind_section(1, 0).await.unwrap();
1714
1715 let size = journal.size(1).unwrap();
1717 assert_eq!(size, 0);
1718
1719 let size = journal.size(2).unwrap();
1721 assert!(size > 0);
1722 });
1723 }
1724
1725 #[test_traced]
1726 fn test_journal_small_items() {
1727 let executor = deterministic::Runner::default();
1728 executor.start(|context| async move {
1729 let cfg = Config {
1730 partition: "test-partition".into(),
1731 compression: None,
1732 codec_config: (),
1733 page_cache: CacheRef::from_pooler(&context, PAGE_SIZE, PAGE_CACHE_SIZE),
1734 write_buffer: NZUsize!(1024),
1735 };
1736
1737 let mut journal = Journal::init(context.child("first"), cfg.clone())
1738 .await
1739 .expect("Failed to initialize journal");
1740
1741 let num_items = 100;
1743 let mut offsets = Vec::new();
1744 for i in 0..num_items {
1745 let (offset, size) = journal
1746 .append(1, &(i as u8))
1747 .await
1748 .expect("Failed to append data");
1749 assert_eq!(size, 1, "u8 should encode to 1 byte");
1750 offsets.push(offset);
1751 }
1752 journal.sync(1).await.expect("Failed to sync");
1753
1754 for (i, &offset) in offsets.iter().enumerate() {
1756 let item: u8 = journal.get(1, offset).await.expect("Failed to get item");
1757 assert_eq!(item, i as u8, "Item mismatch at offset {offset}");
1758 }
1759
1760 drop(journal);
1762 let mut journal = Journal::<_, u8>::init(context.child("second"), cfg)
1763 .await
1764 .expect("Failed to re-initialize journal");
1765
1766 let stream = journal
1768 .replay(0, 0, NZUsize!(1024))
1769 .await
1770 .expect("Failed to setup replay");
1771 pin_mut!(stream);
1772
1773 let mut count = 0;
1774 while let Some(result) = stream.next().await {
1775 let (section, offset, size, item) = result.expect("Failed to replay item");
1776 assert_eq!(section, 1);
1777 assert_eq!(offset, offsets[count]);
1778 assert_eq!(size, 1);
1779 assert_eq!(item, count as u8);
1780 count += 1;
1781 }
1782 assert_eq!(count, num_items, "Should replay all items");
1783 });
1784 }
1785
1786 #[test_traced]
1787 fn test_journal_rewind_many_sections() {
1788 let executor = deterministic::Runner::default();
1789 executor.start(|context| async move {
1790 let cfg = Config {
1791 partition: "test-partition".into(),
1792 compression: None,
1793 codec_config: (),
1794 page_cache: CacheRef::from_pooler(&context, PAGE_SIZE, PAGE_CACHE_SIZE),
1795 write_buffer: NZUsize!(1024),
1796 };
1797 let mut journal = Journal::init(context.child("storage"), cfg.clone())
1798 .await
1799 .unwrap();
1800
1801 for section in 1u64..=10 {
1803 journal.append(section, &(section as i32)).await.unwrap();
1804 }
1805 journal.sync_all().await.unwrap();
1806
1807 for section in 1u64..=10 {
1809 let size = journal.size(section).unwrap();
1810 assert!(size > 0, "section {section} should have data");
1811 }
1812
1813 journal.rewind(5, journal.size(5).unwrap()).await.unwrap();
1815
1816 for section in 1u64..=5 {
1818 let size = journal.size(section).unwrap();
1819 assert!(size > 0, "section {section} should still have data");
1820 }
1821
1822 for section in 6u64..=10 {
1824 let size = journal.size(section).unwrap();
1825 assert_eq!(size, 0, "section {section} should be removed");
1826 }
1827
1828 {
1830 let stream = journal.replay(0, 0, NZUsize!(1024)).await.unwrap();
1831 pin_mut!(stream);
1832 let mut items = Vec::new();
1833 while let Some(result) = stream.next().await {
1834 let (section, _, _, item) = result.unwrap();
1835 items.push((section, item));
1836 }
1837 assert_eq!(items.len(), 5);
1838 for (i, (section, item)) in items.iter().enumerate() {
1839 assert_eq!(*section, (i + 1) as u64);
1840 assert_eq!(*item, (i + 1) as i32);
1841 }
1842 }
1843
1844 journal.destroy().await.unwrap();
1845 });
1846 }
1847
1848 #[test_traced]
1849 fn test_journal_rewind_partial_truncation() {
1850 let executor = deterministic::Runner::default();
1851 executor.start(|context| async move {
1852 let cfg = Config {
1853 partition: "test-partition".into(),
1854 compression: None,
1855 codec_config: (),
1856 page_cache: CacheRef::from_pooler(&context, PAGE_SIZE, PAGE_CACHE_SIZE),
1857 write_buffer: NZUsize!(1024),
1858 };
1859 let mut journal = Journal::init(context.child("storage"), cfg.clone())
1860 .await
1861 .unwrap();
1862
1863 let mut sizes = Vec::new();
1865 for i in 0..5 {
1866 journal.append(1, &i).await.unwrap();
1867 journal.sync(1).await.unwrap();
1868 sizes.push(journal.size(1).unwrap());
1869 }
1870
1871 let target_size = sizes[2];
1873 journal.rewind(1, target_size).await.unwrap();
1874
1875 let new_size = journal.size(1).unwrap();
1877 assert_eq!(new_size, target_size);
1878
1879 {
1881 let stream = journal.replay(0, 0, NZUsize!(1024)).await.unwrap();
1882 pin_mut!(stream);
1883 let mut items = Vec::new();
1884 while let Some(result) = stream.next().await {
1885 let (_, _, _, item) = result.unwrap();
1886 items.push(item);
1887 }
1888 assert_eq!(items.len(), 3);
1889 for (i, item) in items.iter().enumerate() {
1890 assert_eq!(*item, i as i32);
1891 }
1892 }
1893
1894 journal.destroy().await.unwrap();
1895 });
1896 }
1897
1898 #[test_traced]
1899 fn test_journal_rewind_nonexistent_target() {
1900 let executor = deterministic::Runner::default();
1901 executor.start(|context| async move {
1902 let cfg = Config {
1903 partition: "test-partition".into(),
1904 compression: None,
1905 codec_config: (),
1906 page_cache: CacheRef::from_pooler(&context, PAGE_SIZE, PAGE_CACHE_SIZE),
1907 write_buffer: NZUsize!(1024),
1908 };
1909 let mut journal = Journal::init(context.child("storage"), cfg.clone())
1910 .await
1911 .unwrap();
1912
1913 for section in 5u64..=7 {
1915 journal.append(section, &(section as i32)).await.unwrap();
1916 }
1917 journal.sync_all().await.unwrap();
1918
1919 journal.rewind(3, 0).await.unwrap();
1921
1922 for section in 5u64..=7 {
1924 let size = journal.size(section).unwrap();
1925 assert_eq!(size, 0, "section {section} should be removed");
1926 }
1927
1928 {
1930 let stream = journal.replay(0, 0, NZUsize!(1024)).await.unwrap();
1931 pin_mut!(stream);
1932 let items: Vec<_> = stream.collect().await;
1933 assert!(items.is_empty());
1934 }
1935
1936 journal.destroy().await.unwrap();
1937 });
1938 }
1939
1940 #[test_traced]
1941 fn test_journal_rewind_persistence() {
1942 let executor = deterministic::Runner::default();
1943 executor.start(|context| async move {
1944 let cfg = Config {
1945 partition: "test-partition".into(),
1946 compression: None,
1947 codec_config: (),
1948 page_cache: CacheRef::from_pooler(&context, PAGE_SIZE, PAGE_CACHE_SIZE),
1949 write_buffer: NZUsize!(1024),
1950 };
1951
1952 let mut journal = Journal::init(context.child("first"), cfg.clone())
1954 .await
1955 .unwrap();
1956 for section in 1u64..=5 {
1957 journal.append(section, &(section as i32)).await.unwrap();
1958 }
1959 journal.sync_all().await.unwrap();
1960
1961 let size = journal.size(2).unwrap();
1963 journal.rewind(2, size).await.unwrap();
1964 journal.sync_all().await.unwrap();
1965 drop(journal);
1966
1967 let mut journal = Journal::<_, i32>::init(context.child("second"), cfg.clone())
1969 .await
1970 .unwrap();
1971
1972 for section in 1u64..=2 {
1974 let size = journal.size(section).unwrap();
1975 assert!(size > 0, "section {section} should have data after restart");
1976 }
1977
1978 for section in 3u64..=5 {
1980 let size = journal.size(section).unwrap();
1981 assert_eq!(size, 0, "section {section} should be gone after restart");
1982 }
1983
1984 {
1986 let stream = journal.replay(0, 0, NZUsize!(1024)).await.unwrap();
1987 pin_mut!(stream);
1988 let mut items = Vec::new();
1989 while let Some(result) = stream.next().await {
1990 let (section, _, _, item) = result.unwrap();
1991 items.push((section, item));
1992 }
1993 assert_eq!(items.len(), 2);
1994 assert_eq!(items[0], (1, 1));
1995 assert_eq!(items[1], (2, 2));
1996 }
1997
1998 journal.destroy().await.unwrap();
1999 });
2000 }
2001
2002 #[test_traced]
2003 fn test_journal_rewind_to_zero_removes_all_newer() {
2004 let executor = deterministic::Runner::default();
2005 executor.start(|context| async move {
2006 let cfg = Config {
2007 partition: "test-partition".into(),
2008 compression: None,
2009 codec_config: (),
2010 page_cache: CacheRef::from_pooler(&context, PAGE_SIZE, PAGE_CACHE_SIZE),
2011 write_buffer: NZUsize!(1024),
2012 };
2013 let mut journal = Journal::init(context.child("storage"), cfg.clone())
2014 .await
2015 .unwrap();
2016
2017 for section in 1u64..=3 {
2019 journal.append(section, &(section as i32)).await.unwrap();
2020 }
2021 journal.sync_all().await.unwrap();
2022
2023 journal.rewind(1, 0).await.unwrap();
2025
2026 let size = journal.size(1).unwrap();
2028 assert_eq!(size, 0, "section 1 should be empty");
2029
2030 for section in 2u64..=3 {
2032 let size = journal.size(section).unwrap();
2033 assert_eq!(size, 0, "section {section} should be removed");
2034 }
2035
2036 {
2038 let stream = journal.replay(0, 0, NZUsize!(1024)).await.unwrap();
2039 pin_mut!(stream);
2040 let items: Vec<_> = stream.collect().await;
2041 assert!(items.is_empty());
2042 }
2043
2044 journal.destroy().await.unwrap();
2045 });
2046 }
2047
2048 #[test_traced]
2049 fn test_journal_replay_start_offset_with_trailing_bytes() {
2050 let executor = deterministic::Runner::default();
2052 executor.start(|context| async move {
2053 let cfg = Config {
2054 partition: "test-partition".into(),
2055 compression: None,
2056 codec_config: (),
2057 page_cache: CacheRef::from_pooler(&context, PAGE_SIZE, PAGE_CACHE_SIZE),
2058 write_buffer: NZUsize!(1024),
2059 };
2060 let mut journal = Journal::init(context.child("first"), cfg.clone())
2061 .await
2062 .expect("Failed to initialize journal");
2063
2064 for i in 0..5i32 {
2066 journal.append(1, &i).await.unwrap();
2067 }
2068 journal.sync(1).await.unwrap();
2069 let valid_logical_size = journal.size(1).unwrap();
2070 drop(journal);
2071
2072 let (blob, physical_size_before) = context
2074 .open(&cfg.partition, &1u64.to_be_bytes())
2075 .await
2076 .unwrap();
2077
2078 blob.write_at_sync(physical_size_before, vec![0xFF, 0xFF])
2081 .await
2082 .unwrap();
2083
2084 let start_offset = valid_logical_size;
2088 {
2089 let mut journal = Journal::<_, i32>::init(context.child("second"), cfg.clone())
2090 .await
2091 .unwrap();
2092
2093 let stream = journal
2094 .replay(1, start_offset, NZUsize!(1024))
2095 .await
2096 .unwrap();
2097 pin_mut!(stream);
2098
2099 while let Some(_result) = stream.next().await {}
2101 }
2102
2103 let (_, physical_size_after) = context
2105 .open(&cfg.partition, &1u64.to_be_bytes())
2106 .await
2107 .unwrap();
2108
2109 assert!(
2112 physical_size_after >= physical_size_before,
2113 "Valid data was lost! Physical blob truncated from {physical_size_before} to \
2114 {physical_size_after}. Logical valid size was {valid_logical_size}. \
2115 This indicates valid_offset was incorrectly initialized to 0 instead of start_offset."
2116 );
2117 });
2118 }
2119
2120 #[test_traced]
2121 fn test_journal_replay_rejects_start_offset_past_section() {
2122 let executor = deterministic::Runner::default();
2123 executor.start(|context| async move {
2124 let cfg = Config {
2125 partition: "test-partition".into(),
2126 compression: None,
2127 codec_config: (),
2128 page_cache: CacheRef::from_pooler(&context, PAGE_SIZE, PAGE_CACHE_SIZE),
2129 write_buffer: NZUsize!(1024),
2130 };
2131 let mut journal = Journal::init(context.child("storage"), cfg).await.unwrap();
2132 journal.append(1, &7i32).await.unwrap();
2133
2134 let result = journal.replay(1, u64::MAX, NZUsize!(1024)).await;
2135 assert!(matches!(result, Err(Error::ItemOutOfRange(u64::MAX))));
2136 drop(result);
2137
2138 journal.destroy().await.unwrap();
2139 });
2140 }
2141
2142 #[test_traced]
2143 fn test_journal_large_item_spanning_pages() {
2144 const LARGE_SIZE: usize = 2048;
2146 type LargeItem = [u8; LARGE_SIZE];
2147
2148 let executor = deterministic::Runner::default();
2149 executor.start(|context| async move {
2150 let cfg = Config {
2151 partition: "test-partition".into(),
2152 compression: None,
2153 codec_config: (),
2154 page_cache: CacheRef::from_pooler(&context, PAGE_SIZE, PAGE_CACHE_SIZE),
2155 write_buffer: NZUsize!(4096),
2156 };
2157 let mut journal = Journal::init(context.child("first"), cfg.clone())
2158 .await
2159 .expect("Failed to initialize journal");
2160
2161 let mut large_data: LargeItem = [0u8; LARGE_SIZE];
2163 for (i, byte) in large_data.iter_mut().enumerate() {
2164 *byte = (i % 256) as u8;
2165 }
2166 assert!(
2167 LARGE_SIZE > PAGE_SIZE.get() as usize,
2168 "Item must be larger than page size"
2169 );
2170
2171 let (offset, size) = journal
2173 .append(1, &large_data)
2174 .await
2175 .expect("Failed to append large item");
2176 assert_eq!(size as usize, LARGE_SIZE);
2177 journal.sync(1).await.expect("Failed to sync");
2178
2179 let retrieved: LargeItem = journal
2181 .get(1, offset)
2182 .await
2183 .expect("Failed to get large item");
2184 assert_eq!(retrieved, large_data, "Random access read mismatch");
2185
2186 drop(journal);
2188 let mut journal = Journal::<_, LargeItem>::init(context.child("second"), cfg.clone())
2189 .await
2190 .expect("Failed to re-initialize journal");
2191
2192 {
2194 let stream = journal
2195 .replay(0, 0, NZUsize!(1024))
2196 .await
2197 .expect("Failed to setup replay");
2198 pin_mut!(stream);
2199
2200 let mut items = Vec::new();
2201 while let Some(result) = stream.next().await {
2202 let (section, off, sz, item) = result.expect("Failed to replay item");
2203 items.push((section, off, sz, item));
2204 }
2205
2206 assert_eq!(items.len(), 1, "Should have exactly one item");
2207 let (section, off, sz, item) = &items[0];
2208 assert_eq!(*section, 1);
2209 assert_eq!(*off, offset);
2210 assert_eq!(*sz as usize, LARGE_SIZE);
2211 assert_eq!(*item, large_data, "Replay read mismatch");
2212 }
2213
2214 journal.destroy().await.unwrap();
2215 });
2216 }
2217
2218 #[test_traced]
2219 fn test_journal_large_item_direct_path() {
2220 const LARGE_SIZE: usize = 2048;
2225 type LargeItem = [u8; LARGE_SIZE];
2226
2227 let executor = deterministic::Runner::default();
2228 executor.start(|context| async move {
2229 let cfg = Config {
2230 partition: "test-partition".into(),
2231 compression: None,
2232 codec_config: (),
2233 page_cache: CacheRef::from_pooler(&context, PAGE_SIZE, PAGE_CACHE_SIZE),
2234 write_buffer: NZUsize!(1024),
2235 };
2236 let mut journal = Journal::init(context.child("first"), cfg.clone())
2237 .await
2238 .expect("Failed to initialize journal");
2239
2240 let mut first: LargeItem = [0u8; LARGE_SIZE];
2241 for (i, byte) in first.iter_mut().enumerate() {
2242 *byte = (i % 256) as u8;
2243 }
2244 let mut second: LargeItem = [0u8; LARGE_SIZE];
2245 for (i, byte) in second.iter_mut().enumerate() {
2246 *byte = ((i + 7) % 251) as u8;
2247 }
2248
2249 let (first_offset, _) = journal
2250 .append(1, &first)
2251 .await
2252 .expect("Failed to append first item");
2253 let (second_offset, _) = journal
2254 .append(1, &second)
2255 .await
2256 .expect("Failed to append second item");
2257
2258 let retrieved: LargeItem = journal.get(1, first_offset).await.unwrap();
2260 assert_eq!(retrieved, first);
2261 let retrieved: LargeItem = journal.get(1, second_offset).await.unwrap();
2262 assert_eq!(retrieved, second);
2263
2264 journal.sync(1).await.expect("Failed to sync");
2266 drop(journal);
2267 let mut journal = Journal::<_, LargeItem>::init(context.child("second"), cfg.clone())
2268 .await
2269 .expect("Failed to re-initialize journal");
2270
2271 let retrieved: LargeItem = journal.get(1, first_offset).await.unwrap();
2272 assert_eq!(retrieved, first);
2273 let retrieved: LargeItem = journal.get(1, second_offset).await.unwrap();
2274 assert_eq!(retrieved, second);
2275
2276 {
2277 let stream = journal
2278 .replay(0, 0, NZUsize!(1024))
2279 .await
2280 .expect("Failed to setup replay");
2281 pin_mut!(stream);
2282
2283 let mut items = Vec::new();
2284 while let Some(result) = stream.next().await {
2285 let (section, off, _, item) = result.expect("Failed to replay item");
2286 items.push((section, off, item));
2287 }
2288 assert_eq!(items.len(), 2);
2289 assert_eq!(items[0], (1, first_offset, first));
2290 assert_eq!(items[1], (1, second_offset, second));
2291 }
2292
2293 journal.destroy().await.unwrap();
2294 });
2295 }
2296
2297 #[test_traced]
2298 fn test_journal_non_contiguous_sections() {
2299 let executor = deterministic::Runner::default();
2302 executor.start(|context| async move {
2303 let cfg = Config {
2304 partition: "test-partition".into(),
2305 compression: None,
2306 codec_config: (),
2307 page_cache: CacheRef::from_pooler(&context, PAGE_SIZE, PAGE_CACHE_SIZE),
2308 write_buffer: NZUsize!(1024),
2309 };
2310 let mut journal = Journal::init(context.child("first"), cfg.clone())
2311 .await
2312 .expect("Failed to initialize journal");
2313
2314 let sections_and_data = [(1u64, 100i32), (5u64, 500i32), (10u64, 1000i32)];
2316 let mut offsets = Vec::new();
2317
2318 for (section, data) in §ions_and_data {
2319 let (offset, _) = journal
2320 .append(*section, data)
2321 .await
2322 .expect("Failed to append");
2323 offsets.push(offset);
2324 }
2325 journal.sync_all().await.expect("Failed to sync");
2326
2327 for (i, (section, expected_data)) in sections_and_data.iter().enumerate() {
2329 let retrieved: i32 = journal
2330 .get(*section, offsets[i])
2331 .await
2332 .expect("Failed to get item");
2333 assert_eq!(retrieved, *expected_data);
2334 }
2335
2336 for missing_section in [0u64, 2, 3, 4, 6, 7, 8, 9, 11] {
2338 let result = journal.get(missing_section, 0).await;
2339 assert!(
2340 matches!(result, Err(Error::SectionOutOfRange(_))),
2341 "Expected SectionOutOfRange for section {}, got {:?}",
2342 missing_section,
2343 result
2344 );
2345 }
2346
2347 drop(journal);
2349 let mut journal = Journal::<_, i32>::init(context.child("second"), cfg.clone())
2350 .await
2351 .expect("Failed to re-initialize journal");
2352
2353 {
2355 let stream = journal
2356 .replay(0, 0, NZUsize!(1024))
2357 .await
2358 .expect("Failed to setup replay");
2359 pin_mut!(stream);
2360
2361 let mut items = Vec::new();
2362 while let Some(result) = stream.next().await {
2363 let (section, _, _, item) = result.expect("Failed to replay item");
2364 items.push((section, item));
2365 }
2366
2367 assert_eq!(items.len(), 3, "Should have 3 items");
2368 assert_eq!(items[0], (1, 100));
2369 assert_eq!(items[1], (5, 500));
2370 assert_eq!(items[2], (10, 1000));
2371 }
2372
2373 {
2375 let stream = journal
2376 .replay(5, 0, NZUsize!(1024))
2377 .await
2378 .expect("Failed to setup replay from section 5");
2379 pin_mut!(stream);
2380
2381 let mut items = Vec::new();
2382 while let Some(result) = stream.next().await {
2383 let (section, _, _, item) = result.expect("Failed to replay item");
2384 items.push((section, item));
2385 }
2386
2387 assert_eq!(items.len(), 2, "Should have 2 items from section 5 onwards");
2388 assert_eq!(items[0], (5, 500));
2389 assert_eq!(items[1], (10, 1000));
2390 }
2391
2392 {
2394 let stream = journal
2395 .replay(3, 0, NZUsize!(1024))
2396 .await
2397 .expect("Failed to setup replay from section 3");
2398 pin_mut!(stream);
2399
2400 let mut items = Vec::new();
2401 while let Some(result) = stream.next().await {
2402 let (section, _, _, item) = result.expect("Failed to replay item");
2403 items.push((section, item));
2404 }
2405
2406 assert_eq!(items.len(), 2);
2408 assert_eq!(items[0], (5, 500));
2409 assert_eq!(items[1], (10, 1000));
2410 }
2411
2412 journal.destroy().await.unwrap();
2413 });
2414 }
2415
2416 #[test_traced]
2417 fn test_journal_empty_section_in_middle() {
2418 let executor = deterministic::Runner::default();
2421 executor.start(|context| async move {
2422 let cfg = Config {
2423 partition: "test-partition".into(),
2424 compression: None,
2425 codec_config: (),
2426 page_cache: CacheRef::from_pooler(&context, PAGE_SIZE, PAGE_CACHE_SIZE),
2427 write_buffer: NZUsize!(1024),
2428 };
2429 let mut journal = Journal::init(context.child("first"), cfg.clone())
2430 .await
2431 .expect("Failed to initialize journal");
2432
2433 journal.append(1, &100i32).await.expect("Failed to append");
2435
2436 journal.append(2, &200i32).await.expect("Failed to append");
2439 journal.sync(2).await.expect("Failed to sync");
2440 journal
2441 .rewind_section(2, 0)
2442 .await
2443 .expect("Failed to rewind");
2444
2445 journal.append(3, &300i32).await.expect("Failed to append");
2447
2448 journal.sync_all().await.expect("Failed to sync");
2449
2450 assert!(journal.size(1).unwrap() > 0);
2452 assert_eq!(journal.size(2).unwrap(), 0);
2453 assert!(journal.size(3).unwrap() > 0);
2454
2455 drop(journal);
2457 let mut journal = Journal::<_, i32>::init(context.child("second"), cfg.clone())
2458 .await
2459 .expect("Failed to re-initialize journal");
2460
2461 {
2463 let stream = journal
2464 .replay(0, 0, NZUsize!(1024))
2465 .await
2466 .expect("Failed to setup replay");
2467 pin_mut!(stream);
2468
2469 let mut items = Vec::new();
2470 while let Some(result) = stream.next().await {
2471 let (section, _, _, item) = result.expect("Failed to replay item");
2472 items.push((section, item));
2473 }
2474
2475 assert_eq!(
2476 items.len(),
2477 2,
2478 "Should have 2 items (skipping empty section)"
2479 );
2480 assert_eq!(items[0], (1, 100));
2481 assert_eq!(items[1], (3, 300));
2482 }
2483
2484 {
2486 let stream = journal
2487 .replay(2, 0, NZUsize!(1024))
2488 .await
2489 .expect("Failed to setup replay from section 2");
2490 pin_mut!(stream);
2491
2492 let mut items = Vec::new();
2493 while let Some(result) = stream.next().await {
2494 let (section, _, _, item) = result.expect("Failed to replay item");
2495 items.push((section, item));
2496 }
2497
2498 assert_eq!(items.len(), 1, "Should have 1 item from section 3");
2499 assert_eq!(items[0], (3, 300));
2500 }
2501
2502 journal.destroy().await.unwrap();
2503 });
2504 }
2505
2506 #[test_traced]
2507 fn test_journal_item_exactly_page_size() {
2508 const ITEM_SIZE: usize = PAGE_SIZE.get() as usize;
2511 type ExactItem = [u8; ITEM_SIZE];
2512
2513 let executor = deterministic::Runner::default();
2514 executor.start(|context| async move {
2515 let cfg = Config {
2516 partition: "test-partition".into(),
2517 compression: None,
2518 codec_config: (),
2519 page_cache: CacheRef::from_pooler(&context, PAGE_SIZE, PAGE_CACHE_SIZE),
2520 write_buffer: NZUsize!(4096),
2521 };
2522 let mut journal = Journal::init(context.child("first"), cfg.clone())
2523 .await
2524 .expect("Failed to initialize journal");
2525
2526 let mut exact_data: ExactItem = [0u8; ITEM_SIZE];
2528 for (i, byte) in exact_data.iter_mut().enumerate() {
2529 *byte = (i % 256) as u8;
2530 }
2531
2532 let (offset, size) = journal
2534 .append(1, &exact_data)
2535 .await
2536 .expect("Failed to append exact item");
2537 assert_eq!(size as usize, ITEM_SIZE);
2538 journal.sync(1).await.expect("Failed to sync");
2539
2540 let retrieved: ExactItem = journal
2542 .get(1, offset)
2543 .await
2544 .expect("Failed to get exact item");
2545 assert_eq!(retrieved, exact_data, "Random access read mismatch");
2546
2547 drop(journal);
2549 let mut journal = Journal::<_, ExactItem>::init(context.child("second"), cfg.clone())
2550 .await
2551 .expect("Failed to re-initialize journal");
2552
2553 {
2555 let stream = journal
2556 .replay(0, 0, NZUsize!(1024))
2557 .await
2558 .expect("Failed to setup replay");
2559 pin_mut!(stream);
2560
2561 let mut items = Vec::new();
2562 while let Some(result) = stream.next().await {
2563 let (section, off, sz, item) = result.expect("Failed to replay item");
2564 items.push((section, off, sz, item));
2565 }
2566
2567 assert_eq!(items.len(), 1, "Should have exactly one item");
2568 let (section, off, sz, item) = &items[0];
2569 assert_eq!(*section, 1);
2570 assert_eq!(*off, offset);
2571 assert_eq!(*sz as usize, ITEM_SIZE);
2572 assert_eq!(*item, exact_data, "Replay read mismatch");
2573 }
2574
2575 journal.destroy().await.unwrap();
2576 });
2577 }
2578
2579 #[test_traced]
2580 fn test_journal_varint_spanning_page_boundary() {
2581 const SMALL_PAGE: NonZeroU16 = NZU16!(16);
2589
2590 let executor = deterministic::Runner::default();
2591 executor.start(|context| async move {
2592 let cfg = Config {
2593 partition: "test-partition".into(),
2594 compression: None,
2595 codec_config: (),
2596 page_cache: CacheRef::from_pooler(&context, SMALL_PAGE, PAGE_CACHE_SIZE),
2597 write_buffer: NZUsize!(1024),
2598 };
2599 let mut journal: Journal<_, [u8; 128]> =
2600 Journal::init(context.child("first"), cfg.clone())
2601 .await
2602 .expect("Failed to initialize journal");
2603
2604 let item1: [u8; 128] = [1u8; 128];
2606 let item2: [u8; 128] = [2u8; 128];
2607 let item3: [u8; 128] = [3u8; 128];
2608
2609 let (offset1, _) = journal.append(1, &item1).await.expect("Failed to append");
2612 let (offset2, _) = journal.append(1, &item2).await.expect("Failed to append");
2613 let (offset3, _) = journal.append(1, &item3).await.expect("Failed to append");
2614
2615 journal.sync(1).await.expect("Failed to sync");
2616
2617 let retrieved1: [u8; 128] = journal.get(1, offset1).await.expect("Failed to get");
2619 let retrieved2: [u8; 128] = journal.get(1, offset2).await.expect("Failed to get");
2620 let retrieved3: [u8; 128] = journal.get(1, offset3).await.expect("Failed to get");
2621 assert_eq!(retrieved1, item1);
2622 assert_eq!(retrieved2, item2);
2623 assert_eq!(retrieved3, item3);
2624
2625 drop(journal);
2627 let mut journal: Journal<_, [u8; 128]> =
2628 Journal::init(context.child("second"), cfg.clone())
2629 .await
2630 .expect("Failed to re-initialize journal");
2631
2632 {
2634 let stream = journal
2635 .replay(0, 0, NZUsize!(64))
2636 .await
2637 .expect("Failed to setup replay");
2638 pin_mut!(stream);
2639
2640 let mut items = Vec::new();
2641 while let Some(result) = stream.next().await {
2642 let (section, off, _, item) = result.expect("Failed to replay item");
2643 items.push((section, off, item));
2644 }
2645
2646 assert_eq!(items.len(), 3, "Should have 3 items");
2647 assert_eq!(items[0], (1, offset1, item1));
2648 assert_eq!(items[1], (1, offset2, item2));
2649 assert_eq!(items[2], (1, offset3, item3));
2650 }
2651
2652 journal.destroy().await.unwrap();
2653 });
2654 }
2655
2656 #[test_traced]
2657 fn test_journal_clear() {
2658 let executor = deterministic::Runner::default();
2659 executor.start(|context| async move {
2660 let cfg = Config {
2661 partition: "clear-test".into(),
2662 compression: None,
2663 codec_config: (),
2664 page_cache: CacheRef::from_pooler(&context, PAGE_SIZE, PAGE_CACHE_SIZE),
2665 write_buffer: NZUsize!(1024),
2666 };
2667
2668 let mut journal: Journal<_, u64> = Journal::init(context.child("journal"), cfg.clone())
2669 .await
2670 .expect("Failed to initialize journal");
2671
2672 for section in 0..5u64 {
2674 for i in 0..10u64 {
2675 journal
2676 .append(section, &(section * 1000 + i))
2677 .await
2678 .expect("Failed to append");
2679 }
2680 journal.sync(section).await.expect("Failed to sync");
2681 }
2682
2683 assert_eq!(journal.get(0, 0).await.unwrap(), 0);
2685 assert_eq!(journal.get(4, 0).await.unwrap(), 4000);
2686
2687 journal.clear().await.expect("Failed to clear");
2689
2690 for section in 0..5u64 {
2692 assert!(matches!(
2693 journal.get(section, 0).await,
2694 Err(Error::SectionOutOfRange(s)) if s == section
2695 ));
2696 }
2697
2698 for i in 0..5u64 {
2700 journal
2701 .append(10, &(i * 100))
2702 .await
2703 .expect("Failed to append after clear");
2704 }
2705 journal.sync(10).await.expect("Failed to sync after clear");
2706
2707 assert_eq!(journal.get(10, 0).await.unwrap(), 0);
2709
2710 assert!(matches!(
2712 journal.get(0, 0).await,
2713 Err(Error::SectionOutOfRange(0))
2714 ));
2715
2716 journal.destroy().await.unwrap();
2717 });
2718 }
2719}