Skip to main content

commonware_runtime/storage/
faulty.rs

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