1use super::manager::{Config as ManagerConfig, Manager, WriteFactory};
30use crate::{
31 Context,
32 journal::{Error, frame},
33};
34use bytes::Bytes;
35use commonware_codec::{Codec, CodecShared, FixedSize};
36use commonware_cryptography::{Crc32, crc32};
37#[cfg(any(test, feature = "test-utils"))]
38use commonware_runtime::{Blob as _, ReadOptions, Storage, WriteOptions};
39use commonware_runtime::{BufMut, Error as RError, Handle};
40use std::{collections::BTreeMap, num::NonZeroUsize};
41use zstd::zstd_safe::compress_bound;
42
43pub(crate) const CHECKSUM_SIZE: usize = crc32::Digest::SIZE;
45
46#[derive(Clone)]
48pub struct Config<C> {
49 pub partition: String,
51
52 pub compression: Option<u8>,
57
58 pub codec_config: C,
60
61 pub write_buffer: NonZeroUsize,
63}
64
65struct Inner<E: Context, V: Codec> {
67 manager: Manager<E, WriteFactory>,
68
69 compression: Option<u8>,
71
72 codec_config: V::Cfg,
74}
75
76impl<E: Context, V: CodecShared> Inner<E, V> {
77 async fn init(context: E, cfg: Config<V::Cfg>, ceiling: u64) -> Result<Self, Error> {
79 let manager_cfg = ManagerConfig {
80 partition: cfg.partition,
81 factory: WriteFactory {
82 capacity: cfg.write_buffer,
83 },
84 };
85 let manager = Manager::init_bounded(context, manager_cfg, ceiling).await?;
86
87 Ok(Self {
88 manager,
89 compression: cfg.compression,
90 codec_config: cfg.codec_config,
91 })
92 }
93
94 async fn append(&mut self, section: u64, value: &V) -> Result<(u64, u32), Error> {
96 let buf = if let Some(level) = self.compression {
98 let encoded = value.encode();
100 let mut compressed = Vec::with_capacity(compress_bound(encoded.len()) + CHECKSUM_SIZE);
101 frame::compress_into(level, &encoded, &mut compressed)?;
102 let checksum = Crc32::checksum(&compressed);
103 compressed.put_u32(checksum);
104 compressed
105 } else {
106 let entry_size = value.encode_size() + CHECKSUM_SIZE;
108 let mut buf = Vec::with_capacity(entry_size);
109 value.write(&mut buf);
110 let checksum = Crc32::checksum(&buf);
111 buf.put_u32(checksum);
112 buf
113 };
114
115 let entry_size = u32::try_from(buf.len()).map_err(|_| Error::ValueTooLarge)?;
117 let writer = self.manager.get_or_create(section).await?;
118 let offset = writer.size();
119 if !writer.try_write_at(offset, &buf) {
120 let writer = self.manager.take(section).await?;
122 let writer = writer.write_at(offset, buf).await.map_err(Error::Runtime)?;
123 self.manager.put(section, writer);
124 }
125
126 Ok((offset, entry_size))
127 }
128
129 async fn get(&self, section: u64, offset: u64, size: u32) -> Result<V, Error> {
131 let writer = self
132 .manager
133 .get(section)?
134 .ok_or(Error::SectionOutOfRange(section))?;
135
136 let buf = writer.read_at(offset, size as usize).await?.coalesce();
138
139 if buf.len() < CHECKSUM_SIZE {
141 return Err(Error::Runtime(RError::BlobInsufficientLength));
142 }
143
144 let data_len = buf.len() - CHECKSUM_SIZE;
145 let compressed_data = &buf.as_ref()[..data_len];
146 let stored_checksum = u32::from_be_bytes(
147 buf.as_ref()[data_len..]
148 .try_into()
149 .expect("checksum is 4 bytes"),
150 );
151
152 let checksum = Crc32::checksum(compressed_data);
154 if checksum != stored_checksum {
155 return Err(Error::ChecksumMismatch(stored_checksum, checksum));
156 }
157
158 let value = if self.compression.is_some() {
160 let decompressed = frame::decompress(compressed_data)?;
161 V::decode_cfg(decompressed, &self.codec_config).map_err(Error::Codec)?
162 } else {
163 V::decode_cfg(Bytes::from(buf.slice(..data_len)), &self.codec_config)
165 .map_err(Error::Codec)?
166 };
167
168 Ok(value)
169 }
170
171 async fn verify(&self, section: u64, offset: u64, size: u32) -> Result<bool, Error> {
173 if (size as usize) < CHECKSUM_SIZE {
175 return Ok(false);
176 }
177 let Some(writer) = self.manager.get(section)? else {
178 return Ok(false);
179 };
180
181 let buf = match writer.read_at(offset, size as usize).await {
182 Ok(buf) => buf.coalesce(),
183 Err(RError::BlobInsufficientLength | RError::OffsetOverflow) => return Ok(false),
184 Err(err) => return Err(Error::Runtime(err)),
185 };
186 let data_len = buf.len() - CHECKSUM_SIZE;
187 let stored_checksum = u32::from_be_bytes(
188 buf.as_ref()[data_len..]
189 .try_into()
190 .expect("checksum is 4 bytes"),
191 );
192 Ok(Crc32::checksum(&buf.as_ref()[..data_len]) == stored_checksum)
193 }
194
195 #[cfg(test)]
197 async fn inject(&mut self, section: u64, offset: u64, buf: Vec<u8>) -> Result<(), Error> {
198 let writer = self.manager.take(section).await?;
199 let writer = writer.write_at(offset, buf).await.map_err(Error::Runtime)?;
200 self.manager.put(section, writer);
201 Ok(())
202 }
203
204 async fn sync(mut self: Box<Self>, sections: impl crate::Sections) -> Result<Box<Self>, Error> {
206 self.manager = self.manager.sync(sections).await?;
207 Ok(self)
208 }
209
210 async fn start_sync(
212 mut self: Box<Self>,
213 sections: impl crate::Sections,
214 ) -> Result<(Box<Self>, Handle<()>), Error> {
215 let (manager, handle) = self.manager.start_sync(sections).await?;
216 self.manager = manager;
217 Ok((self, handle))
218 }
219
220 async fn sync_all(mut self: Box<Self>) -> Result<Box<Self>, Error> {
222 self.manager = self.manager.sync_all().await?;
223 Ok(self)
224 }
225
226 fn size(&self, section: u64) -> Result<u64, Error> {
228 self.manager.size(section)
229 }
230
231 async fn truncate_pending(
233 mut self: Box<Self>,
234 section: u64,
235 size: u64,
236 ) -> Result<Box<Self>, Error> {
237 self.manager = self.manager.truncate_pending(section, size).await?;
238 Ok(self)
239 }
240
241 async fn prune(mut self: Box<Self>, min: u64) -> Result<(Box<Self>, bool), Error> {
243 let (manager, pruned) = self.manager.prune(min).await?;
244 self.manager = manager;
245 Ok((self, pruned))
246 }
247
248 const fn pruned(&self, section: u64) -> bool {
250 self.manager.pruned(section)
251 }
252
253 fn oldest_section(&self) -> Option<u64> {
255 self.manager.oldest_section()
256 }
257
258 fn newest_section(&self) -> Option<u64> {
260 self.manager.newest_section()
261 }
262
263 fn sections(&self) -> impl Iterator<Item = u64> + '_ {
265 self.manager.sections()
266 }
267
268 async fn remove_section(mut self: Box<Self>, section: u64) -> Result<(Box<Self>, bool), Error> {
270 let (manager, removed) = self.manager.remove_section(section).await?;
271 self.manager = manager;
272 Ok((self, removed))
273 }
274
275 async fn destroy(self) -> Result<(), Error> {
277 self.manager.destroy().await
278 }
279}
280
281pub struct Glob<E: Context, V: Codec>(Box<Inner<E, V>>);
292
293impl<E: Context, V: CodecShared> std::fmt::Debug for Glob<E, V> {
294 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
295 f.debug_struct("Glob")
296 .field("oldest_section", &self.oldest_section())
297 .field("newest_section", &self.newest_section())
298 .finish_non_exhaustive()
299 }
300}
301
302impl<E: Context, V: CodecShared> Glob<E, V> {
303 pub async fn init(context: E, cfg: Config<V::Cfg>) -> Result<Self, Error> {
305 Ok(Recovery::init(context, cfg, u64::MAX).await?.into())
306 }
307
308 pub async fn append(mut self, section: u64, value: &V) -> Result<(Self, u64, u32), Error> {
314 let (offset, size) = self.0.append(section, value).await?;
315 Ok((self, offset, size))
316 }
317
318 pub async fn get(&self, section: u64, offset: u64, size: u32) -> Result<V, Error> {
323 self.0.get(section, offset, size).await
324 }
325
326 #[cfg(test)]
328 pub(super) async fn inject(
329 &mut self,
330 section: u64,
331 offset: u64,
332 buf: Vec<u8>,
333 ) -> Result<(), Error> {
334 self.0.inject(section, offset, buf).await
335 }
336
337 pub async fn sync(mut self, sections: impl crate::Sections) -> Result<Self, Error> {
339 self.0 = self.0.sync(sections).await?;
340 Ok(self)
341 }
342
343 pub async fn start_sync(
348 mut self,
349 sections: impl crate::Sections,
350 ) -> Result<(Self, Handle<()>), Error> {
351 let (inner, handle) = self.0.start_sync(sections).await?;
352 self.0 = inner;
353 Ok((self, handle))
354 }
355
356 pub async fn sync_all(mut self) -> Result<Self, Error> {
358 self.0 = self.0.sync_all().await?;
359 Ok(self)
360 }
361
362 pub fn size(&self, section: u64) -> Result<u64, Error> {
364 self.0.size(section)
365 }
366
367 pub async fn prune(mut self, min: u64) -> Result<(Self, bool), Error> {
369 let (inner, pruned) = self.0.prune(min).await?;
370 self.0 = inner;
371 Ok((self, pruned))
372 }
373
374 pub fn pruned(&self, section: u64) -> bool {
379 self.0.pruned(section)
380 }
381
382 pub fn oldest_section(&self) -> Option<u64> {
384 self.0.oldest_section()
385 }
386
387 pub fn newest_section(&self) -> Option<u64> {
389 self.0.newest_section()
390 }
391
392 pub fn sections(&self) -> impl Iterator<Item = u64> + '_ {
394 self.0.sections()
395 }
396
397 pub async fn remove_section(mut self, section: u64) -> Result<(Self, bool), Error> {
399 let (inner, removed) = self.0.remove_section(section).await?;
400 self.0 = inner;
401 Ok((self, removed))
402 }
403
404 pub async fn destroy(self) -> Result<(), Error> {
406 self.0.destroy().await
407 }
408}
409
410pub(crate) struct Recovery<E: Context, V: Codec>(Box<Inner<E, V>>);
412
413impl<E: Context, V: CodecShared> From<Recovery<E, V>> for Glob<E, V> {
414 fn from(recovery: Recovery<E, V>) -> Self {
416 Self(recovery.0)
417 }
418}
419
420impl<E: Context, V: CodecShared> Recovery<E, V> {
421 pub(crate) async fn init(context: E, cfg: Config<V::Cfg>, ceiling: u64) -> Result<Self, Error> {
424 Ok(Self(Box::new(Inner::init(context, cfg, ceiling).await?)))
425 }
426
427 pub(crate) async fn verify(&self, section: u64, offset: u64, size: u32) -> Result<bool, Error> {
433 self.0.verify(section, offset, size).await
434 }
435
436 pub(crate) async fn truncate(mut self, section: u64, size: u64) -> Result<Self, Error> {
441 self.0 = self.0.truncate_pending(section, size).await?;
442 Ok(self)
443 }
444
445 pub(crate) async fn truncate_section(mut self, section: u64, size: u64) -> Result<Self, Error> {
449 self.0.manager = self
450 .0
451 .manager
452 .truncate_pending_section(section, size)
453 .await?;
454 Ok(self)
455 }
456
457 pub(crate) async fn truncate_sections(
459 mut self,
460 sizes: &BTreeMap<u64, u64>,
461 ) -> Result<Self, Error> {
462 self.0.manager = self.0.manager.truncate_pending_sections(sizes).await?;
463 Ok(self)
464 }
465
466 pub(crate) fn size(&self, section: u64) -> Result<u64, Error> {
468 self.0.size(section)
469 }
470
471 pub(crate) fn sections(&self) -> impl Iterator<Item = u64> + '_ {
473 self.0.sections()
474 }
475
476 pub(crate) async fn sync(mut self, sections: impl crate::Sections) -> Result<Self, Error> {
478 self.0 = self.0.sync(sections).await?;
479 Ok(self)
480 }
481
482 pub(crate) async fn remove_section(mut self, section: u64) -> Result<Self, Error> {
484 let (inner, _) = self.0.remove_section(section).await?;
485 self.0 = inner;
486 Ok(self)
487 }
488}
489
490#[cfg(any(test, feature = "test-utils"))]
495pub async fn corrupt_frame(
496 storage: &impl Storage,
497 partition: &str,
498 name: &[u8],
499 frame: u64,
500 frame_size: u64,
501) {
502 let offset = frame * frame_size;
503 let (blob, size) = storage.open(partition, name).await.unwrap();
504 assert!(offset < size, "corruption target must be inside the blob");
505 let byte = blob
506 .read_at(offset, 1, ReadOptions::default())
507 .await
508 .unwrap()
509 .coalesce();
510 blob.write_at(offset, vec![byte.as_ref()[0] ^ 0xFF], WriteOptions::SYNC)
511 .await
512 .unwrap();
513}
514
515#[cfg(test)]
516mod tests {
517 use super::*;
518 use commonware_codec::Encode as _;
519 use commonware_macros::test_traced;
520 use commonware_runtime::{Runner, Supervisor as _, deterministic};
521 use commonware_utils::{NZUsize, probability};
522 use rand::Rng as _;
523
524 impl<E: crate::Context, V: CodecShared> Glob<E, V> {
525 pub(in super::super) fn test_configuration(&self) -> (E, Config<V::Cfg>) {
526 let (context, partition, factory) = self.0.manager.test_configuration();
527 (
528 context,
529 Config {
530 partition,
531 write_buffer: factory.capacity,
532 compression: self.0.compression,
533 codec_config: self.0.codec_config.clone(),
534 },
535 )
536 }
537
538 async fn test_reopen_section(self, section: u64, end: u64) -> Result<Self, Error> {
539 let (context, partition, factory) = self.0.manager.test_configuration();
540 let cfg = Config {
541 partition,
542 write_buffer: factory.capacity,
543 compression: self.0.compression,
544 codec_config: self.0.codec_config.clone(),
545 };
546 _ = self.sync_all().await?;
547 let pending = Recovery::init(context, cfg, u64::MAX).await?;
548 let pending = pending.truncate_section(section, end).await?;
549 Ok(pending.into())
550 }
551 }
552
553 fn test_cfg() -> Config<()> {
554 Config {
555 partition: "test-partition".into(),
556 compression: None,
557 codec_config: (),
558 write_buffer: NZUsize!(1024),
559 }
560 }
561
562 #[test_traced]
563 fn test_glob_append_and_get() {
564 let executor = deterministic::Runner::default();
565 executor.start(|context| async move {
566 let glob: Glob<_, i32> = Glob::init(context.child("storage"), test_cfg())
567 .await
568 .expect("Failed to init glob");
569
570 let value: i32 = 42;
572 let (glob, offset, size) = glob.append(1, &value).await.expect("Failed to append");
573 assert_eq!(offset, 0);
574
575 let retrieved = glob.get(1, offset, size).await.expect("Failed to get");
577 assert_eq!(retrieved, value);
578
579 let glob = glob.sync(1).await.expect("Failed to sync");
581 let retrieved = glob.get(1, offset, size).await.expect("Failed to get");
582 assert_eq!(retrieved, value);
583
584 glob.destroy().await.expect("Failed to destroy");
585 });
586 }
587
588 #[test_traced]
589 fn test_glob_get_view() {
590 let executor = deterministic::Runner::default();
591 executor.start(|context| async move {
592 let cfg = Config {
593 partition: "test-partition".into(),
594 compression: None,
595 codec_config: (..).into(),
596 write_buffer: NZUsize!(1024),
597 };
598 let glob: Glob<_, Bytes> = Glob::init(context.child("storage"), cfg)
599 .await
600 .expect("Failed to init glob");
601
602 let value = Bytes::from(vec![7u8; 32]);
604 let (glob, offset, size) = glob.append(1, &value).await.expect("Failed to append");
605
606 let a = glob.get(1, offset, size).await.expect("Failed to get");
608 let b = glob.get(1, offset, size).await.expect("Failed to get");
609 assert_eq!(a, value);
610 assert_eq!(b, value);
611 assert_eq!(a.as_ptr(), b.as_ptr());
612
613 glob.destroy().await.expect("Failed to destroy");
614 });
615 }
616
617 #[test_traced]
618 fn test_glob_multiple_values() {
619 let executor = deterministic::Runner::default();
620 executor.start(|context| async move {
621 let mut glob: Glob<_, i32> = Glob::init(context.child("storage"), test_cfg())
622 .await
623 .expect("Failed to init glob");
624
625 let values: Vec<i32> = vec![1, 2, 3, 4, 5];
627 let mut locations = Vec::new();
628
629 for value in &values {
630 let offset;
631 let size;
632 (glob, offset, size) = glob.append(1, value).await.expect("Failed to append");
633 locations.push((offset, size));
634 }
635
636 for (i, (offset, size)) in locations.iter().enumerate() {
638 let retrieved = glob.get(1, *offset, *size).await.expect("Failed to get");
639 assert_eq!(retrieved, values[i]);
640 }
641
642 glob.destroy().await.expect("Failed to destroy");
643 });
644 }
645
646 #[test_traced]
647 fn test_glob_with_compression() {
648 let executor = deterministic::Runner::default();
649 executor.start(|context| async move {
650 let cfg = Config {
651 partition: "test-partition".into(),
652 compression: Some(3), codec_config: (),
654 write_buffer: NZUsize!(1024),
655 };
656 let glob: Glob<_, [u8; 100]> = Glob::init(context.child("storage"), cfg)
657 .await
658 .expect("Failed to init glob");
659
660 let value: [u8; 100] = [0u8; 100]; let (glob, offset, size) = glob.append(1, &value).await.expect("Failed to append");
663
664 assert!(size < 100 + 4);
666
667 let retrieved = glob.get(1, offset, size).await.expect("Failed to get");
669 assert_eq!(retrieved, value);
670
671 glob.destroy().await.expect("Failed to destroy");
672 });
673 }
674
675 #[test_traced]
676 fn test_glob_compressed_entries_match_reference_format() {
677 let executor = deterministic::Runner::default();
678 executor.start(|mut context| async move {
679 let cfg = Config {
680 partition: "test-partition".into(),
681 compression: Some(19),
682 codec_config: ((..).into(), ()),
683 write_buffer: NZUsize!(1024),
684 };
685 let mut glob: Glob<_, Vec<u8>> = Glob::init(context.child("first"), cfg.clone())
686 .await
687 .expect("Failed to init glob");
688
689 let mut values: Vec<Vec<u8>> = [0usize, 1, 127, 4096, 70_000]
692 .into_iter()
693 .map(|len| (0..len).map(|i| (i % 7) as u8).collect())
694 .collect();
695 let mut random = vec![0; 4096];
696 context.fill_bytes(&mut random);
697 values.push(random);
698
699 let mut entries = Vec::new();
700 for value in values {
701 let offset;
702 let size;
703 (glob, offset, size) = glob.append(1, &value).await.expect("Failed to append");
704
705 let mut expected = zstd::bulk::compress(&value.encode(), 19).unwrap();
706 let checksum = Crc32::checksum(&expected);
707 expected.put_u32(checksum);
708 let writer = glob.0.manager.get(1).unwrap().unwrap();
709 let stored = writer.read_at(offset, size as usize).await.unwrap();
710 assert_eq!(stored.coalesce().as_ref(), expected.as_slice());
711 assert_eq!(glob.get(1, offset, size).await.unwrap(), value);
712 entries.push((offset, size, expected, value));
713 }
714 assert!(entries.last().unwrap().1 > 1024);
715 let glob = glob.sync(1).await.expect("Failed to sync");
716 drop(glob);
717
718 let glob: Glob<_, Vec<u8>> = Glob::init(context.child("second"), cfg)
720 .await
721 .expect("Failed to reinit glob");
722 let writer = glob.0.manager.get(1).unwrap().unwrap();
723 for (offset, size, expected, value) in &entries {
724 let stored = writer.read_at(*offset, *size as usize).await.unwrap();
725 assert_eq!(stored.coalesce().as_ref(), expected.as_slice());
726 assert_eq!(glob.get(1, *offset, *size).await.unwrap(), *value);
727 }
728
729 glob.destroy().await.expect("Failed to destroy");
730 });
731 }
732
733 #[test_traced]
734 fn test_glob_prune() {
735 let executor = deterministic::Runner::default();
736 executor.start(|context| async move {
737 let mut glob: Glob<_, i32> = Glob::init(context.child("storage"), test_cfg())
738 .await
739 .expect("Failed to init glob");
740
741 for section in 1..=5 {
743 (glob, _, _) = glob
744 .append(section, &(section as i32))
745 .await
746 .expect("Failed to append");
747 glob = glob.sync(section).await.expect("Failed to sync");
748 }
749
750 let (glob, _) = glob.prune(3).await.expect("Failed to prune");
752
753 assert!(glob.pruned(1));
755 assert!(glob.pruned(2));
756 assert!(!glob.pruned(3));
757
758 assert!(glob.get(1, 0, 8).await.is_err());
760 assert!(glob.get(2, 0, 8).await.is_err());
761
762 assert!(glob.0.manager.blobs.contains_key(&3));
764 assert!(glob.0.manager.blobs.contains_key(&4));
765 assert!(glob.0.manager.blobs.contains_key(&5));
766
767 glob.destroy().await.expect("Failed to destroy");
768 });
769 }
770
771 #[test_traced]
772 fn test_glob_checksum_mismatch() {
773 let executor = deterministic::Runner::default();
774 executor.start(|context| async move {
775 let glob: Glob<_, i32> = Glob::init(context.child("storage"), test_cfg())
776 .await
777 .expect("Failed to init glob");
778
779 let value: i32 = 42;
781 let (glob, offset, size) = glob.append(1, &value).await.expect("Failed to append");
782 let mut glob = glob.sync(1).await.expect("Failed to sync");
783
784 let writer = glob.0.manager.take(1).await.unwrap();
786 let writer = writer
787 .write_at(offset, vec![0xFF, 0xFF, 0xFF, 0xFF])
788 .await
789 .expect("Failed to corrupt");
790 let writer = writer.sync().await.expect("Failed to sync");
791 glob.0.manager.put(1, writer);
792
793 let result = glob.get(1, offset, size).await;
795 assert!(matches!(result, Err(Error::ChecksumMismatch(_, _))));
796
797 glob.destroy().await.expect("Failed to destroy");
798 });
799 }
800
801 #[test_traced]
802 fn test_glob_truncate() {
803 let executor = deterministic::Runner::default();
804 executor.start(|context| async move {
805 let mut glob: Glob<_, i32> = Glob::init(context.child("storage"), test_cfg())
806 .await
807 .expect("Failed to init glob");
808
809 let values: Vec<i32> = vec![1, 2, 3, 4, 5];
811 let mut locations = Vec::new();
812
813 for value in &values {
814 let offset;
815 let size;
816 (glob, offset, size) = glob.append(1, value).await.expect("Failed to append");
817 locations.push((offset, size));
818 }
819 glob = glob.sync(1).await.expect("Failed to sync");
820
821 let (third_offset, third_size) = locations[2];
823 let truncate_size = third_offset + u64::from(third_size);
824 let glob = glob
825 .test_reopen_section(1, truncate_size)
826 .await
827 .expect("Failed to truncate");
828
829 for (i, (offset, size)) in locations.iter().take(3).enumerate() {
831 let retrieved = glob.get(1, *offset, *size).await.expect("Failed to get");
832 assert_eq!(retrieved, values[i]);
833 }
834
835 let (fourth_offset, fourth_size) = locations[3];
837 let result = glob.get(1, fourth_offset, fourth_size).await;
838 assert!(result.is_err());
839
840 glob.destroy().await.expect("Failed to destroy");
841 });
842 }
843
844 #[test_traced]
848 fn test_glob_truncate_survives_crash() {
849 let cfg = || Config {
851 write_buffer: NZUsize!(4),
852 ..test_cfg()
853 };
854 for retained in [true, false] {
855 let executor = deterministic::Runner::default();
857 let ((kept, offset, size), checkpoint) =
858 executor.start_and_recover(move |context| async move {
859 let mut glob: Glob<_, i32> =
860 Glob::init(context.child("first"), cfg()).await.unwrap();
861 let mut kept = 0;
862 for value in 1..=3 {
863 let (offset, size);
864 (glob, offset, size) = glob.append(1, &value).await.unwrap();
865 if value == 1 {
866 kept = offset + u64::from(size);
867 }
868 }
869 let glob = glob.sync(1).await.unwrap();
870
871 *context.storage_fault_config().write() = if retained {
873 deterministic::FaultConfig {
874 write_rate: Some(deterministic::WriteConfig {
875 failure_rate: probability!(0.0),
876 retention_rate: probability!(1.0),
877 mode: deterministic::PartialWriteMode::Prefix,
878 }),
879 resize_rate: Some(deterministic::ResizeConfig {
880 failure_rate: probability!(0.0),
881 partial_rate: probability!(0.0),
882 }),
883 ..Default::default()
884 }
885 } else {
886 deterministic::FaultConfig::default()
887 };
888 let glob = glob.test_reopen_section(1, kept).await.unwrap();
889 assert_eq!(glob.size(1).unwrap(), kept);
890 let (glob, offset, size) = glob.append(1, &4).await.unwrap();
891 assert_eq!(offset, kept);
892 drop(glob);
893 (kept, offset, size)
894 });
895
896 deterministic::Runner::from(checkpoint).start(move |context| async move {
898 *context.storage_fault_config().write() = deterministic::FaultConfig::default();
899 let glob: Glob<_, i32> = Glob::init(context.child("second"), cfg()).await.unwrap();
900 let end = if retained {
901 offset + u64::from(size)
902 } else {
903 kept
904 };
905 let recovered = glob.size(1).unwrap();
906 assert_eq!(
907 recovered, end,
908 "recovered {recovered} bytes, not the {end} byte prefix of the new history"
909 );
910 assert_eq!(glob.get(1, 0, kept as u32).await.unwrap(), 1);
911 if retained {
912 assert_eq!(glob.get(1, offset, size).await.unwrap(), 4);
913 }
914 glob.destroy().await.unwrap();
915 });
916 }
917 }
918
919 #[test_traced]
920 fn test_glob_persistence() {
921 let executor = deterministic::Runner::default();
922 executor.start(|context| async move {
923 let cfg = test_cfg();
924
925 let glob: Glob<_, i32> = Glob::init(context.child("first"), cfg.clone())
927 .await
928 .expect("Failed to init glob");
929
930 let value: i32 = 42;
931 let (glob, offset, size) = glob.append(1, &value).await.expect("Failed to append");
932 let glob = glob.sync(1).await.expect("Failed to sync");
933 drop(glob);
934
935 let glob: Glob<_, i32> = Glob::init(context.child("second"), cfg)
937 .await
938 .expect("Failed to reinit glob");
939
940 let retrieved = glob.get(1, offset, size).await.expect("Failed to get");
941 assert_eq!(retrieved, value);
942
943 glob.destroy().await.expect("Failed to destroy");
944 });
945 }
946
947 #[test_traced]
948 fn test_glob_get_invalid_size() {
949 let executor = deterministic::Runner::default();
950 executor.start(|context| async move {
951 let glob: Glob<_, i32> = Glob::init(context.child("storage"), test_cfg())
952 .await
953 .expect("Failed to init glob");
954
955 let (glob, offset, _size) = glob.append(1, &42).await.expect("Failed to append");
956 let glob = glob.sync(1).await.expect("Failed to sync");
957
958 assert!(glob.get(1, offset, 0).await.is_err());
960
961 for size in 1..4u32 {
963 let result = glob.get(1, offset, size).await;
964 assert!(matches!(
965 result,
966 Err(Error::Runtime(RError::BlobInsufficientLength))
967 ));
968 }
969
970 glob.destroy().await.expect("Failed to destroy");
971 });
972 }
973
974 #[test_traced]
975 fn test_glob_get_wrong_size() {
976 let executor = deterministic::Runner::default();
977 executor.start(|context| async move {
978 let glob: Glob<_, i32> = Glob::init(context.child("storage"), test_cfg())
979 .await
980 .expect("Failed to init glob");
981
982 let (glob, offset, correct_size) = glob.append(1, &42).await.expect("Failed to append");
983 let glob = glob.sync(1).await.expect("Failed to sync");
984
985 let result = glob.get(1, offset, correct_size - 1).await;
987 assert!(matches!(result, Err(Error::ChecksumMismatch(_, _))));
988
989 glob.destroy().await.expect("Failed to destroy");
990 });
991 }
992}