1use crate::{
4 BlobVersion, Error, Handle, IoBufs, IoBufsMut, ReadOptions, WriteOptions,
5 deterministic::BoxDynRng,
6};
7use bytes::Buf;
8use commonware_utils::{
9 Probability, probability,
10 sync::{AsyncMutex, Mutex, RwLock},
11};
12use futures::{FutureExt as _, future::Shared};
13use rand::RngExt as _;
14use std::{
15 collections::{BTreeMap, HashSet},
16 io::Error as IoError,
17 sync::{
18 Arc, OnceLock, Weak,
19 atomic::{AtomicU64, Ordering},
20 },
21};
22
23#[derive(Debug, Clone, Copy, PartialEq, Eq)]
25enum Op {
26 Open,
27 Read,
28 Write,
29 Sync,
30 Resize,
31 Remove,
32 Scan,
33}
34
35#[derive(Clone, Copy, Debug, Eq, PartialEq)]
40pub enum PartialWriteMode {
41 Prefix,
43
44 Subset,
46}
47
48#[derive(Clone, Copy, Debug, PartialEq)]
52pub struct WriteConfig {
53 pub failure_rate: Probability,
55
56 pub retention_rate: Probability,
58
59 pub mode: PartialWriteMode,
61}
62
63#[cfg(feature = "arbitrary")]
64const WRITE_CONFIG_RATE_STEPS: u16 = 101;
65#[cfg(feature = "arbitrary")]
66const WRITE_CONFIG_RATE_PAIRS: u16 = WRITE_CONFIG_RATE_STEPS * WRITE_CONFIG_RATE_STEPS;
67#[cfg(feature = "arbitrary")]
68const WRITE_CONFIG_CELLS: u16 = WRITE_CONFIG_RATE_PAIRS * 2;
69
70#[cfg(feature = "arbitrary")]
71fn write_config_from_cell(cell: u16) -> WriteConfig {
72 let rates = cell % WRITE_CONFIG_RATE_PAIRS;
73 let failure = rates % WRITE_CONFIG_RATE_STEPS;
74 let retention = rates / WRITE_CONFIG_RATE_STEPS;
75 WriteConfig {
76 failure_rate: Probability::new(u64::from(failure), 100).unwrap(),
77 retention_rate: Probability::new(u64::from(retention), 100).unwrap(),
78 mode: if cell < WRITE_CONFIG_RATE_PAIRS {
79 PartialWriteMode::Prefix
80 } else {
81 PartialWriteMode::Subset
82 },
83 }
84}
85
86#[cfg(feature = "arbitrary")]
87impl<'a> arbitrary::Arbitrary<'a> for WriteConfig {
88 fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
89 Ok(write_config_from_cell(
90 u.int_in_range(0..=WRITE_CONFIG_CELLS - 1)?,
91 ))
92 }
93
94 fn size_hint(_: usize) -> (usize, Option<usize>) {
95 (2, Some(2))
96 }
97}
98
99#[derive(Clone, Copy, Debug, PartialEq)]
101pub struct ResizeConfig {
102 pub failure_rate: Probability,
106
107 pub partial_rate: Probability,
110}
111
112#[derive(Clone, Debug, Default)]
114pub struct Config {
115 pub open_rate: Option<Probability>,
117
118 pub read_rate: Option<Probability>,
120
121 pub write_rate: Option<WriteConfig>,
123
124 pub sync_rate: Option<Probability>,
126
127 pub resize_rate: Option<ResizeConfig>,
129
130 pub remove_rate: Option<Probability>,
132
133 pub scan_rate: Option<Probability>,
135}
136
137impl Config {
138 fn rate_for(&self, op: Op) -> Probability {
140 match op {
141 Op::Open => self.open_rate,
142 Op::Read => self.read_rate,
143 Op::Write => self.write_rate.map(|config| config.failure_rate),
144 Op::Sync => self.sync_rate,
145 Op::Resize => self.resize_rate.map(|config| config.failure_rate),
146 Op::Remove => self.remove_rate,
147 Op::Scan => self.scan_rate,
148 }
149 .unwrap_or(probability!(0.0))
150 }
151
152 pub const fn open(mut self, rate: Probability) -> Self {
154 self.open_rate = Some(rate);
155 self
156 }
157
158 pub const fn read(mut self, rate: Probability) -> Self {
160 self.read_rate = Some(rate);
161 self
162 }
163
164 pub const fn write(mut self, config: WriteConfig) -> Self {
166 self.write_rate = Some(config);
167 self
168 }
169
170 pub const fn sync(mut self, rate: Probability) -> Self {
172 self.sync_rate = Some(rate);
173 self
174 }
175
176 pub const fn resize(mut self, config: ResizeConfig) -> Self {
178 self.resize_rate = Some(config);
179 self
180 }
181
182 pub const fn remove(mut self, rate: Probability) -> Self {
184 self.remove_rate = Some(rate);
185 self
186 }
187
188 pub const fn scan(mut self, rate: Probability) -> Self {
190 self.scan_rate = Some(rate);
191 self
192 }
193}
194
195#[derive(Clone)]
197struct Oracle {
198 rng: Arc<Mutex<BoxDynRng>>,
199 config: Arc<RwLock<Config>>,
200}
201
202enum PendingMutation<B> {
204 Write {
207 generation: Arc<FileGeneration>,
208 blob: Arc<B>,
209 offset: u64,
210 bufs: IoBufs,
211 retention: Arc<PendingWriteRetention>,
212 selection_offset: usize,
213 },
214 Resize {
216 generation: Arc<FileGeneration>,
217 blob: Arc<B>,
218 len: u64,
219 },
220 Sync { sync: Arc<PendingSync> },
222}
223
224struct PendingSync {
227 generation: Arc<FileGeneration>,
228 completion: Shared<Handle<()>>,
229}
230
231impl PendingSync {
232 fn completed_successfully(&self) -> bool {
233 matches!(self.completion.clone().now_or_never(), Some(Ok(())))
234 }
235}
236
237struct PendingWriteRetention {
240 policy: (PartialWriteMode, Probability),
241 len: usize,
242 selected: OnceLock<Vec<bool>>,
243}
244
245impl PendingWriteRetention {
246 const fn new(policy: (PartialWriteMode, Probability), len: usize) -> Self {
247 Self {
248 policy,
249 len,
250 selected: OnceLock::new(),
251 }
252 }
253}
254
255impl<B> PendingMutation<B> {
256 fn generation(&self) -> &Arc<FileGeneration> {
257 match self {
258 Self::Write { generation, .. } | Self::Resize { generation, .. } => generation,
259 Self::Sync { sync } => &sync.generation,
260 }
261 }
262}
263
264type PendingMutations<B> = Arc<Mutex<Vec<PendingMutation<B>>>>;
266
267pub(crate) struct Retired<B> {
269 _mutations: Vec<PendingMutation<B>>,
270}
271
272type FileKey = (String, Vec<u8>);
274
275struct FileGeneration {
277 mutation: AsyncMutex<()>,
278}
279
280impl FileGeneration {
281 fn new() -> Self {
282 Self {
283 mutation: AsyncMutex::new(()),
284 }
285 }
286}
287
288type FileGenerations = Arc<Mutex<BTreeMap<FileKey, Weak<FileGeneration>>>>;
290
291fn clear_pending<B>(
292 pending: &PendingMutations<B>,
293 generations: &[Arc<FileGeneration>],
294) -> Retired<B> {
295 let mut pending = pending.lock();
296 let retired = pending
297 .extract_if(.., |mutation| {
298 generations
299 .iter()
300 .any(|generation| Arc::ptr_eq(mutation.generation(), generation))
301 })
302 .collect();
303 Retired {
304 _mutations: retired,
305 }
306}
307
308fn resolve_pending_sync<B>(
310 pending: &PendingMutations<B>,
311 sync: &Arc<PendingSync>,
312 succeeded: bool,
313) {
314 let mut pending = pending.lock();
315 let Some(cut) = pending.iter().position(
316 |mutation| matches!(mutation, PendingMutation::Sync { sync: candidate, .. } if Arc::ptr_eq(candidate, sync)),
317 ) else {
318 return;
319 };
320 let mut index = 0;
321 let retired: Vec<_> = pending.extract_if(.., |mutation| {
322 let is_target = matches!(mutation, PendingMutation::Sync { sync: candidate, .. } if Arc::ptr_eq(candidate, sync));
323 let retire = is_target
324 || (succeeded
325 && index < cut
326 && Arc::ptr_eq(mutation.generation(), &sync.generation));
327 index += 1;
328 retire
329 }).collect();
330 drop(pending);
331 drop(retired);
332}
333
334impl Oracle {
335 fn should_fail(&self, op: Op) -> bool {
337 self.roll(self.config.read().rate_for(op))
338 }
339
340 fn check_write_fault(&self) -> (bool, Option<(PartialWriteMode, Probability)>) {
343 let config = self.config.read();
344 let fail = self.roll(config.rate_for(Op::Write));
345 let retention = config
346 .write_rate
347 .map(|config| (config.mode, config.retention_rate))
348 .filter(|(_, retention_rate)| !retention_rate.is_zero());
349 (fail, retention)
350 }
351
352 fn check_resize_fault(&self) -> (bool, Probability, bool) {
355 let config = self.config.read();
356 let Some(resize_config) = config.resize_rate else {
357 return (false, probability!(0.0), false);
358 };
359 let failure_rate = config.rate_for(Op::Resize);
360 let fail = self.roll(failure_rate);
361 let retain = !fail && self.roll(failure_rate);
362 (fail, resize_config.partial_rate, retain)
363 }
364
365 fn roll(&self, rate: Probability) -> bool {
367 rate.sample(&mut **self.rng.lock())
368 }
369
370 fn random_between(&self, from: u64, to: u64) -> Option<u64> {
372 if from == to {
373 return None;
374 }
375 let (min, max) = if from < to { (from, to) } else { (to, from) };
376 if max - min <= 1 {
377 return None;
378 }
379 Some(self.rng.lock().random_range(min + 1..max))
380 }
381
382 fn retained_bytes(
384 &self,
385 len: usize,
386 (mode, retention_rate): (PartialWriteMode, Probability),
387 ) -> Vec<bool> {
388 let mut rng = self.rng.lock();
389 match mode {
390 PartialWriteMode::Prefix => {
391 let mut positions = vec![false; len];
392 let retained = (0..len)
393 .take_while(|_| retention_rate.sample(&mut **rng))
394 .count();
395 positions[..retained].fill(true);
396 positions
397 }
398 PartialWriteMode::Subset => (0..len)
399 .map(|_| retention_rate.sample(&mut **rng))
400 .collect(),
401 }
402 }
403
404 fn try_partial(&self, rate: Probability, from: u64, to: u64) -> Option<u64> {
407 if self.roll(rate) {
408 self.random_between(from, to)
409 } else {
410 None
411 }
412 }
413}
414
415#[derive(Clone)]
419pub struct Storage<S: crate::Storage> {
420 inner: S,
421 ctx: Oracle,
422 pending: PendingMutations<S::Blob>,
423 generations: FileGenerations,
424}
425
426impl<S: crate::Storage> Storage<S> {
427 pub fn new(inner: S, rng: Arc<Mutex<BoxDynRng>>, config: Arc<RwLock<Config>>) -> Self {
429 Self {
430 inner,
431 ctx: Oracle { rng, config },
432 pending: Arc::new(Mutex::new(Vec::new())),
433 generations: Arc::new(Mutex::new(BTreeMap::new())),
434 }
435 }
436
437 pub const fn inner(&self) -> &S {
439 &self.inner
440 }
441
442 pub fn config(&self) -> Arc<RwLock<Config>> {
444 self.ctx.config.clone()
445 }
446
447 fn wrap_blob(&self, partition: &str, name: &[u8], inner: S::Blob, size: u64) -> Blob<S::Blob> {
449 let key = (partition.to_string(), name.to_vec());
450 let generation = {
451 let mut generations = self.generations.lock();
452 generations
453 .get(&key)
454 .and_then(Weak::upgrade)
455 .unwrap_or_else(|| {
456 let generation = Arc::new(FileGeneration::new());
457 generations.insert(key.clone(), Arc::downgrade(&generation));
458 generation
459 })
460 };
461 Blob::new(
462 self.ctx.clone(),
463 self.pending.clone(),
464 generation,
465 inner,
466 size,
467 )
468 }
469
470 fn retire_names(&self, partition: &str, name: Option<&[u8]>) -> Retired<S::Blob> {
472 let retired = {
473 let mut generations = self.generations.lock();
474 match name {
475 Some(name) => generations
476 .remove(&(partition.to_string(), name.to_vec()))
477 .and_then(|generation| generation.upgrade())
478 .into_iter()
479 .collect::<Vec<_>>(),
480 None => generations
481 .extract_if(.., |(candidate, _), _| candidate == partition)
482 .filter_map(|(_, generation)| generation.upgrade())
483 .collect(),
484 }
485 };
486 clear_pending(&self.pending, &retired)
487 }
488
489 pub(crate) async fn remove_retired(
491 &self,
492 partition: &str,
493 name: Option<&[u8]>,
494 ) -> Result<Retired<S::Blob>, Error> {
495 if self.ctx.should_fail(Op::Remove) {
496 return Err(injected_io_error().into());
497 }
498 self.inner.remove(partition, name).await?;
499 Ok(self.retire_names(partition, name))
500 }
501}
502
503impl Storage<crate::storage::memory::Storage> {
504 pub(crate) fn admit(
507 &self,
508 partition: &str,
509 name: &[u8],
510 ) -> Retired<crate::storage::memory::Blob> {
511 let generation = self
512 .generations
513 .lock()
514 .get(&(partition.to_owned(), name.to_vec()))
515 .and_then(Weak::upgrade)
516 .expect("an admitted blob retains its file generation");
517 clear_pending(&self.pending, std::slice::from_ref(&generation))
518 }
519
520 pub(crate) fn crash(&self) -> Result<(), Error> {
522 let pending = std::mem::take(&mut *self.pending.lock());
523 let mut synced = HashSet::new();
524 let mut replay = Vec::with_capacity(pending.len());
525 for mutation in pending.into_iter().rev() {
526 match mutation {
527 PendingMutation::Sync { sync } => {
528 if sync.completed_successfully() {
529 synced.insert(Arc::as_ptr(&sync.generation));
530 }
531 }
532 mutation => {
533 if !synced.contains(&Arc::as_ptr(mutation.generation())) {
534 replay.push(mutation);
535 }
536 }
537 }
538 }
539 for mutation in replay.into_iter().rev() {
540 match mutation {
541 PendingMutation::Write {
542 blob,
543 offset,
544 bufs,
545 retention,
546 selection_offset,
547 ..
548 } => {
549 let selected = retention
550 .selected
551 .get_or_init(|| self.ctx.retained_bytes(retention.len, retention.policy));
552 let selection_end = selection_offset
553 .checked_add(bufs.remaining())
554 .expect("a pending-write fragment stays within its selection");
555 let mut retained = selected[selection_offset..selection_end].iter().copied();
556 blob.retain_crash_write(offset, bufs, || {
557 retained
558 .next()
559 .expect("the retention policy covers every submitted byte")
560 })?;
561 }
562 PendingMutation::Resize { blob, len, .. } => {
563 blob.retain_crash_resize(len)?;
564 }
565 PendingMutation::Sync { .. } => unreachable!("sync markers are not replayed"),
566 }
567 }
568 Ok(())
569 }
570}
571
572fn injected_io_error() -> IoError {
574 IoError::other("injected storage fault")
575}
576
577impl<S: crate::Storage> crate::Storage for Storage<S> {
578 type Blob = Blob<S::Blob>;
579
580 async fn open_versioned(
581 &self,
582 partition: &str,
583 name: &[u8],
584 versions: std::ops::RangeInclusive<BlobVersion>,
585 ) -> Result<(Self::Blob, u64, BlobVersion), Error> {
586 if self.ctx.should_fail(Op::Open) {
587 return Err(injected_io_error().into());
588 }
589 let (blob, len, blob_version) =
590 self.inner.open_versioned(partition, name, versions).await?;
591 Ok((
592 self.wrap_blob(partition, name, blob, len),
593 len,
594 blob_version,
595 ))
596 }
597
598 async fn remove(&self, partition: &str, name: Option<&[u8]>) -> Result<(), Error> {
599 drop(self.remove_retired(partition, name).await?);
600 Ok(())
601 }
602
603 async fn scan(&self, partition: &str) -> Result<Vec<Vec<u8>>, Error> {
604 if self.ctx.should_fail(Op::Scan) {
605 return Err(injected_io_error().into());
606 }
607 self.inner.scan(partition).await
608 }
609}
610
611pub struct Blob<B: crate::Blob> {
613 inner: Arc<B>,
614 ctx: Oracle,
615 pending: PendingMutations<B>,
616 generation: Arc<FileGeneration>,
617 size: AtomicU64,
619}
620
621impl<B: crate::Blob> Blob<B> {
622 fn new(
623 ctx: Oracle,
624 pending: PendingMutations<B>,
625 generation: Arc<FileGeneration>,
626 inner: B,
627 size: u64,
628 ) -> Self {
629 Self {
630 inner: Arc::new(inner),
631 ctx,
632 pending,
633 generation,
634 size: AtomicU64::new(size),
635 }
636 }
637
638 fn record_pending(
639 &self,
640 offset: u64,
641 bufs: IoBufs,
642 retention: (PartialWriteMode, Probability),
643 ) {
644 if bufs.is_empty() {
645 return;
646 }
647 let retention = Arc::new(PendingWriteRetention::new(retention, bufs.remaining()));
648 self.pending.lock().push(PendingMutation::Write {
649 generation: self.generation.clone(),
650 blob: self.inner.clone(),
651 offset,
652 bufs,
653 retention,
654 selection_offset: 0,
655 });
656 }
657
658 fn record_pending_resize(&self, len: u64) {
659 self.pending.lock().push(PendingMutation::Resize {
660 generation: self.generation.clone(),
661 blob: self.inner.clone(),
662 len,
663 });
664 }
665
666 fn record_durable_range(&self, offset: u64, durable: IoBufs) {
668 let len = durable.remaining() as u64;
669 if len == 0 {
670 return;
671 }
672 let end = offset
673 .checked_add(len)
674 .expect("submitted write range was validated before mutation");
675 let mut pending = self.pending.lock();
676 let mutations = std::mem::take(&mut *pending);
677 let mut retained = Vec::with_capacity(mutations.len() + 1);
678 let mut retired = Vec::new();
679 let mut follows_resize = false;
680 for mutation in mutations {
681 if !Arc::ptr_eq(mutation.generation(), &self.generation) {
682 retained.push(mutation);
683 continue;
684 }
685 let (write_generation, write_blob, write_offset, mut bufs, retention, selection) =
686 match mutation {
687 PendingMutation::Write {
688 generation,
689 blob,
690 offset,
691 bufs,
692 retention,
693 selection_offset,
694 } => (generation, blob, offset, bufs, retention, selection_offset),
695 resize @ PendingMutation::Resize { .. } => {
696 follows_resize = true;
697 retained.push(resize);
698 continue;
699 }
700 sync @ PendingMutation::Sync { .. } => {
701 retained.push(sync);
702 continue;
703 }
704 };
705 let write_len = bufs.remaining() as u64;
706 let write_end = write_offset
707 .checked_add(write_len)
708 .expect("pending write ranges are validated before recording");
709 let overlap_start = write_offset.max(offset);
710 let overlap_end = write_end.min(end);
711 if overlap_start >= overlap_end {
712 retained.push(PendingMutation::Write {
713 generation: write_generation,
714 blob: write_blob,
715 offset: write_offset,
716 bufs,
717 retention,
718 selection_offset: selection,
719 });
720 continue;
721 }
722
723 let prefix_len = usize::try_from(overlap_start - write_offset)
724 .expect("a pending-write subrange fits its source buffer");
725 let prefix = bufs.split_to(prefix_len);
726 let overlap_len = usize::try_from(overlap_end - overlap_start)
727 .expect("an overlapping subrange fits its source buffer");
728 retired.push(bufs.split_to(overlap_len));
729 if prefix_len != 0 {
730 retained.push(PendingMutation::Write {
731 generation: write_generation.clone(),
732 blob: write_blob.clone(),
733 offset: write_offset,
734 bufs: prefix,
735 retention: retention.clone(),
736 selection_offset: selection,
737 });
738 }
739 if overlap_end < write_end {
740 let suffix_start = usize::try_from(overlap_end - write_offset)
741 .expect("a pending-write subrange fits its source buffer");
742 retained.push(PendingMutation::Write {
743 generation: write_generation,
744 blob: write_blob,
745 offset: overlap_end,
746 bufs,
747 retention,
748 selection_offset: selection
749 .checked_add(suffix_start)
750 .expect("a pending-write fragment stays within its selection"),
751 });
752 }
753 }
754 if follows_resize {
755 let retention = Arc::new(PendingWriteRetention::new(
756 (PartialWriteMode::Prefix, probability!(1.0)),
757 durable.remaining(),
758 ));
759 retained.push(PendingMutation::Write {
760 generation: self.generation.clone(),
761 blob: self.inner.clone(),
762 offset,
763 bufs: durable,
764 retention,
765 selection_offset: 0,
766 });
767 }
768 *pending = retained;
769 drop(pending);
770 drop(retired);
771 }
772}
773
774impl<B: crate::Blob> crate::Blob for Blob<B> {
775 async fn read_at(
776 &self,
777 offset: u64,
778 len: usize,
779 options: ReadOptions,
780 ) -> Result<IoBufsMut, Error> {
781 if self.ctx.should_fail(Op::Read) {
782 return Err(injected_io_error().into());
783 }
784 self.inner.read_at(offset, len, options).await
785 }
786
787 async fn read_at_buf(
788 &self,
789 offset: u64,
790 len: usize,
791 bufs: impl Into<IoBufsMut> + Send,
792 options: ReadOptions,
793 ) -> Result<IoBufsMut, Error> {
794 if self.ctx.should_fail(Op::Read) {
795 return Err(injected_io_error().into());
796 }
797 self.inner
798 .read_at_buf(offset, len, bufs.into(), options)
799 .await
800 }
801
802 async fn write_at(
803 &self,
804 offset: u64,
805 bufs: impl Into<IoBufs> + Send,
806 options: WriteOptions,
807 ) -> Result<(), Error> {
808 let bufs = bufs.into();
809 let total_bytes = bufs.remaining() as u64;
810 let sync = options.contains(WriteOptions::SYNC);
811 if sync && total_bytes == 0 {
812 return Ok(());
813 }
814 offset
815 .checked_add(total_bytes)
816 .ok_or(Error::OffsetOverflow)?;
817 let (should_fail, write_retention) = self.ctx.check_write_fault();
818 let _mutation = self.generation.mutation.lock().await;
819 if should_fail {
820 if let Some(retention) = write_retention {
821 let len = bufs.remaining();
822 let retained = self.ctx.retained_bytes(len, retention);
823 let bufs = bufs.coalesce();
824 let mut position = 0;
825 while position < len {
826 if !retained[position] {
827 position += 1;
828 continue;
829 }
830
831 let start = position;
832 while position < len && retained[position] {
833 position += 1;
834 }
835 let end = position;
836 let run_offset = offset
837 .checked_add(start as u64)
838 .ok_or(Error::OffsetOverflow)?;
839 let run = bufs.slice(start..end);
840 let durable = run.clone().into();
841 self.inner
842 .write_at(run_offset, run, options | WriteOptions::SYNC)
843 .await?;
844 self.record_durable_range(run_offset, durable);
845 self.size.fetch_max(
846 run_offset.saturating_add((end - start) as u64),
847 Ordering::Relaxed,
848 );
849 }
850 }
851 return Err(injected_io_error().into());
852 }
853
854 if sync && self.ctx.should_fail(Op::Sync) {
855 let pending = write_retention.map(|retention| (bufs.clone(), retention));
856 self.inner
857 .write_at(offset, bufs, options.without(WriteOptions::SYNC))
858 .await?;
859 self.size
860 .fetch_max(offset.saturating_add(total_bytes), Ordering::Relaxed);
861 if let Some((bufs, retention)) = pending {
862 self.record_pending(offset, bufs, retention);
863 }
864 return Err(injected_io_error().into());
865 }
866
867 let pending = match (sync, write_retention) {
868 (false, Some(retention)) => Some((bufs.clone(), retention)),
869 _ => None,
870 };
871 let durable = sync.then(|| bufs.clone());
872 self.inner.write_at(offset, bufs, options).await?;
873 self.size
874 .fetch_max(offset.saturating_add(total_bytes), Ordering::Relaxed);
875 if let Some(durable) = durable {
876 self.record_durable_range(offset, durable);
877 } else if let Some((bufs, retention)) = pending {
878 self.record_pending(offset, bufs, retention);
879 }
880 Ok(())
881 }
882
883 async fn resize(&self, len: u64) -> Result<(), Error> {
884 let (should_fail, partial_rate, retain) = self.ctx.check_resize_fault();
885 let _mutation = self.generation.mutation.lock().await;
886 if should_fail {
887 let current = self.size.load(Ordering::Relaxed);
888 if let Some(len) = self.ctx.try_partial(partial_rate, current, len) {
889 self.inner.resize(len).await?;
890 self.record_pending_resize(len);
891 self.size.store(len, Ordering::Relaxed);
892 return Err(injected_io_error().into());
893 }
894 return Err(injected_io_error().into());
895 }
896 self.inner.resize(len).await?;
897 if retain {
898 self.record_pending_resize(len);
899 }
900 self.size.store(len, Ordering::Relaxed);
901 Ok(())
902 }
903
904 async fn sync(&self) -> Result<(), Error> {
905 if self.ctx.should_fail(Op::Sync) {
906 return Err(injected_io_error().into());
907 }
908 let _mutation = self.generation.mutation.lock().await;
909 self.inner.sync().await?;
910 clear_pending(&self.pending, std::slice::from_ref(&self.generation));
911 Ok(())
912 }
913
914 async fn start_sync(&self) -> Handle<()> {
915 if self.ctx.should_fail(Op::Sync) {
916 return Handle::ready(Err(injected_io_error().into()));
917 }
918 let _mutation = self.generation.mutation.lock().await;
919 let sync = Arc::new(PendingSync {
920 generation: self.generation.clone(),
921 completion: self.inner.start_sync().await.shared(),
922 });
923 self.pending
924 .lock()
925 .push(PendingMutation::Sync { sync: sync.clone() });
926
927 let pending = self.pending.clone();
928 let completion = sync.completion.clone();
929 Handle::from_future(async move {
930 let result = completion.await;
931 resolve_pending_sync(&pending, &sync, result.is_ok());
932 result
933 })
934 }
935}
936
937#[cfg(test)]
938mod tests {
939 use super::*;
940 use crate::{
941 Blob as _, IoBufMut, Runner, Spawner, Storage as _,
942 mocks::RecordingContext,
943 storage::{memory::Storage as MemStorage, tests::run_storage_tests},
944 };
945 use commonware_utils::ScriptedRng;
946 use futures::task::noop_waker;
947 use rand::{SeedableRng, rngs::StdRng};
948 use rstest::rstest;
949 use std::{
950 future::Future,
951 pin::Pin,
952 sync::atomic::AtomicBool,
953 task::{Context, Poll},
954 };
955
956 #[cfg(feature = "arbitrary")]
957 #[test]
958 fn test_write_config_arbitrary_is_compact_and_covers_grid() {
959 use arbitrary::{Arbitrary as _, Unstructured};
960
961 assert_eq!(WriteConfig::size_hint(0), (2, Some(2)));
962 let mut input = Unstructured::new(&[0, 0, 0]);
963 WriteConfig::arbitrary(&mut input).unwrap();
964 assert_eq!(input.len(), 1);
965
966 for cell in 0..WRITE_CONFIG_CELLS {
967 let config = write_config_from_cell(cell);
968 let rates = cell % WRITE_CONFIG_RATE_PAIRS;
969 assert_eq!(
970 config.failure_rate,
971 Probability::new(u64::from(rates % WRITE_CONFIG_RATE_STEPS), 100).unwrap()
972 );
973 assert_eq!(
974 config.retention_rate,
975 Probability::new(u64::from(rates / WRITE_CONFIG_RATE_STEPS), 100).unwrap()
976 );
977 assert_eq!(
978 config.mode,
979 if cell < WRITE_CONFIG_RATE_PAIRS {
980 PartialWriteMode::Prefix
981 } else {
982 PartialWriteMode::Subset
983 }
984 );
985 }
986 }
987
988 struct OperationGate<B, C> {
989 inner: Arc<B>,
990 context: Arc<C>,
991 pause_after_write: bool,
992 armed: Arc<AtomicBool>,
993 started: Arc<tokio::sync::Notify>,
994 release: Arc<tokio::sync::Notify>,
995 completed: Arc<tokio::sync::Notify>,
996 }
997
998 impl<B, C> Clone for OperationGate<B, C> {
999 fn clone(&self) -> Self {
1000 Self {
1001 inner: self.inner.clone(),
1002 context: self.context.clone(),
1003 pause_after_write: self.pause_after_write,
1004 armed: self.armed.clone(),
1005 started: self.started.clone(),
1006 release: self.release.clone(),
1007 completed: self.completed.clone(),
1008 }
1009 }
1010 }
1011
1012 impl<B, C> OperationGate<B, C> {
1013 async fn pause(&self, after_write: bool) {
1014 if self.pause_after_write == after_write && self.armed.swap(false, Ordering::Relaxed) {
1015 self.started.notify_one();
1016 self.release.notified().await;
1017 }
1018 }
1019 }
1020
1021 impl<B: crate::Blob, C: Spawner> crate::Blob for OperationGate<B, C> {
1022 async fn read_at_buf(
1023 &self,
1024 offset: u64,
1025 len: usize,
1026 bufs: impl Into<IoBufsMut> + Send,
1027 options: ReadOptions,
1028 ) -> Result<IoBufsMut, Error> {
1029 self.inner.read_at_buf(offset, len, bufs, options).await
1030 }
1031
1032 async fn read_at(
1033 &self,
1034 offset: u64,
1035 len: usize,
1036 options: ReadOptions,
1037 ) -> Result<IoBufsMut, Error> {
1038 self.inner.read_at(offset, len, options).await
1039 }
1040
1041 async fn write_at(
1042 &self,
1043 offset: u64,
1044 bufs: impl Into<IoBufs> + Send,
1045 options: WriteOptions,
1046 ) -> Result<(), Error> {
1047 self.inner.write_at(offset, bufs, options).await?;
1048 self.pause(true).await;
1049 Ok(())
1050 }
1051
1052 async fn resize(&self, len: u64) -> Result<(), Error> {
1053 self.inner.resize(len).await
1054 }
1055
1056 async fn sync(&self) -> Result<(), Error> {
1057 self.inner.sync().await?;
1058 self.pause(false).await;
1059 Ok(())
1060 }
1061
1062 async fn start_sync(&self) -> Handle<()> {
1063 let gate = self.clone();
1064 let (sender, receiver) = tokio::sync::oneshot::channel();
1065 drop(self.context.child("sync").spawn(move |_| async move {
1066 let _ = sender.send(gate.sync().await);
1067 gate.completed.notify_one();
1068 }));
1069 Handle::from_receiver(receiver)
1070 }
1071 }
1072
1073 fn poll_once<F: Future>(future: Pin<&mut F>) -> Poll<F::Output> {
1074 let waker = noop_waker();
1075 future.poll(&mut Context::from_waker(&waker))
1076 }
1077
1078 struct Harness {
1080 inner: MemStorage,
1081 storage: Storage<MemStorage>,
1082 config: Arc<RwLock<Config>>,
1083 }
1084
1085 impl Harness {
1086 fn new(config: Config) -> Self {
1087 Self::with_seed(42, config)
1088 }
1089
1090 fn with_seed(seed: u64, config: Config) -> Self {
1091 Self::with_rng(Box::new(StdRng::seed_from_u64(seed)), config)
1092 }
1093
1094 fn with_rng(rng: BoxDynRng, config: Config) -> Self {
1095 let inner = MemStorage::new();
1096 let rng = Arc::new(Mutex::new(rng));
1097 let config = Arc::new(RwLock::new(config));
1098 let storage = Storage::new(inner.clone(), rng, config.clone());
1099 Self {
1100 inner,
1101 storage,
1102 config,
1103 }
1104 }
1105 }
1106
1107 #[rstest]
1108 #[case::tokio(crate::tokio::Runner::default())]
1109 #[cfg_attr(target_os = "linux", case::iouring(crate::iouring::Runner::default()))]
1110 fn test_start_sync_returns_before_backing_completion<R: Runner>(#[case] runner: R)
1111 where
1112 R::Context: Spawner,
1113 {
1114 runner.start(|context| async move {
1115 let h = Harness::new(Config::default().write(WriteConfig {
1116 failure_rate: probability!(0.0),
1117 retention_rate: probability!(1.0),
1118 mode: PartialWriteMode::Prefix,
1119 }));
1120 let (inner, _) = h.inner.open("partition", b"start-sync").await.unwrap();
1121 inner
1122 .write_at(0, b"data", WriteOptions::SYNC)
1123 .await
1124 .unwrap();
1125
1126 let started = Arc::new(tokio::sync::Notify::new());
1127 let release = Arc::new(tokio::sync::Notify::new());
1128 let gated = OperationGate {
1129 inner: Arc::new(inner),
1130 context: Arc::new(context),
1131 pause_after_write: false,
1132 armed: Arc::new(AtomicBool::new(true)),
1133 started: started.clone(),
1134 release: release.clone(),
1135 completed: Arc::new(tokio::sync::Notify::new()),
1136 };
1137 let pending = Arc::new(Mutex::new(Vec::new()));
1138 let blob = Blob::new(
1139 h.storage.ctx.clone(),
1140 pending,
1141 Arc::new(FileGeneration::new()),
1142 gated,
1143 4,
1144 );
1145
1146 let mut start = Box::pin(blob.start_sync());
1147 let Poll::Ready(mut completion) = poll_once(start.as_mut()) else {
1148 panic!("start_sync waited for backing durability");
1149 };
1150 started.notified().await;
1151 assert!(poll_once(Pin::new(&mut completion)).is_pending());
1152 release.notify_one();
1153 completion.await.unwrap();
1154 });
1155 }
1156
1157 async fn run_overlapping_barrier(context: impl Spawner, start: bool) {
1158 let h = Harness::new(Config::default().write(WriteConfig {
1159 failure_rate: probability!(0.0),
1160 retention_rate: probability!(1.0),
1161 mode: PartialWriteMode::Prefix,
1162 }));
1163 let (inner, _) = h.inner.open("partition", b"overlap").await.unwrap();
1164 inner
1165 .write_at(0, b"base", WriteOptions::SYNC)
1166 .await
1167 .unwrap();
1168
1169 let started = Arc::new(tokio::sync::Notify::new());
1170 let release = Arc::new(tokio::sync::Notify::new());
1171 let completed = Arc::new(tokio::sync::Notify::new());
1172 let gated = OperationGate {
1173 inner: Arc::new(inner),
1174 context: Arc::new(context.child("gate")),
1175 pause_after_write: false,
1176 armed: Arc::new(AtomicBool::new(true)),
1177 started: started.clone(),
1178 release: release.clone(),
1179 completed: completed.clone(),
1180 };
1181 let pending = Arc::new(Mutex::new(Vec::new()));
1182 let blob = Blob::new(
1183 h.storage.ctx.clone(),
1184 pending.clone(),
1185 Arc::new(FileGeneration::new()),
1186 gated,
1187 4,
1188 );
1189 let blob = Arc::new(blob);
1190
1191 let barrier_blob = blob.clone();
1192 let barrier = context.child("barrier").spawn(move |_| async move {
1193 if start {
1194 drop(barrier_blob.start_sync().await);
1195 } else {
1196 barrier_blob.sync().await.unwrap();
1197 }
1198 });
1199 started.notified().await;
1200
1201 let mut late = Box::pin(blob.write_at(0, b"late", WriteOptions::default()));
1202 if start {
1203 let Poll::Ready(result) = poll_once(late.as_mut()) else {
1204 panic!("a started sync blocked a later write");
1205 };
1206 result.unwrap();
1207 release.notify_one();
1208 barrier.await.unwrap();
1209 completed.notified().await;
1211 } else {
1212 assert!(poll_once(late.as_mut()).is_pending());
1213 release.notify_one();
1214 barrier.await.unwrap();
1215 late.await.unwrap();
1216 }
1217
1218 for mutation in std::mem::take(&mut *pending.lock()) {
1219 match mutation {
1220 PendingMutation::Write {
1221 blob,
1222 offset,
1223 bufs,
1224 retention,
1225 ..
1226 } => {
1227 assert_eq!(
1228 retention.policy,
1229 (PartialWriteMode::Prefix, probability!(1.0))
1230 );
1231 blob.inner
1232 .retain_crash_write(offset, bufs, || true)
1233 .unwrap();
1234 }
1235 PendingMutation::Sync { .. } => {}
1236 PendingMutation::Resize { .. } => panic!("write test recorded a resize"),
1237 }
1238 }
1239 let (durable, len) = h.inner.open("partition", b"overlap").await.unwrap();
1240 assert_eq!(len, 4);
1241 assert_eq!(
1242 durable
1243 .read_at(0, 4, ReadOptions::default())
1244 .await
1245 .unwrap()
1246 .coalesce(),
1247 b"late"
1248 );
1249 }
1250
1251 #[test]
1252 fn test_completed_backing_write_cannot_record_after_later_full_sync() {
1253 crate::tokio::Runner::default().start(|context| async move {
1254 let h = Harness::new(Config::default().write(WriteConfig {
1255 failure_rate: probability!(0.0),
1256 retention_rate: probability!(1.0),
1257 mode: PartialWriteMode::Prefix,
1258 }));
1259 let (inner, _) = h.inner.open("partition", b"late-record").await.unwrap();
1260 inner
1261 .write_at(0, b"base!", WriteOptions::SYNC)
1262 .await
1263 .unwrap();
1264
1265 let started = Arc::new(tokio::sync::Notify::new());
1266 let release = Arc::new(tokio::sync::Notify::new());
1267 let gated = OperationGate {
1268 inner: Arc::new(inner),
1269 context: Arc::new(context),
1270 pause_after_write: true,
1271 armed: Arc::new(AtomicBool::new(true)),
1272 started: started.clone(),
1273 release: release.clone(),
1274 completed: Arc::new(tokio::sync::Notify::new()),
1275 };
1276 let pending = Arc::new(Mutex::new(Vec::new()));
1277 let blob = Blob::new(
1278 h.storage.ctx.clone(),
1279 pending.clone(),
1280 Arc::new(FileGeneration::new()),
1281 gated,
1282 5,
1283 );
1284 let blob = Arc::new(blob);
1285
1286 let mut stale = Box::pin(blob.write_at(0, b"stale", WriteOptions::default()));
1287 assert!(poll_once(stale.as_mut()).is_pending());
1288 started.notified().await;
1289
1290 *h.config.write() = Config::default();
1291 let fresh_blob = blob.clone();
1292 let mut fresh = Box::pin(async move {
1293 fresh_blob
1294 .write_at(0, b"fresh", WriteOptions::default())
1295 .await?;
1296 fresh_blob.sync().await
1297 });
1298 assert!(poll_once(fresh.as_mut()).is_pending());
1299
1300 release.notify_one();
1301 stale.await.unwrap();
1302 fresh.await.unwrap();
1303
1304 for mutation in std::mem::take(&mut *pending.lock()) {
1305 let PendingMutation::Write {
1306 blob,
1307 offset,
1308 bufs,
1309 retention,
1310 ..
1311 } = mutation
1312 else {
1313 panic!("write test recorded a resize");
1314 };
1315 assert_eq!(
1316 retention.policy,
1317 (PartialWriteMode::Prefix, probability!(1.0))
1318 );
1319 blob.inner
1320 .retain_crash_write(offset, bufs, || true)
1321 .unwrap();
1322 }
1323 let (durable, len) = h.inner.open("partition", b"late-record").await.unwrap();
1324 assert_eq!(len, 5);
1325 assert_eq!(
1326 durable
1327 .read_at(0, 5, ReadOptions::default())
1328 .await
1329 .unwrap()
1330 .coalesce(),
1331 b"fresh"
1332 );
1333 });
1334 }
1335
1336 async fn run_subset_overwrite(seed: u64, original: &[u8], replacement: &[u8]) -> Vec<u8> {
1337 assert_eq!(original.len(), replacement.len());
1338
1339 let h = Harness::with_seed(
1340 seed,
1341 Config::default().write(WriteConfig {
1342 failure_rate: probability!(0.0),
1343 retention_rate: probability!(0.5),
1344 mode: PartialWriteMode::Subset,
1345 }),
1346 );
1347 let (blob, _) = h.storage.open("partition", b"test").await.unwrap();
1348 blob.write_at(0, original.to_vec(), WriteOptions::default())
1349 .await
1350 .unwrap();
1351 blob.sync().await.unwrap();
1352
1353 {
1354 let mut config = h.config.write();
1355 config.write_rate = Some(WriteConfig {
1356 failure_rate: probability!(1.0),
1357 retention_rate: probability!(0.5),
1358 mode: PartialWriteMode::Subset,
1359 });
1360 }
1361
1362 let result = blob
1363 .write_at(0, replacement.to_vec(), WriteOptions::default())
1364 .await;
1365 assert!(matches!(result, Err(Error::Io(_))));
1366
1367 let (inner_blob, size) = h.inner.open("partition", b"test").await.unwrap();
1368 assert_eq!(size, original.len() as u64);
1369 inner_blob
1370 .read_at(0, original.len(), ReadOptions::default())
1371 .await
1372 .unwrap()
1373 .coalesce()
1374 .as_ref()
1375 .to_vec()
1376 }
1377
1378 async fn run_random_crash(seed: u64, len: usize) -> Vec<u8> {
1379 let h = Harness::with_seed(
1380 seed,
1381 Config::default().write(WriteConfig {
1382 failure_rate: probability!(0.0),
1383 retention_rate: probability!(0.5),
1384 mode: PartialWriteMode::Subset,
1385 }),
1386 );
1387 let (blob, _) = h.storage.open("partition", b"test").await.unwrap();
1388 blob.write_at(0, vec![0; len], WriteOptions::SYNC)
1389 .await
1390 .unwrap();
1391 blob.write_at(0, vec![1; len], WriteOptions::default())
1392 .await
1393 .unwrap();
1394
1395 h.config.write().write_rate = None;
1397 h.storage.crash().unwrap();
1398
1399 let (blob, durable_len) = h.inner.open("partition", b"test").await.unwrap();
1400 assert_eq!(durable_len, len as u64);
1401 blob.read_at(0, len, ReadOptions::default())
1402 .await
1403 .unwrap()
1404 .coalesce()
1405 .as_ref()
1406 .to_vec()
1407 }
1408
1409 #[tokio::test]
1410 async fn test_reopened_sync_clears_prior_handle_crash_writes() {
1411 let h = Harness::new(Config::default().write(WriteConfig {
1412 failure_rate: probability!(0.0),
1413 retention_rate: probability!(1.0),
1414 mode: PartialWriteMode::Prefix,
1415 }));
1416 let (blob, _) = h.storage.open("partition", b"test").await.unwrap();
1417 blob.write_at(0, b"stale", WriteOptions::default())
1418 .await
1419 .unwrap();
1420 drop(blob);
1421
1422 let (blob, _) = h.storage.open("partition", b"test").await.unwrap();
1423 blob.write_at(0, b"fresh", WriteOptions::default())
1424 .await
1425 .unwrap();
1426 blob.sync().await.unwrap();
1427 drop(blob);
1428
1429 h.storage.crash().unwrap();
1430 let (blob, len) = h.inner.open("partition", b"test").await.unwrap();
1431 assert_eq!(len, 5);
1432 assert_eq!(
1433 blob.read_at(0, 5, ReadOptions::default())
1434 .await
1435 .unwrap()
1436 .coalesce()
1437 .as_ref(),
1438 b"fresh"
1439 );
1440 }
1441
1442 #[tokio::test]
1443 async fn test_dropped_completed_start_sync_clears_the_crash_epoch() {
1444 let h = Harness::new(Config::default().write(WriteConfig {
1445 failure_rate: probability!(0.0),
1446 retention_rate: probability!(1.0),
1447 mode: PartialWriteMode::Prefix,
1448 }));
1449 let (blob, _) = h.storage.open("partition", b"test").await.unwrap();
1450 blob.write_at(0, b"stale", WriteOptions::default())
1451 .await
1452 .unwrap();
1453 h.config.write().write_rate = None;
1454 blob.write_at(0, b"fresh", WriteOptions::default())
1455 .await
1456 .unwrap();
1457
1458 let completion = blob.start_sync().await;
1459 drop(completion);
1460
1461 h.config.write().write_rate = Some(WriteConfig {
1462 failure_rate: probability!(0.0),
1463 retention_rate: probability!(1.0),
1464 mode: PartialWriteMode::Prefix,
1465 });
1466 blob.write_at(5, b"later", WriteOptions::default())
1467 .await
1468 .unwrap();
1469 h.storage.crash().unwrap();
1470
1471 let (blob, len) = h.inner.open("partition", b"test").await.unwrap();
1472 assert_eq!(len, 10);
1473 assert_eq!(
1474 blob.read_at(0, 10, ReadOptions::default())
1475 .await
1476 .unwrap()
1477 .coalesce()
1478 .as_ref(),
1479 b"freshlater"
1480 );
1481 }
1482
1483 #[tokio::test]
1484 async fn test_sync_write_does_not_barrier_disjoint_pending_write() {
1485 let h = Harness::new(Config::default().write(WriteConfig {
1486 failure_rate: probability!(0.0),
1487 retention_rate: probability!(1.0),
1488 mode: PartialWriteMode::Prefix,
1489 }));
1490 let (blob, _) = h.storage.open("partition", b"test").await.unwrap();
1491 blob.write_at(0, b"........", WriteOptions::SYNC)
1492 .await
1493 .unwrap();
1494
1495 blob.write_at(0, b"A", WriteOptions::default())
1496 .await
1497 .unwrap();
1498 blob.write_at(7, b"Z", WriteOptions::SYNC).await.unwrap();
1499 h.storage.crash().unwrap();
1500
1501 let (durable, len) = h.inner.open("partition", b"test").await.unwrap();
1502 assert_eq!(len, 8);
1503 assert_eq!(
1504 durable
1505 .read_at(0, 8, ReadOptions::default())
1506 .await
1507 .unwrap()
1508 .coalesce(),
1509 b"A......Z"
1510 );
1511 }
1512
1513 #[tokio::test]
1514 async fn test_sync_write_retires_only_overlapping_pending_bytes() {
1515 let h = Harness::new(Config::default().write(WriteConfig {
1516 failure_rate: probability!(0.0),
1517 retention_rate: probability!(1.0),
1518 mode: PartialWriteMode::Prefix,
1519 }));
1520 let (blob, _) = h.storage.open("partition", b"test").await.unwrap();
1521 let (other, _) = h.storage.open("partition", b"other").await.unwrap();
1522 for candidate in [&blob, &other] {
1523 candidate
1524 .write_at(0, b"........", WriteOptions::SYNC)
1525 .await
1526 .unwrap();
1527 }
1528
1529 blob.write_at(0, b"ABCDEFGH", WriteOptions::default())
1530 .await
1531 .unwrap();
1532 other
1533 .write_at(0, b"12345678", WriteOptions::default())
1534 .await
1535 .unwrap();
1536 blob.write_at(3, b"xy", WriteOptions::SYNC).await.unwrap();
1537 h.storage.crash().unwrap();
1538
1539 let (durable, _) = h.inner.open("partition", b"test").await.unwrap();
1540 assert_eq!(
1541 durable
1542 .read_at(0, 8, ReadOptions::default())
1543 .await
1544 .unwrap()
1545 .coalesce(),
1546 b"ABCxyFGH"
1547 );
1548 let (durable, _) = h.inner.open("partition", b"other").await.unwrap();
1549 assert_eq!(
1550 durable
1551 .read_at(0, 8, ReadOptions::default())
1552 .await
1553 .unwrap()
1554 .coalesce(),
1555 b"12345678"
1556 );
1557 }
1558
1559 #[tokio::test]
1560 async fn test_range_sync_preserves_prefix_retention_across_pending_fragments() {
1561 let mut exercised = false;
1562 for seed in 0..64 {
1563 let h = Harness::with_seed(
1564 seed,
1565 Config::default().write(WriteConfig {
1566 failure_rate: probability!(0.0),
1567 retention_rate: probability!(0.5),
1568 mode: PartialWriteMode::Prefix,
1569 }),
1570 );
1571 let (blob, _) = h.storage.open("partition", b"test").await.unwrap();
1572 blob.write_at(0, b"........", WriteOptions::SYNC)
1573 .await
1574 .unwrap();
1575 blob.write_at(0, b"ABCDEFGH", WriteOptions::default())
1576 .await
1577 .unwrap();
1578 blob.write_at(3, b"xy", WriteOptions::SYNC).await.unwrap();
1579
1580 h.storage.crash().unwrap();
1581 let (durable, _) = h.inner.open("partition", b"test").await.unwrap();
1582 let durable = durable
1583 .read_at(0, 8, ReadOptions::default())
1584 .await
1585 .unwrap()
1586 .coalesce();
1587 if durable.as_ref()[..3].contains(&b'.') {
1588 exercised = true;
1589 assert_eq!(&durable.as_ref()[5..], b"...");
1590 }
1591 }
1592 assert!(exercised, "seed sweep must exercise a partial prefix");
1593 }
1594
1595 #[tokio::test]
1596 async fn test_crash_replays_writes_and_resizes_in_issue_order() {
1597 let retained_resizes = [u64::MAX, 0].repeat(3);
1598 let h = Harness::with_rng(
1599 Box::new(ScriptedRng::new(retained_resizes)),
1600 Config::default()
1601 .write(WriteConfig {
1602 failure_rate: probability!(0.0),
1603 retention_rate: probability!(1.0),
1604 mode: PartialWriteMode::Prefix,
1605 })
1606 .resize(ResizeConfig {
1607 failure_rate: probability!(0.5),
1608 partial_rate: probability!(0.0),
1609 }),
1610 );
1611 let (write_then_resize, _) = h.storage.open("partition", b"first").await.unwrap();
1612 write_then_resize
1613 .write_at(0, b"abcdef", WriteOptions::default())
1614 .await
1615 .unwrap();
1616 write_then_resize.resize(3).await.unwrap();
1617 write_then_resize.resize(5).await.unwrap();
1618
1619 let (resize_then_write, _) = h.storage.open("partition", b"second").await.unwrap();
1620 resize_then_write
1621 .write_at(0, b"abcdef", WriteOptions::SYNC)
1622 .await
1623 .unwrap();
1624 resize_then_write.resize(3).await.unwrap();
1625 resize_then_write
1626 .write_at(5, b"X", WriteOptions::default())
1627 .await
1628 .unwrap();
1629
1630 h.storage.crash().unwrap();
1631
1632 let (first, len) = h.inner.open("partition", b"first").await.unwrap();
1633 assert_eq!(len, 5);
1634 assert_eq!(
1635 first
1636 .read_at(0, 5, ReadOptions::default())
1637 .await
1638 .unwrap()
1639 .coalesce(),
1640 b"abc\0\0"
1641 );
1642 let (second, len) = h.inner.open("partition", b"second").await.unwrap();
1643 assert_eq!(len, 6);
1644 assert_eq!(
1645 second
1646 .read_at(0, 6, ReadOptions::default())
1647 .await
1648 .unwrap()
1649 .coalesce(),
1650 b"abc\0\0X"
1651 );
1652 }
1653
1654 #[tokio::test]
1655 async fn test_partial_sync_write_replays_after_retained_resize() {
1656 let retained_resize = [u64::MAX, 0];
1657 let retained_write_bytes = [0, u64::MAX, 0, u64::MAX];
1658 let h = Harness::with_rng(
1659 Box::new(ScriptedRng::new(
1660 retained_resize.into_iter().chain(retained_write_bytes),
1661 )),
1662 Config::default().resize(ResizeConfig {
1663 failure_rate: probability!(0.5),
1664 partial_rate: probability!(0.0),
1665 }),
1666 );
1667 let (blob, _) = h.storage.open("partition", b"test").await.unwrap();
1668 blob.write_at(0, b"abcdefghij", WriteOptions::SYNC)
1669 .await
1670 .unwrap();
1671 blob.resize(3).await.unwrap();
1672 {
1673 let mut config = h.config.write();
1674 config.write_rate = Some(WriteConfig {
1675 failure_rate: probability!(1.0),
1676 retention_rate: probability!(0.5),
1677 mode: PartialWriteMode::Subset,
1678 });
1679 }
1680
1681 assert!(blob.write_at(6, b"WXYZ", WriteOptions::SYNC).await.is_err());
1682 let (before, _) = h.inner.open("partition", b"test").await.unwrap();
1683 let before = before
1684 .read_at(0, 10, ReadOptions::default())
1685 .await
1686 .unwrap()
1687 .coalesce();
1688 let mut expected = b"abc".to_vec();
1689 for (index, replacement) in b"WXYZ".iter().copied().enumerate() {
1690 let index = index + 6;
1691 if before.as_ref()[index] != replacement {
1692 continue;
1693 }
1694 expected.resize(index + 1, 0);
1695 expected[index] = replacement;
1696 }
1697 assert!(expected.len() > 3);
1698
1699 h.storage.crash().unwrap();
1700
1701 let (durable, len) = h.inner.open("partition", b"test").await.unwrap();
1702 assert_eq!(len, expected.len() as u64);
1703 assert_eq!(
1704 durable
1705 .read_at(0, expected.len(), ReadOptions::default())
1706 .await
1707 .unwrap()
1708 .coalesce()
1709 .as_ref(),
1710 expected
1711 );
1712 }
1713
1714 #[tokio::test]
1715 async fn test_preissued_sync_write_survives_failed_partial_resize() {
1716 let h = Harness::new(Config::default().resize(ResizeConfig {
1717 failure_rate: probability!(1.0),
1718 partial_rate: probability!(1.0),
1719 }));
1720 let (blob, _) = h.storage.open("partition", b"test").await.unwrap();
1721 blob.write_at(0, b"abcdefghij", WriteOptions::SYNC)
1722 .await
1723 .unwrap();
1724
1725 let (resize, write) = tokio::join!(biased;
1726 blob.resize(0),
1727 blob.write_at(10, b"X", WriteOptions::SYNC),
1728 );
1729 assert!(resize.is_err());
1730 write.unwrap();
1731
1732 let retained_len = h
1733 .storage
1734 .pending
1735 .lock()
1736 .iter()
1737 .find_map(|mutation| match mutation {
1738 PendingMutation::Resize { len, .. } => Some(*len),
1739 PendingMutation::Write { .. } | PendingMutation::Sync { .. } => None,
1740 })
1741 .unwrap();
1742 h.storage.crash().unwrap();
1743
1744 let (durable, len) = h.inner.open("partition", b"test").await.unwrap();
1745 assert_eq!(len, 11);
1746 let mut expected = b"abcdefghij".to_vec();
1747 expected.truncate(retained_len as usize);
1748 expected.resize(10, 0);
1749 expected.push(b'X');
1750 assert_eq!(
1751 durable
1752 .read_at(0, 11, ReadOptions::default())
1753 .await
1754 .unwrap()
1755 .coalesce()
1756 .as_ref(),
1757 expected
1758 );
1759 }
1760
1761 #[tokio::test]
1762 async fn test_partial_sync_write_retires_each_persisted_range() {
1763 for partial_write_mode in [PartialWriteMode::Prefix, PartialWriteMode::Subset] {
1764 let h = Harness::new(Config::default().write(WriteConfig {
1765 failure_rate: probability!(0.0),
1766 retention_rate: probability!(1.0),
1767 mode: PartialWriteMode::Prefix,
1768 }));
1769 let (blob, _) = h.storage.open("partition", b"test").await.unwrap();
1770 blob.write_at(0, b"........", WriteOptions::SYNC)
1771 .await
1772 .unwrap();
1773 blob.write_at(0, b"ABCDEFGH", WriteOptions::default())
1774 .await
1775 .unwrap();
1776 {
1777 let mut config = h.config.write();
1778 config.write_rate = Some(WriteConfig {
1779 failure_rate: probability!(1.0),
1780 retention_rate: probability!(0.5),
1781 mode: partial_write_mode,
1782 });
1783 }
1784
1785 assert!(blob.write_at(2, b"wxyz", WriteOptions::SYNC).await.is_err());
1786 let (before, _) = h.inner.open("partition", b"test").await.unwrap();
1787 let before = before
1788 .read_at(0, 8, ReadOptions::default())
1789 .await
1790 .unwrap()
1791 .coalesce();
1792 h.storage.crash().unwrap();
1793 let (after, _) = h.inner.open("partition", b"test").await.unwrap();
1794 let after = after
1795 .read_at(0, 8, ReadOptions::default())
1796 .await
1797 .unwrap()
1798 .coalesce();
1799 for (index, (&before, &after)) in before
1800 .as_ref()
1801 .iter()
1802 .zip(after.as_ref().iter())
1803 .enumerate()
1804 {
1805 let expected = if before == b'.' {
1806 b'A' + index as u8
1807 } else {
1808 before
1809 };
1810 assert_eq!(
1811 after, expected,
1812 "mode={partial_write_mode:?}, index={index}"
1813 );
1814 }
1815 }
1816 }
1817
1818 #[rstest]
1819 #[case::tokio(crate::tokio::Runner::default())]
1820 #[cfg_attr(target_os = "linux", case::iouring(crate::iouring::Runner::default()))]
1821 fn test_full_sync_epoch_is_linearized_with_overlapping_write<R: Runner>(#[case] runner: R)
1822 where
1823 R::Context: Spawner,
1824 {
1825 runner.start(|context| async move {
1826 run_overlapping_barrier(context, false).await;
1827 });
1828 }
1829
1830 #[rstest]
1831 #[case::tokio(crate::tokio::Runner::default())]
1832 #[cfg_attr(target_os = "linux", case::iouring(crate::iouring::Runner::default()))]
1833 fn test_dropped_start_sync_epoch_is_linearized_with_overlapping_write<R: Runner>(
1834 #[case] runner: R,
1835 ) where
1836 R::Context: Spawner,
1837 {
1838 runner.start(|context| async move {
1839 run_overlapping_barrier(context, true).await;
1840 });
1841 }
1842
1843 #[rstest]
1844 #[case::tokio(crate::tokio::Runner::default())]
1845 #[cfg_attr(target_os = "linux", case::iouring(crate::iouring::Runner::default()))]
1846 fn test_faulty_storage_no_faults<R: Runner>(#[case] runner: R)
1847 where
1848 R::Context: Spawner,
1849 {
1850 runner.start(|context| async move {
1851 let h = Harness::new(Config::default());
1852 run_storage_tests(context, h.storage).await;
1853 });
1854 }
1855
1856 #[test]
1857 fn test_probability_endpoints_do_not_consume_randomness() {
1858 let expected = Harness::with_seed(0, Config::default());
1859 let expected = expected.storage.ctx.rng.lock().random::<u64>();
1860
1861 let h = Harness::with_seed(0, Config::default());
1862 for (probability, outcome) in [(probability!(0.0), false), (probability!(1.0), true)] {
1863 assert_eq!(h.storage.ctx.roll(probability), outcome);
1864 for mode in [PartialWriteMode::Prefix, PartialWriteMode::Subset] {
1865 assert_eq!(
1866 h.storage.ctx.retained_bytes(4, (mode, probability)),
1867 [outcome; 4]
1868 );
1869 }
1870 }
1871
1872 assert_eq!(h.storage.ctx.rng.lock().random::<u64>(), expected);
1873 }
1874
1875 #[test]
1876 fn test_prefix_retention_rate_selects_inclusive_prefix() {
1877 let h = Harness::new(Config::default());
1878 let mut observed = [false; 5];
1879 for seed in 0..512 {
1880 let h = Harness::with_seed(seed, Config::default());
1881 let retained = h
1882 .storage
1883 .ctx
1884 .retained_bytes(4, (PartialWriteMode::Prefix, probability!(0.5)));
1885 let prefix_len = retained.iter().take_while(|&&keep| keep).count();
1886 assert!(retained[prefix_len..].iter().all(|&keep| !keep));
1887 observed[prefix_len] = true;
1888 }
1889 assert!(observed.iter().all(|&seen| seen));
1890
1891 assert!(
1892 h.storage
1893 .ctx
1894 .retained_bytes(0, (PartialWriteMode::Prefix, probability!(0.5)))
1895 .is_empty()
1896 );
1897 }
1898
1899 #[tokio::test]
1900 async fn test_write_rejects_offset_overflow_before_retention() {
1901 let h = Harness::new(Config::default().write(WriteConfig {
1902 failure_rate: probability!(1.0),
1903 retention_rate: probability!(0.5),
1904 mode: PartialWriteMode::Subset,
1905 }));
1906 let (blob, _) = h.storage.open("partition", b"blob").await.unwrap();
1907 assert!(matches!(
1908 blob.write_at(u64::MAX, vec![1, 2], WriteOptions::default())
1909 .await,
1910 Err(Error::OffsetOverflow)
1911 ));
1912 let (_, len) = h.inner.open("partition", b"blob").await.unwrap();
1913 assert_eq!(len, 0);
1914 }
1915
1916 #[tokio::test]
1917 async fn test_failed_write_can_retain_every_byte() {
1918 let h = Harness::new(Config::default().write(WriteConfig {
1919 failure_rate: probability!(1.0),
1920 retention_rate: probability!(1.0),
1921 mode: PartialWriteMode::Prefix,
1922 }));
1923 let (blob, _) = h.storage.open("partition", b"blob").await.unwrap();
1924
1925 assert!(matches!(
1926 blob.write_at(0, b"x", WriteOptions::default()).await,
1927 Err(Error::Io(_))
1928 ));
1929 let (durable, len) = h.inner.open("partition", b"blob").await.unwrap();
1930 assert_eq!(len, 1);
1931 assert_eq!(
1932 durable
1933 .read_at(0, 1, ReadOptions::default())
1934 .await
1935 .unwrap()
1936 .coalesce(),
1937 b"x"
1938 );
1939 }
1940
1941 #[tokio::test]
1942 async fn test_faulty_blob_forwards_read_options_without_faults() {
1943 let (inner, recordings) = RecordingContext::new(MemStorage::new());
1944 let rng = Arc::new(Mutex::new(Box::new(StdRng::seed_from_u64(42)) as BoxDynRng));
1945 let storage = Storage::new(inner, rng, Arc::new(RwLock::new(Config::default())));
1946 let (blob, _) = storage.open("partition", b"blob").await.unwrap();
1947 blob.write_at(0, b"data", WriteOptions::default())
1948 .await
1949 .unwrap();
1950 recordings.clear();
1951
1952 let read = blob.read_at(0, 4, ReadOptions::DONT_CACHE).await.unwrap();
1954 assert_eq!(read.coalesce(), b"data");
1955 let read = blob
1956 .read_at_buf(0, 4, IoBufMut::with_capacity(4), ReadOptions::DONT_CACHE)
1957 .await
1958 .unwrap();
1959 assert_eq!(read.coalesce(), b"data");
1960
1961 assert_eq!(
1962 recordings.snapshot().reads,
1963 vec![ReadOptions::DONT_CACHE, ReadOptions::DONT_CACHE]
1964 );
1965 }
1966
1967 #[tokio::test]
1968 async fn test_faulty_storage_sync_always_fails() {
1969 let h = Harness::new(Config::default().sync(probability!(1.0)));
1970
1971 let (blob, _) = h.storage.open("partition", b"test").await.unwrap();
1972 blob.write_at(0, b"data".to_vec(), WriteOptions::default())
1973 .await
1974 .unwrap();
1975
1976 assert!(matches!(blob.sync().await, Err(Error::Io(_))));
1977 }
1978
1979 #[tokio::test]
1980 async fn test_faulty_storage_start_sync_always_fails() {
1981 let h = Harness::new(
1982 Config::default()
1983 .write(WriteConfig {
1984 failure_rate: probability!(0.0),
1985 retention_rate: probability!(1.0),
1986 mode: PartialWriteMode::Prefix,
1987 })
1988 .sync(probability!(1.0)),
1989 );
1990
1991 let (blob, _) = h.storage.open("partition", b"test").await.unwrap();
1992 blob.write_at(0, b"data".to_vec(), WriteOptions::default())
1993 .await
1994 .unwrap();
1995
1996 let result = blob.start_sync().await.await;
1997 assert!(matches!(result, Err(Error::Io(_))));
1998 assert_eq!(h.storage.pending.lock().len(), 1);
1999 }
2000
2001 #[tokio::test]
2002 async fn test_faulty_storage_write_always_fails() {
2003 let h = Harness::new(Config::default().write(WriteConfig {
2004 failure_rate: probability!(1.0),
2005 retention_rate: probability!(0.0),
2006 mode: PartialWriteMode::Prefix,
2007 }));
2008
2009 let (blob, _) = h.storage.open("partition", b"test").await.unwrap();
2010
2011 assert!(matches!(
2012 blob.write_at(0, b"data".to_vec(), WriteOptions::default())
2013 .await,
2014 Err(Error::Io(_))
2015 ));
2016 }
2017
2018 #[tokio::test]
2019 async fn test_faulty_storage_write_at_sync_write_always_fails() {
2020 let h = Harness::new(Config::default().write(WriteConfig {
2021 failure_rate: probability!(1.0),
2022 retention_rate: probability!(0.0),
2023 mode: PartialWriteMode::Prefix,
2024 }));
2025
2026 let (blob, _) = h.storage.open("partition", b"test").await.unwrap();
2027
2028 assert!(matches!(
2029 blob.write_at(0, b"data".to_vec(), WriteOptions::SYNC).await,
2030 Err(Error::Io(_))
2031 ));
2032 }
2033
2034 #[tokio::test]
2035 async fn test_faulty_storage_write_at_sync_failure_is_not_durable() {
2036 let h = Harness::new(Config::default().sync(probability!(1.0)));
2037
2038 let (blob, _) = h.storage.open("partition", b"test").await.unwrap();
2039
2040 assert!(matches!(
2041 blob.write_at(0, b"data".to_vec(), WriteOptions::SYNC).await,
2042 Err(Error::Io(_))
2043 ));
2044
2045 let (_reopened, size) = h.inner.open("partition", b"test").await.unwrap();
2046 assert_eq!(size, 0);
2047 }
2048
2049 #[tokio::test]
2050 async fn test_faulty_storage_empty_write_at_sync_does_not_sync_prior_write() {
2051 let h = Harness::new(Config::default().sync(probability!(1.0)));
2052
2053 let (blob, _) = h.storage.open("partition", b"test").await.unwrap();
2054 blob.write_at(0, b"data".to_vec(), WriteOptions::default())
2055 .await
2056 .unwrap();
2057
2058 blob.write_at(4, Vec::<u8>::new(), WriteOptions::SYNC)
2059 .await
2060 .unwrap();
2061
2062 let (_reopened, size) = h.inner.open("partition", b"test").await.unwrap();
2063 assert_eq!(size, 0);
2064 }
2065
2066 #[tokio::test]
2067 async fn test_empty_unsynced_write_does_not_create_crash_debt() {
2068 let h = Harness::new(Config::default().write(WriteConfig {
2069 failure_rate: probability!(0.0),
2070 retention_rate: probability!(1.0),
2071 mode: PartialWriteMode::Prefix,
2072 }));
2073 let (blob, _) = h.storage.open("partition", b"test").await.unwrap();
2074 blob.write_at(0, Vec::<u8>::new(), WriteOptions::default())
2075 .await
2076 .unwrap();
2077 assert!(h.storage.pending.lock().is_empty());
2078 }
2079
2080 #[tokio::test]
2081 async fn test_faulty_storage_read_always_fails() {
2082 let h = Harness::new(Config::default());
2083
2084 let (blob, _) = h.storage.open("partition", b"test").await.unwrap();
2086 blob.write_at(0, b"data".to_vec(), WriteOptions::default())
2087 .await
2088 .unwrap();
2089 blob.sync().await.unwrap();
2090
2091 h.config.write().read_rate = Some(probability!(1.0));
2093
2094 assert!(matches!(
2095 blob.read_at(0, 4, ReadOptions::default()).await,
2096 Err(Error::Io(_))
2097 ));
2098 }
2099
2100 #[tokio::test]
2101 async fn test_faulty_storage_open_always_fails() {
2102 let h = Harness::new(Config::default().open(probability!(1.0)));
2103
2104 assert!(matches!(
2105 h.storage.open("partition", b"test").await,
2106 Err(Error::Io(_))
2107 ));
2108 }
2109
2110 #[tokio::test]
2111 async fn test_partition_removal_preserves_unrelated_crash_writes() {
2112 let h = Harness::new(Config::default().write(WriteConfig {
2114 failure_rate: probability!(0.0),
2115 retention_rate: probability!(1.0),
2116 mode: PartialWriteMode::Prefix,
2117 }));
2118 let (a, _) = h.storage.open("removed", b"a").await.unwrap();
2119 let (b, _) = h.storage.open("removed", b"b").await.unwrap();
2120 let (kept, _) = h.storage.open("kept", b"blob").await.unwrap();
2121
2122 for (blob, offset, bytes) in [
2124 (&a, 0, b"one"),
2125 (&kept, 0, b"old"),
2126 (&b, 0, b"two"),
2127 (&kept, 3, b"new"),
2128 ] {
2129 blob.write_at(offset, bytes.as_slice(), WriteOptions::default())
2130 .await
2131 .unwrap();
2132 }
2133 drop((a, b, kept));
2134
2135 h.storage.remove("removed", None).await.unwrap();
2137 assert_eq!(h.storage.pending.lock().len(), 2);
2138 h.storage.crash().unwrap();
2139
2140 assert!(matches!(
2142 h.inner.scan("removed").await,
2143 Err(Error::PartitionMissing(_))
2144 ));
2145 let (kept, len) = h.inner.open("kept", b"blob").await.unwrap();
2146 assert_eq!(len, 6);
2147 assert_eq!(
2148 kept.read_at(0, 6, ReadOptions::default())
2149 .await
2150 .unwrap()
2151 .coalesce()
2152 .as_ref(),
2153 b"oldnew"
2154 );
2155 }
2156
2157 #[tokio::test]
2158 async fn test_faulty_storage_remove_always_fails() {
2159 let h = Harness::new(Config::default());
2160
2161 let (blob, _) = h.storage.open("partition", b"test").await.unwrap();
2163 blob.write_at(0, b"data".to_vec(), WriteOptions::default())
2164 .await
2165 .unwrap();
2166 blob.sync().await.unwrap();
2167 drop(blob);
2168
2169 h.config.write().remove_rate = Some(probability!(1.0));
2171
2172 assert!(matches!(
2173 h.storage.remove("partition", Some(b"test")).await,
2174 Err(Error::Io(_))
2175 ));
2176 }
2177
2178 #[tokio::test]
2179 async fn test_faulty_storage_scan_always_fails() {
2180 let h = Harness::new(Config::default());
2181
2182 for i in 0..3 {
2184 let name = format!("blob{i}");
2185 let (blob, _) = h.storage.open("partition", name.as_bytes()).await.unwrap();
2186 blob.write_at(0, b"data".to_vec(), WriteOptions::default())
2187 .await
2188 .unwrap();
2189 blob.sync().await.unwrap();
2190 }
2191
2192 h.config.write().scan_rate = Some(probability!(1.0));
2194
2195 assert!(matches!(
2196 h.storage.scan("partition").await,
2197 Err(Error::Io(_))
2198 ));
2199 }
2200
2201 #[tokio::test]
2202 async fn test_faulty_storage_determinism() {
2203 async fn run_ops(seed: u64, rate: Probability) -> Vec<bool> {
2204 let h = Harness::with_seed(seed, Config::default().open(rate));
2205 let mut results = Vec::new();
2206 for i in 0..20 {
2207 let name = format!("blob{i}");
2208 results.push(h.storage.open("partition", name.as_bytes()).await.is_ok());
2209 }
2210 results
2211 }
2212
2213 let results1 = run_ops(42, probability!(0.5)).await;
2214 let results2 = run_ops(42, probability!(0.5)).await;
2215 assert_eq!(results1, results2, "Same seed should produce same results");
2216
2217 let results3 = run_ops(999, probability!(0.5)).await;
2218 assert_ne!(
2219 results1, results3,
2220 "Different seeds should produce different results"
2221 );
2222 }
2223
2224 #[tokio::test]
2225 async fn test_faulty_storage_rate_for() {
2226 let config = Config::default()
2227 .open(probability!(0.1))
2228 .write(WriteConfig {
2229 failure_rate: probability!(0.3),
2230 retention_rate: probability!(0.4),
2231 mode: PartialWriteMode::Subset,
2232 })
2233 .resize(ResizeConfig {
2234 failure_rate: probability!(0.7),
2235 partial_rate: probability!(0.8),
2236 })
2237 .sync(probability!(0.9));
2238
2239 assert_eq!(config.rate_for(Op::Open), probability!(0.1));
2240 assert_eq!(config.rate_for(Op::Write), probability!(0.3));
2241 assert_eq!(config.rate_for(Op::Resize), probability!(0.7));
2242 assert_eq!(config.rate_for(Op::Sync), probability!(0.9));
2243 }
2244
2245 #[tokio::test]
2246 async fn test_faulty_storage_dynamic_config() {
2247 let h = Harness::new(Config::default());
2248
2249 let (blob, _) = h.storage.open("partition", b"test").await.unwrap();
2250 blob.sync().await.unwrap();
2251
2252 h.config.write().sync_rate = Some(probability!(1.0));
2253 assert!(matches!(blob.sync().await, Err(Error::Io(_))));
2254
2255 h.config.write().sync_rate = Some(probability!(0.0));
2256 blob.sync().await.unwrap();
2257 }
2258
2259 #[tokio::test]
2260 async fn test_write_retention_is_snapshotted_and_replayed_in_order() {
2261 let h = Harness::new(Config::default().write(WriteConfig {
2262 failure_rate: probability!(0.0),
2263 retention_rate: probability!(1.0),
2264 mode: PartialWriteMode::Prefix,
2265 }));
2266 let (blob, _) = h.storage.open("partition", b"test").await.unwrap();
2267 blob.write_at(0, b"........", WriteOptions::SYNC)
2268 .await
2269 .unwrap();
2270
2271 blob.write_at(0, b"AB", WriteOptions::default())
2272 .await
2273 .unwrap();
2274 h.config.write().write_rate = None;
2275 blob.write_at(2, b"CD", WriteOptions::default())
2276 .await
2277 .unwrap();
2278 h.config.write().write_rate = Some(WriteConfig {
2279 failure_rate: probability!(0.0),
2280 retention_rate: probability!(1.0),
2281 mode: PartialWriteMode::Prefix,
2282 });
2283 blob.write_at(1, b"XY", WriteOptions::default())
2284 .await
2285 .unwrap();
2286 h.config.write().write_rate = None;
2287
2288 h.storage.crash().unwrap();
2289 let (durable, _) = h.inner.open("partition", b"test").await.unwrap();
2290 assert_eq!(
2291 durable
2292 .read_at(0, 8, ReadOptions::default())
2293 .await
2294 .unwrap()
2295 .coalesce(),
2296 b"AXY....."
2297 );
2298
2299 durable.write_at(0, b"Q", WriteOptions::SYNC).await.unwrap();
2300 h.storage.crash().unwrap();
2301 let (durable, _) = h.inner.open("partition", b"test").await.unwrap();
2302 assert_eq!(
2303 durable
2304 .read_at(0, 8, ReadOptions::default())
2305 .await
2306 .unwrap()
2307 .coalesce(),
2308 b"QXY....."
2309 );
2310 }
2311
2312 #[tokio::test]
2313 async fn test_random_crash_write_is_deterministic_and_inclusive() {
2314 let first = run_random_crash(12345, 64).await;
2315 let second = run_random_crash(12345, 64).await;
2316 let different = run_random_crash(54321, 64).await;
2317 assert_eq!(first, second);
2318 assert_ne!(first, different);
2319 assert!(first.contains(&0));
2320 assert!(first.contains(&1));
2321
2322 let mut saw_empty = false;
2323 let mut saw_full = false;
2324 for seed in 0..64 {
2325 match run_random_crash(seed, 1).await.as_slice() {
2326 [0] => saw_empty = true,
2327 [1] => saw_full = true,
2328 other => panic!("unexpected one-byte crash result: {other:?}"),
2329 }
2330 }
2331 assert!(saw_empty && saw_full);
2332 }
2333
2334 #[tokio::test]
2335 async fn failed_partial_write_does_not_barrier_prior_crash_writes() {
2336 for partial_write_mode in [PartialWriteMode::Prefix, PartialWriteMode::Subset] {
2337 let mut saw_old = false;
2338 let mut saw_new = false;
2339 for seed in 0..64 {
2340 let h = Harness::with_seed(
2341 seed,
2342 Config::default().write(WriteConfig {
2343 failure_rate: probability!(0.0),
2344 retention_rate: probability!(0.5),
2345 mode: PartialWriteMode::Subset,
2346 }),
2347 );
2348 let (blob, _) = h.storage.open("partition", b"test").await.unwrap();
2349 blob.write_at(0, b"......", WriteOptions::SYNC)
2350 .await
2351 .unwrap();
2352 blob.write_at(0, b"AAAA", WriteOptions::default())
2353 .await
2354 .unwrap();
2355 {
2356 let mut config = h.config.write();
2357 config.write_rate = Some(WriteConfig {
2358 failure_rate: probability!(1.0),
2359 retention_rate: probability!(0.5),
2360 mode: partial_write_mode,
2361 });
2362 }
2363
2364 assert!(
2365 blob.write_at(4, b"XY", WriteOptions::default())
2366 .await
2367 .is_err()
2368 );
2369 h.storage.crash().unwrap();
2370
2371 let (durable, _) = h.inner.open("partition", b"test").await.unwrap();
2372 let durable = durable
2373 .read_at(0, 4, ReadOptions::default())
2374 .await
2375 .unwrap()
2376 .coalesce();
2377 saw_old |= durable.as_ref().contains(&b'.');
2378 saw_new |= durable.as_ref().contains(&b'A');
2379 }
2380 assert!(saw_old && saw_new);
2381 }
2382 }
2383
2384 #[tokio::test]
2385 async fn failed_partial_resize_does_not_barrier_prior_crash_writes() {
2386 let mut saw_old = false;
2387 let mut saw_new = false;
2388 for seed in 0..64 {
2389 let h = Harness::with_seed(
2390 seed,
2391 Config::default().write(WriteConfig {
2392 failure_rate: probability!(0.0),
2393 retention_rate: probability!(0.5),
2394 mode: PartialWriteMode::Subset,
2395 }),
2396 );
2397 let (blob, _) = h.storage.open("partition", b"test").await.unwrap();
2398 blob.write_at(0, b"......", WriteOptions::SYNC)
2399 .await
2400 .unwrap();
2401 blob.write_at(0, b"AAAA", WriteOptions::default())
2402 .await
2403 .unwrap();
2404 {
2405 let mut config = h.config.write();
2406 config.resize_rate = Some(ResizeConfig {
2407 failure_rate: probability!(1.0),
2408 partial_rate: probability!(1.0),
2409 });
2410 }
2411
2412 assert!(blob.resize(8).await.is_err());
2413 h.storage.crash().unwrap();
2414
2415 let (durable, len) = h.inner.open("partition", b"test").await.unwrap();
2416 assert_eq!(len, 7);
2417 let durable = durable
2418 .read_at(0, 4, ReadOptions::default())
2419 .await
2420 .unwrap()
2421 .coalesce();
2422 saw_old |= durable.as_ref().contains(&b'.');
2423 saw_new |= durable.as_ref().contains(&b'A');
2424 }
2425 assert!(saw_old && saw_new);
2426 }
2427
2428 #[tokio::test]
2429 async fn test_crash_journal_clears_only_after_completed_durability() {
2430 let h = Harness::new(Config::default().write(WriteConfig {
2431 failure_rate: probability!(0.0),
2432 retention_rate: probability!(1.0),
2433 mode: PartialWriteMode::Prefix,
2434 }));
2435 let (blob, _) = h.storage.open("partition", b"test").await.unwrap();
2436
2437 blob.write_at(0, b"a", WriteOptions::default())
2438 .await
2439 .unwrap();
2440 assert_eq!(h.storage.pending.lock().len(), 1);
2441 h.config.write().sync_rate = Some(probability!(1.0));
2442 assert!(matches!(blob.sync().await, Err(Error::Io(_))));
2443 assert_eq!(h.storage.pending.lock().len(), 1);
2444
2445 h.config.write().sync_rate = None;
2446 blob.sync().await.unwrap();
2447 assert!(h.storage.pending.lock().is_empty());
2448
2449 let (other, _) = h.storage.open("partition", b"other").await.unwrap();
2450 blob.write_at(0, b"b", WriteOptions::default())
2451 .await
2452 .unwrap();
2453 other
2454 .write_at(0, b"x", WriteOptions::default())
2455 .await
2456 .unwrap();
2457 assert_eq!(h.storage.pending.lock().len(), 2);
2458 blob.sync().await.unwrap();
2459 assert_eq!(h.storage.pending.lock().len(), 1);
2460 other.sync().await.unwrap();
2461 assert!(h.storage.pending.lock().is_empty());
2462
2463 blob.write_at(0, b"b", WriteOptions::default())
2464 .await
2465 .unwrap();
2466 assert_eq!(h.storage.pending.lock().len(), 1);
2467 blob.start_sync().await.await.unwrap();
2468 assert!(h.storage.pending.lock().is_empty());
2469
2470 blob.write_at(0, b"c", WriteOptions::default())
2471 .await
2472 .unwrap();
2473 assert_eq!(h.storage.pending.lock().len(), 1);
2474 blob.write_at(0, b"d", WriteOptions::SYNC).await.unwrap();
2475 assert!(h.storage.pending.lock().is_empty());
2476 }
2477
2478 #[tokio::test]
2479 async fn test_sync_failure_journals_the_successful_inner_write() {
2480 let h = Harness::new(
2481 Config::default()
2482 .write(WriteConfig {
2483 failure_rate: probability!(0.0),
2484 retention_rate: probability!(1.0),
2485 mode: PartialWriteMode::Prefix,
2486 })
2487 .sync(probability!(1.0)),
2488 );
2489 let (blob, _) = h.storage.open("partition", b"test").await.unwrap();
2490 assert!(matches!(
2491 blob.write_at(0, b"data", WriteOptions::SYNC).await,
2492 Err(Error::Io(_))
2493 ));
2494 assert_eq!(h.storage.pending.lock().len(), 1);
2495
2496 h.storage.crash().unwrap();
2497 let (durable, len) = h.inner.open("partition", b"test").await.unwrap();
2498 assert_eq!(len, 4);
2499 assert_eq!(
2500 durable
2501 .read_at(0, 4, ReadOptions::default())
2502 .await
2503 .unwrap()
2504 .coalesce(),
2505 b"data"
2506 );
2507 }
2508
2509 #[tokio::test]
2510 async fn test_faulty_storage_zero_write_retention_preserves_nothing() {
2511 let h = Harness::new(Config::default().write(WriteConfig {
2512 failure_rate: probability!(1.0),
2513 retention_rate: probability!(0.0),
2514 mode: PartialWriteMode::Prefix,
2515 }));
2516
2517 let (blob, _) = h.storage.open("partition", b"test").await.unwrap();
2518 let data = b"hello world".to_vec();
2519 let result = blob
2520 .write_at(0, data.clone(), WriteOptions::default())
2521 .await;
2522
2523 assert!(matches!(result, Err(Error::Io(_))));
2524
2525 let (_, size) = h.inner.open("partition", b"test").await.unwrap();
2526 assert_eq!(size, 0);
2527 }
2528
2529 #[tokio::test]
2530 async fn test_faulty_storage_partial_write_subset_can_be_non_prefix() {
2531 let original = b"abcdefghijklmnop";
2532 let replacement = b"ABCDEFGHIJKLMNOP";
2533 let observed = run_subset_overwrite(42, original, replacement).await;
2534
2535 let retained: Vec<_> = observed
2536 .iter()
2537 .zip(original)
2538 .zip(replacement)
2539 .map(|((&actual, &old), &new)| {
2540 assert!(actual == old || actual == new);
2541 actual == new
2542 })
2543 .collect();
2544 assert!(retained.iter().any(|&keep| keep));
2545 assert!(retained.iter().any(|&keep| !keep));
2546
2547 let mut omitted = false;
2548 let non_prefix = retained.into_iter().any(|keep| {
2549 if keep {
2550 omitted
2551 } else {
2552 omitted = true;
2553 false
2554 }
2555 });
2556 assert!(non_prefix, "expected a retained byte after an omitted byte");
2557 }
2558
2559 #[tokio::test]
2560 async fn test_faulty_storage_write_retention_rate_endpoints() {
2561 for (retention_rate, expected) in [
2562 (probability!(0.0), b"old".as_slice()),
2563 (probability!(1.0), b"new".as_slice()),
2564 ] {
2565 for mode in [PartialWriteMode::Prefix, PartialWriteMode::Subset] {
2566 let failed = Harness::new(Config::default().write(WriteConfig {
2567 failure_rate: probability!(1.0),
2568 retention_rate,
2569 mode,
2570 }));
2571 let (blob, _) = failed.storage.open("partition", b"failed").await.unwrap();
2572 blob.write_at(0, b"new", WriteOptions::SYNC)
2573 .await
2574 .unwrap_err();
2575 let (durable, len) = failed.inner.open("partition", b"failed").await.unwrap();
2576 if retention_rate.is_one() {
2577 assert_eq!(len, 3);
2578 assert_eq!(
2579 durable
2580 .read_at(0, 3, ReadOptions::default())
2581 .await
2582 .unwrap()
2583 .coalesce(),
2584 expected
2585 );
2586 } else {
2587 assert_eq!(len, 0);
2588 }
2589
2590 let crashed = Harness::new(Config::default().write(WriteConfig {
2591 failure_rate: probability!(0.0),
2592 retention_rate,
2593 mode,
2594 }));
2595 let (blob, _) = crashed.storage.open("partition", b"crashed").await.unwrap();
2596 blob.write_at(0, b"old", WriteOptions::SYNC).await.unwrap();
2597 blob.write_at(0, b"new", WriteOptions::default())
2598 .await
2599 .unwrap();
2600 crashed.storage.crash().unwrap();
2601 let (durable, len) = crashed.inner.open("partition", b"crashed").await.unwrap();
2602 assert_eq!(len, 3);
2603 assert_eq!(
2604 durable
2605 .read_at(0, 3, ReadOptions::default())
2606 .await
2607 .unwrap()
2608 .coalesce(),
2609 expected
2610 );
2611 }
2612 }
2613 }
2614
2615 #[tokio::test]
2616 async fn test_faulty_storage_partial_write_subset_same_seed_is_deterministic() {
2617 let original = vec![0x11; 64];
2618 let replacement = vec![0xAA; 64];
2619
2620 let first = run_subset_overwrite(12345, &original, &replacement).await;
2621 let second = run_subset_overwrite(12345, &original, &replacement).await;
2622
2623 assert_eq!(first, second);
2624 assert!(first.contains(&0x11));
2625 assert!(first.contains(&0xAA));
2626 }
2627
2628 #[tokio::test]
2629 async fn test_faulty_storage_partial_resize_grow() {
2630 let h = Harness::new(Config::default().resize(ResizeConfig {
2631 failure_rate: probability!(1.0),
2632 partial_rate: probability!(1.0),
2633 }));
2634
2635 let (blob, initial_size) = h.storage.open("partition", b"test").await.unwrap();
2636 assert_eq!(initial_size, 0);
2637
2638 let target_size = 100u64;
2639 let result = blob.resize(target_size).await;
2640
2641 assert!(matches!(result, Err(Error::Io(_))));
2642 h.storage.crash().unwrap();
2643
2644 let (_, actual_size) = h.inner.open("partition", b"test").await.unwrap();
2645 assert!(
2646 actual_size > 0 && actual_size < target_size,
2647 "Expected partial resize: size {actual_size} should be between 0 and {target_size}"
2648 );
2649 }
2650
2651 #[tokio::test]
2652 async fn test_faulty_storage_partial_resize_shrink() {
2653 let h = Harness::new(Config::default());
2654
2655 let (blob, _) = h.storage.open("partition", b"test").await.unwrap();
2656 blob.resize(100).await.unwrap();
2657 blob.sync().await.unwrap();
2658
2659 {
2660 let mut cfg = h.config.write();
2661 cfg.resize_rate = Some(ResizeConfig {
2662 failure_rate: probability!(1.0),
2663 partial_rate: probability!(1.0),
2664 });
2665 }
2666
2667 let target_size = 10u64;
2668 let result = blob.resize(target_size).await;
2669
2670 assert!(matches!(result, Err(Error::Io(_))));
2671 h.storage.crash().unwrap();
2672
2673 let (_, actual_size) = h.inner.open("partition", b"test").await.unwrap();
2674 assert!(
2675 actual_size > target_size && actual_size < 100,
2676 "Expected partial shrink: size {actual_size} should be between {target_size} and 100"
2677 );
2678 }
2679
2680 #[tokio::test]
2681 async fn test_faulty_storage_partial_resize_disabled() {
2682 let h = Harness::new(Config::default().resize(ResizeConfig {
2683 failure_rate: probability!(1.0),
2684 partial_rate: probability!(0.0),
2685 }));
2686
2687 let (blob, _) = h.storage.open("partition", b"test").await.unwrap();
2688 let result = blob.resize(100).await;
2689
2690 assert!(matches!(result, Err(Error::Io(_))));
2691
2692 let (_, size) = h.inner.open("partition", b"test").await.unwrap();
2693 assert_eq!(size, 0, "Expected no resize when partial rate is 0");
2694 }
2695
2696 #[tokio::test]
2697 async fn test_faulty_storage_partial_resize_same_size() {
2698 let h = Harness::new(Config::default().resize(ResizeConfig {
2699 failure_rate: probability!(1.0),
2700 partial_rate: probability!(1.0),
2701 }));
2702
2703 let (blob, _) = h.storage.open("partition", b"test").await.unwrap();
2704 let result = blob.resize(0).await;
2705
2706 assert!(matches!(result, Err(Error::Io(_))));
2707
2708 let (_, size) = h.inner.open("partition", b"test").await.unwrap();
2709 assert_eq!(size, 0);
2710 }
2711
2712 #[tokio::test]
2713 async fn test_faulty_storage_partial_resize_after_write_extends() {
2714 let h = Harness::new(Config::default());
2715
2716 let (blob, initial_size) = h.storage.open("partition", b"test").await.unwrap();
2717 assert_eq!(initial_size, 0);
2718
2719 blob.write_at(0, vec![0xABu8; 50], WriteOptions::default())
2720 .await
2721 .unwrap();
2722 blob.sync().await.unwrap();
2723
2724 let (_, size_after_write) = h.inner.open("partition", b"test").await.unwrap();
2725 assert_eq!(size_after_write, 50);
2726
2727 {
2728 let mut cfg = h.config.write();
2729 cfg.resize_rate = Some(ResizeConfig {
2730 failure_rate: probability!(1.0),
2731 partial_rate: probability!(1.0),
2732 });
2733 }
2734
2735 let target_size = 10u64;
2736 let result = blob.resize(target_size).await;
2737
2738 assert!(matches!(result, Err(Error::Io(_))));
2739 h.storage.crash().unwrap();
2740
2741 let (_, actual_size) = h.inner.open("partition", b"test").await.unwrap();
2742 assert!(
2743 actual_size > target_size && actual_size < 50,
2744 "Expected partial shrink from 50: size {actual_size} should be between {target_size} and 50"
2745 );
2746 }
2747
2748 #[tokio::test]
2749 async fn test_faulty_storage_partial_resize_one_byte_difference() {
2750 let h = Harness::new(Config::default().resize(ResizeConfig {
2751 failure_rate: probability!(1.0),
2752 partial_rate: probability!(1.0),
2753 }));
2754
2755 let (blob, _) = h.storage.open("partition", b"test").await.unwrap();
2756 let result = blob.resize(1).await;
2757
2758 assert!(matches!(result, Err(Error::Io(_))));
2759
2760 let (_, size) = h.inner.open("partition", b"test").await.unwrap();
2761 assert_eq!(size, 0);
2762 }
2763}