Skip to main content

commonware_runtime/utils/buffer/
mod.rs

1//! Buffers for reading and writing to [crate::Blob]s.
2
3use futures::future::{BoxFuture, FutureExt as _, Shared};
4
5pub mod paged;
6mod read;
7mod tip;
8mod write;
9
10pub use read::Read;
11pub use write::Write;
12
13/// A shared sync result.
14///
15/// Handles returned by [Completion::handle] are detached observers of the same shared result:
16/// dropping one neither cancels the underlying sync nor consumes its result, and every
17/// observer sees the same outcome.
18#[derive(Clone)]
19struct Completion(Shared<BoxFuture<'static, Result<(), crate::Error>>>);
20
21impl Completion {
22    /// Return a handle for the sync result.
23    fn handle(&self) -> crate::Handle<()> {
24        crate::Handle::from_future(self.0.clone())
25    }
26
27    /// Wait for the sync result.
28    async fn wait(&self) -> Result<(), crate::Error> {
29        self.0.clone().await
30    }
31}
32
33impl From<crate::Handle<()>> for Completion {
34    fn from(handle: crate::Handle<()>) -> Self {
35        Self(handle.boxed().shared())
36    }
37}
38
39/// Tracks whether blob mutations still need a sync.
40///
41/// Callers rely on three properties:
42/// - Every operation that mutates the blob first waits for an in-flight sync, so a started
43///   sync's coverage is never disturbed by later writes.
44/// - [SyncState::start_sync] on a [SyncState::Pending] state returns the in-flight sync's
45///   handle (completed syncs resolve immediately), so re-requesting a sync is a cheap way to
46///   observe outstanding work.
47/// - A failure is never lost: every handle cloned from the shared completion reports it, and
48///   an unobserved failure surfaces from [SyncState::wait_for_pending] on the next operation,
49///   which also marks the state [SyncState::Dirty] since the mutations still need durability.
50enum SyncState {
51    // No unsynced mutations.
52    Clean,
53    // Unsynced mutations need a sync.
54    Dirty,
55    // A started sync is in flight.
56    Pending(Completion),
57}
58
59impl SyncState {
60    /// Mark a new unsynced mutation.
61    fn mark_dirty(&mut self) {
62        assert!(
63            !matches!(self, Self::Pending(_)),
64            "pending sync must be joined before marking dirty"
65        );
66        *self = Self::Dirty;
67    }
68
69    /// Wait for an in-flight sync before reusing or mutating the blob.
70    async fn wait_for_pending(&mut self) -> Result<(), crate::Error> {
71        let Self::Pending(pending) = self else {
72            return Ok(());
73        };
74        match pending.wait().await {
75            Ok(()) => {
76                *self = Self::Clean;
77                Ok(())
78            }
79            Err(err) => {
80                // The sync failed, so the pending mutations still need durability.
81                *self = Self::Dirty;
82                Err(err)
83            }
84        }
85    }
86
87    /// Write data that will require a later sync.
88    async fn write_at(
89        &mut self,
90        blob: &impl crate::Blob,
91        offset: u64,
92        bufs: impl Into<crate::IoBufs> + Send,
93    ) -> Result<(), crate::Error> {
94        self.wait_for_pending().await?;
95        blob.write_at(offset, bufs).await?;
96        self.mark_dirty();
97        Ok(())
98    }
99
100    /// Write data and make it durable before returning.
101    async fn write_at_sync(
102        &mut self,
103        blob: &impl crate::Blob,
104        offset: u64,
105        bufs: impl Into<crate::IoBufs> + Send,
106    ) -> Result<(), crate::Error> {
107        self.wait_for_pending().await?;
108        match self {
109            Self::Dirty => {
110                // Earlier mutations need a full durability barrier too.
111                blob.write_at(offset, bufs).await?;
112                blob.sync().await?;
113                *self = Self::Clean;
114                Ok(())
115            }
116            Self::Clean => {
117                // If this fails, a later sync must still cover the attempted write.
118                self.mark_dirty();
119                blob.write_at_sync(offset, bufs).await?;
120                *self = Self::Clean;
121                Ok(())
122            }
123            Self::Pending(_) => unreachable!("pending sync waited above"),
124        }
125    }
126
127    /// Resize the blob and require a later sync.
128    async fn resize(&mut self, blob: &impl crate::Blob, len: u64) -> Result<(), crate::Error> {
129        self.wait_for_pending().await?;
130        blob.resize(len).await?;
131        self.mark_dirty();
132        Ok(())
133    }
134
135    /// Make all pending mutations durable before returning.
136    async fn sync(&mut self, blob: &impl crate::Blob) -> Result<(), crate::Error> {
137        self.wait_for_pending().await?;
138        if matches!(self, Self::Clean) {
139            return Ok(());
140        }
141        blob.sync().await?;
142        *self = Self::Clean;
143        Ok(())
144    }
145
146    /// Start making pending mutations durable and return a handle for completion.
147    async fn start_sync(&mut self, blob: &impl crate::Blob) -> crate::Handle<()> {
148        match self {
149            Self::Clean => crate::Handle::ready(Ok(())),
150            Self::Dirty => {
151                // Store a shared completion so repeated calls observe the same sync.
152                let pending = Completion::from(blob.start_sync().await);
153                let handle = pending.handle();
154                *self = Self::Pending(pending);
155                handle
156            }
157            Self::Pending(pending) => pending.handle(),
158        }
159    }
160}
161
162#[cfg(test)]
163mod tests {
164    use super::*;
165    use crate::{
166        deterministic,
167        mocks::{next_pending_sync, DelayedSyncBlob},
168        Blob as _, BufMut, Error, Handle, IoBufMut, IoBufs, IoBufsMut, Runner, Storage,
169    };
170    use commonware_macros::test_traced;
171    use commonware_utils::{sync::Mutex, NZUsize};
172    use std::sync::Arc;
173
174    #[derive(Default)]
175    struct RangeSyncState {
176        /// All data currently stored in the blob.
177        ///
178        /// This includes every durable byte plus any newer bytes that have not
179        /// been made durable yet.
180        data: Vec<u8>,
181
182        /// Prefix/ranges of `data` that would survive a crash.
183        durable: Vec<u8>,
184
185        /// Number of write operations.
186        writes: usize,
187
188        /// Number of full sync barriers.
189        full_syncs: usize,
190
191        /// Number of range-scoped write syncs.
192        range_syncs: usize,
193    }
194
195    /// Test blob with separate visible and durable state.
196    ///
197    /// Writes and resizes only update `data`. `write_at_sync` updates `data`
198    /// and then copies only that submitted range into `durable`. `sync` copies all
199    /// of `data` to `durable`. This lets tests assert that `Write::sync` uses range
200    /// sync only when no earlier unsynced mutation needs a full durability barrier.
201    #[derive(Clone)]
202    pub struct SyncTrackingBlob {
203        state: Arc<Mutex<RangeSyncState>>,
204    }
205
206    impl SyncTrackingBlob {
207        pub fn new() -> Self {
208            Self {
209                state: Arc::new(Mutex::new(RangeSyncState::default())),
210            }
211        }
212
213        pub fn snapshot(&self) -> (Vec<u8>, usize, usize, usize) {
214            let state = self.state.lock();
215            (
216                state.durable.clone(),
217                state.writes,
218                state.full_syncs,
219                state.range_syncs,
220            )
221        }
222
223        pub fn size(&self) -> u64 {
224            self.state.lock().data.len() as u64
225        }
226
227        fn write(data: &mut Vec<u8>, offset: u64, buf: &[u8]) -> Result<(), Error> {
228            let start = usize::try_from(offset).map_err(|_| Error::OffsetOverflow)?;
229            let end = start.checked_add(buf.len()).ok_or(Error::OffsetOverflow)?;
230            if end > data.len() {
231                data.resize(end, 0);
232            }
233            data[start..end].copy_from_slice(buf);
234            Ok(())
235        }
236    }
237
238    impl crate::Blob for SyncTrackingBlob {
239        async fn read_at(&self, offset: u64, len: usize) -> Result<IoBufsMut, Error> {
240            self.read_at_buf(offset, len, IoBufMut::default()).await
241        }
242
243        async fn read_at_buf(
244            &self,
245            offset: u64,
246            len: usize,
247            buf: impl Into<IoBufsMut> + Send,
248        ) -> Result<IoBufsMut, Error> {
249            let start = usize::try_from(offset).map_err(|_| Error::OffsetOverflow)?;
250            let end = start.checked_add(len).ok_or(Error::OffsetOverflow)?;
251            let state = self.state.lock();
252            if end > state.data.len() {
253                return Err(Error::BlobInsufficientLength);
254            }
255
256            let mut out = buf.into();
257            out.put_slice(&state.data[start..end]);
258            Ok(out)
259        }
260
261        async fn write_at(&self, offset: u64, buf: impl Into<IoBufs> + Send) -> Result<(), Error> {
262            let buf = buf.into().coalesce();
263            let mut state = self.state.lock();
264            Self::write(&mut state.data, offset, buf.as_ref())?;
265            state.writes += 1;
266            Ok(())
267        }
268
269        async fn write_at_sync(
270            &self,
271            offset: u64,
272            buf: impl Into<IoBufs> + Send,
273        ) -> Result<(), Error> {
274            let buf = buf.into().coalesce();
275            let mut state = self.state.lock();
276            Self::write(&mut state.data, offset, buf.as_ref())?;
277            Self::write(&mut state.durable, offset, buf.as_ref())?;
278            state.writes += 1;
279            state.range_syncs += 1;
280            Ok(())
281        }
282
283        async fn resize(&self, len: u64) -> Result<(), Error> {
284            let len = usize::try_from(len).map_err(|_| Error::OffsetOverflow)?;
285            self.state.lock().data.resize(len, 0);
286            Ok(())
287        }
288
289        async fn sync(&self) -> Result<(), Error> {
290            let mut state = self.state.lock();
291            state.durable = state.data.clone();
292            state.full_syncs += 1;
293            Ok(())
294        }
295
296        async fn start_sync(&self) -> Handle<()> {
297            Handle::ready(self.sync().await)
298        }
299    }
300
301    #[test_traced]
302    fn test_read_basic() {
303        let executor = deterministic::Runner::default();
304        executor.start(|context| async move {
305            // Test basic buffered reading functionality with sequential reads
306            let data = b"Hello, world! This is a test.";
307            let (blob, size) = context.open("partition", b"test").await.unwrap();
308            assert_eq!(size, 0);
309            blob.write_at(0, data).await.unwrap();
310            let size = data.len() as u64;
311
312            // Create a buffered reader with small buffer to test refilling
313            let mut reader = Read::from_pooler(&context, blob, size, NZUsize!(10));
314
315            // Read some data
316            let read = reader.read(5).await.unwrap().coalesce();
317            assert_eq!(read.as_ref(), b"Hello");
318
319            // Read more data that requires a buffer refill
320            let read = reader.read(14).await.unwrap().coalesce();
321            assert_eq!(read.as_ref(), b", world! This ");
322
323            // Verify position tracking
324            assert_eq!(reader.position(), 19);
325
326            // Read the remaining data
327            let read = reader.read(7).await.unwrap().coalesce();
328            assert_eq!(read.as_ref(), b"is a te");
329
330            // Attempt to read beyond the end should fail
331            let result = reader.read(5).await;
332            assert!(matches!(result, Err(Error::BlobInsufficientLength)));
333        });
334    }
335
336    #[test_traced]
337    fn test_read_cross_boundary() {
338        let executor = deterministic::Runner::default();
339        executor.start(|context| async move {
340            // Test reading data that spans multiple buffer refills
341            let data = b"ABCDEFGHIJKLMNOPQRSTUVWXYZ";
342            let (blob, size) = context.open("partition", b"test").await.unwrap();
343            assert_eq!(size, 0);
344            blob.write_at(0, data).await.unwrap();
345            let size = data.len() as u64;
346
347            // Use a buffer smaller than the total data size
348            let mut reader = Read::from_pooler(&context, blob, size, NZUsize!(10));
349
350            // Read data that crosses buffer boundaries
351            let read = reader.read(15).await.unwrap().coalesce();
352            assert_eq!(read.as_ref(), b"ABCDEFGHIJKLMNO");
353
354            // Verify position tracking
355            assert_eq!(reader.position(), 15);
356
357            // Read the remaining data
358            let read = reader.read(11).await.unwrap().coalesce();
359            assert_eq!(read.as_ref(), b"PQRSTUVWXYZ");
360
361            // Verify we're at the end
362            assert_eq!(reader.position(), 26);
363            assert_eq!(reader.blob_remaining(), 0);
364        });
365    }
366
367    // Regression test for https://github.com/commonwarexyz/monorepo/issues/1348
368    #[test_traced]
369    fn test_read_to_end_then_rewind_and_read_again() {
370        let executor = deterministic::Runner::default();
371        executor.start(|context| async move {
372            let data = b"ABCDEFGHIJKLMNOPQRSTUVWXYZ";
373            let (blob, size) = context.open("partition", b"test").await.unwrap();
374            assert_eq!(size, 0);
375            blob.write_at(0, data).await.unwrap();
376            let size = data.len() as u64;
377
378            let mut reader = Read::from_pooler(&context, blob, size, NZUsize!(20));
379
380            // Read data that crosses buffer boundaries
381            let read = reader.read(21).await.unwrap().coalesce();
382            assert_eq!(read.as_ref(), b"ABCDEFGHIJKLMNOPQRSTU");
383
384            // Verify position tracking
385            assert_eq!(reader.position(), 21);
386
387            // Read the remaining data
388            let read = reader.read(5).await.unwrap().coalesce();
389            assert_eq!(read.as_ref(), b"VWXYZ");
390
391            // Rewind and read again
392            reader.seek_to(0).unwrap();
393            let read = reader.read(21).await.unwrap().coalesce();
394            assert_eq!(read.as_ref(), b"ABCDEFGHIJKLMNOPQRSTU");
395        });
396    }
397
398    #[test_traced]
399    fn test_read_with_known_size() {
400        let executor = deterministic::Runner::default();
401        executor.start(|context| async move {
402            // Test reader behavior with known blob size limits
403            let data = b"This is a test with known size limitations.";
404            let (blob, size) = context.open("partition", b"test").await.unwrap();
405            assert_eq!(size, 0);
406            blob.write_at(0, data).await.unwrap();
407            let size = data.len() as u64;
408
409            // Create a buffered reader with buffer smaller than total data
410            let mut reader = Read::from_pooler(&context, blob, size, NZUsize!(10));
411
412            // Check initial remaining bytes
413            assert_eq!(reader.blob_remaining(), size);
414
415            // Read partial data
416            let read = reader.read(5).await.unwrap().coalesce();
417            assert_eq!(read.as_ref(), b"This ");
418
419            // Check remaining bytes after partial read
420            assert_eq!(reader.blob_remaining(), size - 5);
421
422            // Read exactly up to the size limit
423            let read = reader.read((size - 5) as usize).await.unwrap().coalesce();
424            assert_eq!(read.as_ref(), b"is a test with known size limitations.");
425
426            // Verify we're at the end
427            assert_eq!(reader.blob_remaining(), 0);
428
429            // Reading beyond the end should fail
430            let result = reader.read(1).await;
431            assert!(matches!(result, Err(Error::BlobInsufficientLength)));
432        });
433    }
434
435    #[test_traced]
436    fn test_read_oversized_request_does_not_consume_buffered_bytes() {
437        let executor = deterministic::Runner::default();
438        executor.start(|context| async move {
439            let data = b"abcdefghij";
440            let (blob, size) = context
441                .open("partition", b"double-count-regression")
442                .await
443                .unwrap();
444            assert_eq!(size, 0);
445            blob.write_at(0, data).await.unwrap();
446
447            let mut reader = Read::from_pooler(&context, blob, data.len() as u64, NZUsize!(8));
448
449            // Fill the internal buffer and consume most of it (2 bytes remain buffered).
450            let first = reader.read(6).await.unwrap().coalesce();
451            assert_eq!(first.as_ref(), b"abcdef");
452            assert_eq!(reader.position(), 6);
453
454            // Only 4 bytes remain total, so this must fail without consuming anything.
455            let err = reader.read(5).await.unwrap_err();
456            assert!(matches!(err, Error::BlobInsufficientLength));
457            assert_eq!(reader.position(), 6);
458
459            // Remaining bytes should still be readable in full.
460            let tail = reader.read(4).await.unwrap().coalesce();
461            assert_eq!(tail.as_ref(), b"ghij");
462            assert_eq!(reader.position(), 10);
463        });
464    }
465
466    #[test_traced]
467    fn test_read_large_data() {
468        let executor = deterministic::Runner::default();
469        executor.start(|context| async move {
470            // Test reading large amounts of data in chunks
471            let data_size = 1024 * 256; // 256KB of data
472            let data = vec![0x42; data_size];
473            let (blob, size) = context.open("partition", b"test").await.unwrap();
474            assert_eq!(size, 0);
475            blob.write_at(0, data.clone()).await.unwrap();
476            let size = data.len() as u64;
477
478            // Use a buffer much smaller than the total data
479            let mut reader = Read::from_pooler(&context, blob, size, NZUsize!(64 * 1024));
480
481            // Read all data in smaller chunks
482            let mut total_read = 0;
483            let chunk_size = 8 * 1024; // 8KB chunks
484
485            while total_read < data_size {
486                let to_read = std::cmp::min(chunk_size, data_size - total_read);
487                let read = reader.read(to_read).await.unwrap().coalesce();
488
489                // Verify data integrity
490                assert!(
491                    read.as_ref().iter().all(|&b| b == 0x42),
492                    "Data at position {total_read} is not correct"
493                );
494
495                total_read += to_read;
496            }
497
498            // Verify we read everything
499            assert_eq!(total_read, data_size);
500
501            // Reading beyond the end should fail
502            let result = reader.read(1).await;
503            assert!(matches!(result, Err(Error::BlobInsufficientLength)));
504        });
505    }
506
507    #[test_traced]
508    fn test_read_exact_size_reads() {
509        let executor = deterministic::Runner::default();
510        executor.start(|context| async move {
511            // Create a blob with exactly 2.5 buffer sizes of data
512            let buffer_size = 1024;
513            let data_size = buffer_size * 5 / 2; // 2.5 buffers
514            let data = vec![0x37; data_size];
515
516            let (blob, size) = context.open("partition", b"test").await.unwrap();
517            assert_eq!(size, 0);
518            blob.write_at(0, data.clone()).await.unwrap();
519            let size = data.len() as u64;
520
521            let mut reader = Read::from_pooler(&context, blob, size, NZUsize!(buffer_size));
522
523            // Read exactly one buffer size
524            let read = reader.read(buffer_size).await.unwrap().coalesce();
525            assert!(read.as_ref().iter().all(|&b| b == 0x37));
526
527            // Read exactly one buffer size more
528            let read = reader.read(buffer_size).await.unwrap().coalesce();
529            assert!(read.as_ref().iter().all(|&b| b == 0x37));
530
531            // Read the remaining half buffer
532            let half_buffer = buffer_size / 2;
533            let read = reader.read(half_buffer).await.unwrap().coalesce();
534            assert!(read.as_ref().iter().all(|&b| b == 0x37));
535
536            // Verify we're at the end
537            assert_eq!(reader.blob_remaining(), 0);
538            assert_eq!(reader.position(), size);
539        });
540    }
541
542    #[test_traced]
543    fn test_read_structure_single_vs_chunked() {
544        let executor = deterministic::Runner::default();
545        executor.start(|context| async move {
546            let data = b"ABCDEFGHIJKL";
547            let (blob, size) = context.open("partition", b"structural").await.unwrap();
548            assert_eq!(size, 0);
549            blob.write_at(0, data).await.unwrap();
550
551            let mut reader = Read::from_pooler(&context, blob, data.len() as u64, NZUsize!(5));
552
553            // First read fits in one fetched chunk.
554            let first = reader.read(3).await.unwrap();
555            assert!(first.is_single());
556            assert_eq!(first.coalesce().as_ref(), b"ABC");
557
558            // This read spans refill boundaries and should be represented as multiple chunks.
559            let second = reader.read(7).await.unwrap();
560            assert!(!second.is_single());
561            assert_eq!(second.coalesce().as_ref(), b"DEFGHIJ");
562        });
563    }
564
565    #[test_traced]
566    fn test_read_seek_to() {
567        let executor = deterministic::Runner::default();
568        executor.start(|context| async move {
569            // Create a memory blob with some test data
570            let data = b"ABCDEFGHIJKLMNOPQRSTUVWXYZ";
571            let (blob, size) = context.open("partition", b"test").await.unwrap();
572            assert_eq!(size, 0);
573            blob.write_at(0, data).await.unwrap();
574            let size = data.len() as u64;
575
576            // Create a buffer reader
577            let mut reader = Read::from_pooler(&context, blob, size, NZUsize!(10));
578
579            // Read some data to advance the position
580            let read = reader.read(5).await.unwrap().coalesce();
581            assert_eq!(read.as_ref(), b"ABCDE");
582            assert_eq!(reader.position(), 5);
583
584            // Seek to a specific position
585            reader.seek_to(10).unwrap();
586            assert_eq!(reader.position(), 10);
587
588            // Read data from the new position
589            let read = reader.read(5).await.unwrap().coalesce();
590            assert_eq!(read.as_ref(), b"KLMNO");
591
592            // Seek to beginning
593            reader.seek_to(0).unwrap();
594            assert_eq!(reader.position(), 0);
595
596            let read = reader.read(5).await.unwrap().coalesce();
597            assert_eq!(read.as_ref(), b"ABCDE");
598
599            // Seek to end
600            reader.seek_to(size).unwrap();
601            assert_eq!(reader.position(), size);
602
603            // Trying to read should fail
604            let result = reader.read(1).await;
605            assert!(matches!(result, Err(Error::BlobInsufficientLength)));
606
607            // Seek beyond end should fail
608            let result = reader.seek_to(size + 10);
609            assert!(matches!(result, Err(Error::BlobInsufficientLength)));
610        });
611    }
612
613    #[test_traced]
614    fn test_read_seek_with_refill() {
615        let executor = deterministic::Runner::default();
616        executor.start(|context| async move {
617            // Create a memory blob with longer data
618            let data = vec![0x41; 1000]; // 1000 'A' characters
619            let (blob, size) = context.open("partition", b"test").await.unwrap();
620            assert_eq!(size, 0);
621            blob.write_at(0, data.clone()).await.unwrap();
622            let size = data.len() as u64;
623
624            // Create a buffer reader with small buffer
625            let mut reader = Read::from_pooler(&context, blob, size, NZUsize!(10));
626
627            // Read some data
628            let _ = reader.read(5).await.unwrap().coalesce();
629
630            // Seek far ahead, past the current buffer
631            reader.seek_to(500).unwrap();
632
633            // Read data - should get data from position 500
634            let read = reader.read(5).await.unwrap().coalesce();
635            assert_eq!(read.as_ref(), b"AAAAA"); // Should still be 'A's);
636            assert_eq!(reader.position(), 505);
637
638            // Seek backwards
639            reader.seek_to(100).unwrap();
640
641            // Read again - should be at position 100
642            let _ = reader.read(5).await.unwrap().coalesce();
643            assert_eq!(reader.position(), 105);
644        });
645    }
646
647    #[test_traced]
648    fn test_read_seek_within_buffered_range() {
649        let executor = deterministic::Runner::default();
650        executor.start(|context| async move {
651            let data = b"ABCDEFGHIJKLMNOPQRSTUVWXYZ";
652            let (blob, size) = context.open("partition", b"test").await.unwrap();
653            assert_eq!(size, 0);
654            blob.write_at(0, data).await.unwrap();
655
656            let mut reader = Read::from_pooler(&context, blob, data.len() as u64, NZUsize!(10));
657
658            // Reads 0..=5, while the internal fetch cursor advances to 10.
659            let read = reader.read(6).await.unwrap().coalesce();
660            assert_eq!(read.as_ref(), b"ABCDEF");
661            assert_eq!(reader.position(), 6);
662            assert_eq!(reader.buffer_remaining(), 4);
663
664            // Seek back within [buffer_start, fetch_position).
665            reader.seek_to(3).unwrap();
666            assert_eq!(reader.position(), 3);
667            assert_eq!(reader.buffer_remaining(), 7);
668
669            let read = reader.read(5).await.unwrap().coalesce();
670            assert_eq!(read.as_ref(), b"DEFGH");
671            assert_eq!(reader.position(), 8);
672            assert_eq!(reader.buffer_remaining(), 2);
673        });
674    }
675
676    #[test_traced]
677    fn test_read_seek_within_unread_buffer_does_not_refill() {
678        let executor = deterministic::Runner::default();
679        executor.start(|context| async move {
680            let data = b"ABCDEFGHIJKLMNOPQRSTUVWXYZ";
681            let (blob, size) = context
682                .open("partition", b"seek_unread_no_refill")
683                .await
684                .unwrap();
685            assert_eq!(size, 0);
686            blob.write_at(0, data).await.unwrap();
687
688            let mut reader = Read::from_pooler(&context, blob, data.len() as u64, NZUsize!(10));
689
690            // First read triggers a single refill of 10 bytes.
691            let first = reader.read(6).await.unwrap();
692            assert_eq!(first.coalesce().as_ref(), b"ABCDEF");
693            assert_eq!(reader.position(), 6);
694            assert_eq!(reader.buffer_remaining(), 4);
695
696            // Seek within the unread buffered window [6, 10).
697            reader.seek_to(7).unwrap();
698            assert_eq!(reader.position(), 7);
699            assert_eq!(reader.buffer_remaining(), 3);
700
701            // Consume only from the already buffered window.
702            let second = reader.read(3).await.unwrap();
703            assert_eq!(second.coalesce().as_ref(), b"HIJ");
704            assert_eq!(reader.position(), 10);
705            assert_eq!(reader.buffer_remaining(), 0);
706
707            // Refill should happen only now (at exhaustion), not at seek/read above.
708            let third = reader.read(1).await.unwrap();
709            assert_eq!(third.coalesce().as_ref(), b"K");
710            assert_eq!(reader.position(), 11);
711            assert_eq!(reader.buffer_remaining(), 9);
712        });
713    }
714
715    #[test_traced]
716    fn test_read_resize() {
717        let executor = deterministic::Runner::default();
718        executor.start(|context| async move {
719            // Create a memory blob with some test data
720            let data = b"ABCDEFGHIJKLMNOPQRSTUVWXYZ";
721            let (blob, size) = context.open("partition", b"test").await.unwrap();
722            assert_eq!(size, 0);
723            blob.write_at(0, data).await.unwrap();
724            let data_len = data.len() as u64;
725
726            // Create a buffer reader
727            let reader = Read::from_pooler(&context, blob.clone(), data_len, NZUsize!(10));
728
729            // Resize the blob to half its size
730            let resize_len = data_len / 2;
731            reader.resize(resize_len).await.unwrap();
732
733            // Reopen to check truncation
734            let (blob, size) = context.open("partition", b"test").await.unwrap();
735            assert_eq!(size, resize_len, "Blob should be resized to half size");
736
737            // Create a new buffer and read to verify truncation
738            let mut new_reader = Read::from_pooler(&context, blob, size, NZUsize!(10));
739
740            // Read the content
741            let read = new_reader.read(size as usize).await.unwrap().coalesce();
742            assert_eq!(
743                read.as_ref(),
744                b"ABCDEFGHIJKLM",
745                "Resized content should match"
746            );
747
748            // Reading beyond resized size should fail
749            let result = new_reader.read(1).await;
750            assert!(matches!(result, Err(Error::BlobInsufficientLength)));
751
752            // Test resize to larger size
753            new_reader.resize(data_len * 2).await.unwrap();
754
755            // Reopen to check resize
756            let (blob, new_size) = context.open("partition", b"test").await.unwrap();
757            assert_eq!(new_size, data_len * 2);
758
759            // Create a new buffer and read to verify resize
760            let mut new_reader = Read::from_pooler(&context, blob, new_size, NZUsize!(10));
761            let read = new_reader.read(new_size as usize).await.unwrap().coalesce();
762            assert_eq!(&read.as_ref()[..size as usize], b"ABCDEFGHIJKLM");
763            assert_eq!(
764                &read.as_ref()[size as usize..],
765                vec![0u8; new_size as usize - size as usize]
766            );
767        });
768    }
769
770    #[test_traced]
771    fn test_read_resize_to_zero() {
772        let executor = deterministic::Runner::default();
773        executor.start(|context| async move {
774            // Create a memory blob with some test data
775            let data = b"ABCDEFGHIJKLMNOPQRSTUVWXYZ";
776            let data_len = data.len() as u64;
777            let (blob, size) = context.open("partition", b"test").await.unwrap();
778            assert_eq!(size, 0);
779            blob.write_at(0, data).await.unwrap();
780
781            // Create a buffer reader
782            let reader = Read::from_pooler(&context, blob.clone(), data_len, NZUsize!(10));
783
784            // Resize the blob to zero
785            reader.resize(0).await.unwrap();
786
787            // Reopen to check truncation
788            let (blob, size) = context.open("partition", b"test").await.unwrap();
789            assert_eq!(size, 0, "Blob should be resized to zero");
790
791            // Create a new buffer and try to read (should fail)
792            let mut new_reader = Read::from_pooler(&context, blob, size, NZUsize!(10));
793
794            // Reading from resized blob should fail
795            let result = new_reader.read(1).await;
796            assert!(matches!(result, Err(Error::BlobInsufficientLength)));
797        });
798    }
799
800    #[test_traced]
801    fn test_write_basic() {
802        let executor = deterministic::Runner::default();
803        executor.start(|context| async move {
804            // Test basic buffered write and sync functionality
805            let (blob, size) = context.open("partition", b"write_basic").await.unwrap();
806            assert_eq!(size, 0);
807
808            let mut writer = Write::from_pooler(&context, blob.clone(), size, NZUsize!(8));
809            writer.write_at(0, b"hello").await.unwrap();
810            assert_eq!(writer.size(), 5);
811            writer.sync().await.unwrap();
812            assert_eq!(writer.size(), 5);
813
814            // Verify data was written correctly
815            let (blob, size) = context.open("partition", b"write_basic").await.unwrap();
816            assert_eq!(size, 5);
817            let mut reader = Read::from_pooler(&context, blob, size, NZUsize!(8));
818            let read = reader.read(5).await.unwrap().coalesce();
819            assert_eq!(read.as_ref(), b"hello");
820        });
821    }
822
823    #[test_traced]
824    fn test_write_multiple_flushes() {
825        let executor = deterministic::Runner::default();
826        executor.start(|context| async move {
827            // Test writes that cause buffer flushes due to capacity limits
828            let (blob, size) = context.open("partition", b"write_multi").await.unwrap();
829            assert_eq!(size, 0);
830
831            let mut writer = Write::from_pooler(&context, blob.clone(), size, NZUsize!(4));
832            writer.write_at(0, b"abc").await.unwrap();
833            assert_eq!(writer.size(), 3);
834            writer.write_at(3, b"defg").await.unwrap();
835            assert_eq!(writer.size(), 7);
836            writer.sync().await.unwrap();
837
838            // Verify the final result
839            let (blob, size) = context.open("partition", b"write_multi").await.unwrap();
840            assert_eq!(size, 7);
841            let mut reader = Read::from_pooler(&context, blob, size, NZUsize!(4));
842            let read = reader.read(7).await.unwrap().coalesce();
843            assert_eq!(read.as_ref(), b"abcdefg");
844        });
845    }
846
847    #[test_traced]
848    fn test_write_large_data() {
849        let executor = deterministic::Runner::default();
850        executor.start(|context| async move {
851            // Test writing data larger than buffer capacity (direct write)
852            let (blob, size) = context.open("partition", b"write_large").await.unwrap();
853            assert_eq!(size, 0);
854
855            let mut writer = Write::from_pooler(&context, blob.clone(), size, NZUsize!(4));
856            writer.write_at(0, b"abc").await.unwrap();
857            assert_eq!(writer.size(), 3);
858            writer
859                .write_at(3, b"defghijklmnopqrstuvwxyz")
860                .await
861                .unwrap();
862            assert_eq!(writer.size(), 26);
863            writer.sync().await.unwrap();
864            assert_eq!(writer.size(), 26);
865
866            // Verify the complete data
867            let (blob, size) = context.open("partition", b"write_large").await.unwrap();
868            assert_eq!(size, 26);
869            let mut reader = Read::from_pooler(&context, blob, size, NZUsize!(4));
870            let read = reader.read(26).await.unwrap().coalesce();
871            assert_eq!(read.as_ref(), b"abcdefghijklmnopqrstuvwxyz");
872        });
873    }
874
875    #[test_traced]
876    fn test_write_append_to_buffer() {
877        let executor = deterministic::Runner::default();
878        executor.start(|context| async move {
879            // Test sequential appends that exceed buffer capacity
880            let (blob, size) = context.open("partition", b"append_buf").await.unwrap();
881            let mut writer = Write::from_pooler(&context, blob.clone(), size, NZUsize!(10));
882
883            // Write data that fits in buffer
884            writer.write_at(0, b"hello").await.unwrap();
885            assert_eq!(writer.size(), 5);
886
887            // Append data that causes buffer flush
888            writer.write_at(5, b" world").await.unwrap();
889            writer.sync().await.unwrap();
890            assert_eq!(writer.size(), 11);
891
892            // Verify the complete result
893            let (blob, size) = context.open("partition", b"append_buf").await.unwrap();
894            assert_eq!(size, 11);
895            let mut reader = Read::from_pooler(&context, blob, size, NZUsize!(10));
896            let read = reader.read(11).await.unwrap().coalesce();
897            assert_eq!(read.as_ref(), b"hello world");
898        });
899    }
900
901    #[test_traced]
902    fn test_write_into_middle_of_buffer() {
903        let executor = deterministic::Runner::default();
904        executor.start(|context| async move {
905            // Test overwriting data within the buffer and extending it
906            let (blob, size) = context.open("partition", b"middle_buf").await.unwrap();
907            let mut writer = Write::from_pooler(&context, blob.clone(), size, NZUsize!(20));
908
909            // Initial write
910            writer.write_at(0, b"abcdefghij").await.unwrap();
911            assert_eq!(writer.size(), 10);
912
913            // Overwrite middle section
914            writer.write_at(2, b"01234").await.unwrap();
915            assert_eq!(writer.size(), 10);
916            writer.sync().await.unwrap();
917
918            // Verify overwrite result
919            let (blob, size) = context.open("partition", b"middle_buf").await.unwrap();
920            assert_eq!(size, 10);
921            let mut reader = Read::from_pooler(&context, blob, size, NZUsize!(10));
922            let read = reader.read(10).await.unwrap().coalesce();
923            assert_eq!(read.as_ref(), b"ab01234hij");
924
925            // Extend buffer and do partial overwrite
926            writer.write_at(10, b"klmnopqrst").await.unwrap();
927            assert_eq!(writer.size(), 20);
928            writer.write_at(9, b"wxyz").await.unwrap();
929            assert_eq!(writer.size(), 20);
930            writer.sync().await.unwrap();
931
932            // Verify final result
933            let (blob, size) = context.open("partition", b"middle_buf").await.unwrap();
934            assert_eq!(size, 20);
935            let mut reader = Read::from_pooler(&context, blob, size, NZUsize!(20));
936            let read = reader.read(20).await.unwrap().coalesce();
937            assert_eq!(read.as_ref(), b"ab01234hiwxyznopqrst");
938        });
939    }
940
941    #[test_traced]
942    fn test_write_before_buffer() {
943        let executor = deterministic::Runner::default();
944        executor.start(|context| async move {
945            // Test writing at offsets before the current buffer position
946            let (blob, size) = context.open("partition", b"before_buf").await.unwrap();
947            let mut writer = Write::from_pooler(&context, blob.clone(), size, NZUsize!(10));
948
949            // Write data at a later offset first
950            writer.write_at(10, b"0123456789").await.unwrap();
951            assert_eq!(writer.size(), 20);
952
953            // Write at an earlier offset (should flush buffer first)
954            writer.write_at(0, b"abcde").await.unwrap();
955            assert_eq!(writer.size(), 20);
956            writer.sync().await.unwrap();
957
958            // Verify data placement with gap
959            let (blob, size) = context.open("partition", b"before_buf").await.unwrap();
960            assert_eq!(size, 20);
961            let mut reader = Read::from_pooler(&context, blob, size, NZUsize!(20));
962            let read = reader.read(20).await.unwrap().coalesce();
963            let mut expected = vec![0u8; 20];
964            expected[0..5].copy_from_slice("abcde".as_bytes());
965            expected[10..20].copy_from_slice("0123456789".as_bytes());
966            assert_eq!(read.as_ref(), expected.as_slice());
967
968            // Fill the gap between existing data
969            writer.write_at(5, b"fghij").await.unwrap();
970            assert_eq!(writer.size(), 20);
971            writer.sync().await.unwrap();
972            assert_eq!(writer.size(), 20);
973
974            // Verify gap is filled
975            let (blob, size) = context.open("partition", b"before_buf").await.unwrap();
976            assert_eq!(size, 20);
977            let mut reader = Read::from_pooler(&context, blob, size, NZUsize!(20));
978            let read = reader.read(20).await.unwrap().coalesce();
979            expected[0..10].copy_from_slice("abcdefghij".as_bytes());
980            assert_eq!(read.as_ref(), expected.as_slice());
981        });
982    }
983
984    #[test_traced]
985    fn test_write_resize() {
986        let executor = deterministic::Runner::default();
987        executor.start(|context| async move {
988            // Test blob resize functionality and subsequent writes
989            let (blob, size) = context.open("partition", b"resize_write").await.unwrap();
990            let mut writer = Write::from_pooler(&context, blob, size, NZUsize!(10));
991
992            // Write initial data
993            writer.write_at(0, b"hello world").await.unwrap();
994            assert_eq!(writer.size(), 11);
995            writer.sync().await.unwrap();
996            assert_eq!(writer.size(), 11);
997
998            let (blob_check, size_check) =
999                context.open("partition", b"resize_write").await.unwrap();
1000            assert_eq!(size_check, 11);
1001            drop(blob_check);
1002
1003            // Resize to smaller size
1004            writer.resize(5).await.unwrap();
1005            assert_eq!(writer.size(), 5);
1006            writer.sync().await.unwrap();
1007
1008            // Verify resize
1009            let (blob, size) = context.open("partition", b"resize_write").await.unwrap();
1010            assert_eq!(size, 5);
1011            let mut reader = Read::from_pooler(&context, blob, size, NZUsize!(5));
1012            let read = reader.read(5).await.unwrap().coalesce();
1013            assert_eq!(read.as_ref(), b"hello");
1014
1015            // Write to resized blob
1016            writer.write_at(0, b"X").await.unwrap();
1017            assert_eq!(writer.size(), 5);
1018            writer.sync().await.unwrap();
1019
1020            // Verify overwrite
1021            let (blob, size) = context.open("partition", b"resize_write").await.unwrap();
1022            assert_eq!(size, 5);
1023            let mut reader = Read::from_pooler(&context, blob, size, NZUsize!(5));
1024            let read = reader.read(5).await.unwrap().coalesce();
1025            assert_eq!(read.as_ref(), b"Xello");
1026
1027            // Test resize to larger size
1028            writer.resize(10).await.unwrap();
1029            assert_eq!(writer.size(), 10);
1030            writer.sync().await.unwrap();
1031
1032            // Verify resize
1033            let (blob, size) = context.open("partition", b"resize_write").await.unwrap();
1034            assert_eq!(size, 10);
1035            let mut reader = Read::from_pooler(&context, blob, size, NZUsize!(10));
1036            let read = reader.read(10).await.unwrap().coalesce();
1037            assert_eq!(&read.as_ref()[0..5], b"Xello");
1038            assert_eq!(&read.as_ref()[5..10], [0u8; 5]);
1039
1040            // Test resize to zero
1041            let (blob_zero, size) = context.open("partition", b"resize_zero").await.unwrap();
1042            let mut writer_zero =
1043                Write::from_pooler(&context, blob_zero.clone(), size, NZUsize!(10));
1044            writer_zero.write_at(0, b"some data").await.unwrap();
1045            assert_eq!(writer_zero.size(), 9);
1046            writer_zero.sync().await.unwrap();
1047            assert_eq!(writer_zero.size(), 9);
1048            writer_zero.resize(0).await.unwrap();
1049            assert_eq!(writer_zero.size(), 0);
1050            writer_zero.sync().await.unwrap();
1051            assert_eq!(writer_zero.size(), 0);
1052
1053            // Ensure the blob is empty
1054            let (_, size_z) = context.open("partition", b"resize_zero").await.unwrap();
1055            assert_eq!(size_z, 0);
1056        });
1057    }
1058
1059    #[test_traced]
1060    fn test_write_read_at_on_writer() {
1061        let executor = deterministic::Runner::default();
1062        executor.start(|context| async move {
1063            // Test reading through writer's read_at method (buffer + blob reads)
1064            let (blob, size) = context.open("partition", b"read_at_writer").await.unwrap();
1065            let mut writer = Write::from_pooler(&context, blob.clone(), size, NZUsize!(10));
1066
1067            // Write data that stays in buffer
1068            writer.write_at(0, b"buffered").await.unwrap();
1069            assert_eq!(writer.size(), 8);
1070
1071            // Read from buffer via writer
1072            let read_buf_vec = writer.read_at(0, 4).await.unwrap().coalesce();
1073            assert_eq!(read_buf_vec, b"buff");
1074
1075            let read_buf_vec = writer.read_at(4, 4).await.unwrap().coalesce();
1076            assert_eq!(read_buf_vec, b"ered");
1077
1078            // Reading past buffer end should fail
1079            assert!(writer.read_at(8, 1).await.is_err());
1080
1081            // Write large data that flushes buffer
1082            writer.write_at(8, b" and flushed").await.unwrap();
1083            assert_eq!(writer.size(), 20);
1084            writer.sync().await.unwrap();
1085            assert_eq!(writer.size(), 20);
1086
1087            // Read from underlying blob through writer
1088            let read_buf_vec_2 = writer.read_at(0, 4).await.unwrap().coalesce();
1089            assert_eq!(read_buf_vec_2, b"buff");
1090
1091            let read_buf_7_vec = writer.read_at(13, 7).await.unwrap().coalesce();
1092            assert_eq!(read_buf_7_vec, b"flushed");
1093
1094            // Buffer new data at the end
1095            writer.write_at(20, b" more data").await.unwrap();
1096            assert_eq!(writer.size(), 30);
1097
1098            // Read newly buffered data
1099            let read_buf_vec_3 = writer.read_at(20, 5).await.unwrap().coalesce();
1100            assert_eq!(read_buf_vec_3, b" more");
1101
1102            // Read spanning both blob and buffer
1103            let combo_read_buf_vec = writer.read_at(16, 12).await.unwrap();
1104            assert_eq!(combo_read_buf_vec.coalesce(), b"shed more da");
1105
1106            // Verify complete content by reopening
1107            writer.sync().await.unwrap();
1108            assert_eq!(writer.size(), 30);
1109            let (final_blob, final_size) =
1110                context.open("partition", b"read_at_writer").await.unwrap();
1111            assert_eq!(final_size, 30);
1112            let mut final_reader =
1113                Read::from_pooler(&context, final_blob, final_size, NZUsize!(30));
1114            let read = final_reader.read(30).await.unwrap().coalesce();
1115            assert_eq!(read.as_ref(), b"buffered and flushed more data");
1116        });
1117    }
1118
1119    #[test_traced]
1120    fn test_write_zero_length_read_past_eof_errors() {
1121        let executor = deterministic::Runner::default();
1122        executor.start(|context| async move {
1123            let (blob, size) = context.open("partition", b"zero_len_probe").await.unwrap();
1124            let mut writer = Write::from_pooler(&context, blob, size, NZUsize!(8));
1125            writer.write_at(0, b"abc").await.unwrap();
1126
1127            let empty = writer.read_at(3, 0).await.unwrap();
1128            assert!(empty.is_empty());
1129
1130            let err = writer.read_at(4, 0).await.unwrap_err();
1131            assert!(matches!(err, Error::BlobInsufficientLength));
1132        });
1133    }
1134
1135    #[test_traced]
1136    fn test_write_straddling_non_mergeable() {
1137        let executor = deterministic::Runner::default();
1138        executor.start(|context| async move {
1139            // Test writes that cannot be merged into buffer (non-contiguous/too large)
1140            let (blob, size) = context.open("partition", b"write_straddle").await.unwrap();
1141            let mut writer = Write::from_pooler(&context, blob.clone(), size, NZUsize!(10));
1142
1143            // Fill buffer completely
1144            writer.write_at(0, b"0123456789").await.unwrap();
1145            assert_eq!(writer.size(), 10);
1146
1147            // Write at non-contiguous offset (should flush then write directly)
1148            writer.write_at(15, b"abc").await.unwrap();
1149            assert_eq!(writer.size(), 18);
1150            writer.sync().await.unwrap();
1151            assert_eq!(writer.size(), 18);
1152
1153            // Verify data with gap
1154            let (blob_check, size_check) =
1155                context.open("partition", b"write_straddle").await.unwrap();
1156            assert_eq!(size_check, 18);
1157            let mut reader = Read::from_pooler(&context, blob_check, size_check, NZUsize!(20));
1158            let read = reader.read(18).await.unwrap().coalesce();
1159
1160            let mut expected = vec![0u8; 18];
1161            expected[0..10].copy_from_slice(b"0123456789");
1162            expected[15..18].copy_from_slice(b"abc");
1163            assert_eq!(read.as_ref(), expected.as_slice());
1164
1165            // Test write that exceeds buffer capacity
1166            let (blob2, size) = context.open("partition", b"write_straddle2").await.unwrap();
1167            let mut writer2 = Write::from_pooler(&context, blob2.clone(), size, NZUsize!(10));
1168            writer2.write_at(0, b"0123456789").await.unwrap();
1169            assert_eq!(writer2.size(), 10);
1170
1171            // Write large data that exceeds capacity
1172            writer2.write_at(5, b"ABCDEFGHIJKL").await.unwrap();
1173            assert_eq!(writer2.size(), 17);
1174            writer2.sync().await.unwrap();
1175            assert_eq!(writer2.size(), 17);
1176
1177            // Verify overwrite result
1178            let (blob_check2, size_check2) =
1179                context.open("partition", b"write_straddle2").await.unwrap();
1180            assert_eq!(size_check2, 17);
1181            let mut reader2 = Read::from_pooler(&context, blob_check2, size_check2, NZUsize!(20));
1182            let read = reader2.read(17).await.unwrap().coalesce();
1183            assert_eq!(read.as_ref(), b"01234ABCDEFGHIJKL");
1184        });
1185    }
1186
1187    #[test_traced]
1188    fn test_write_close() {
1189        let executor = deterministic::Runner::default();
1190        executor.start(|context| async move {
1191            // Test that closing writer flushes and persists buffered data
1192            let (blob_orig, size) = context.open("partition", b"write_close").await.unwrap();
1193            let mut writer = Write::from_pooler(&context, blob_orig.clone(), size, NZUsize!(8));
1194            writer.write_at(0, b"pending").await.unwrap();
1195            assert_eq!(writer.size(), 7);
1196
1197            // Sync writer to persist data
1198            writer.sync().await.unwrap();
1199
1200            // Verify data persistence
1201            let (blob_check, size_check) = context.open("partition", b"write_close").await.unwrap();
1202            assert_eq!(size_check, 7);
1203            let mut reader = Read::from_pooler(&context, blob_check, size_check, NZUsize!(8));
1204            let read = reader.read(7).await.unwrap().coalesce();
1205            assert_eq!(read.as_ref(), b"pending");
1206        });
1207    }
1208
1209    #[test_traced]
1210    fn test_write_direct_due_to_size() {
1211        let executor = deterministic::Runner::default();
1212        executor.start(|context| async move {
1213            // Test direct writes when data exceeds buffer capacity
1214            let (blob, size) = context
1215                .open("partition", b"write_direct_size")
1216                .await
1217                .unwrap();
1218            let mut writer = Write::from_pooler(&context, blob.clone(), size, NZUsize!(5));
1219
1220            // Write data larger than buffer capacity (should write directly)
1221            let data_large = b"0123456789";
1222            writer.write_at(0, data_large).await.unwrap();
1223            assert_eq!(writer.size(), 10);
1224
1225            // Sync to ensure data is persisted
1226            writer.sync().await.unwrap();
1227
1228            // Verify direct write worked
1229            let (blob_check, size_check) = context
1230                .open("partition", b"write_direct_size")
1231                .await
1232                .unwrap();
1233            assert_eq!(size_check, 10);
1234            let mut reader = Read::from_pooler(&context, blob_check, size_check, NZUsize!(10));
1235            let read = reader.read(10).await.unwrap().coalesce();
1236            assert_eq!(read.as_ref(), data_large.as_slice());
1237
1238            // Now write small data that should be buffered
1239            writer.write_at(10, b"abc").await.unwrap();
1240            assert_eq!(writer.size(), 13);
1241
1242            // Verify it's in buffer by reading through writer
1243            let read_small_buf_vec = writer.read_at(10, 3).await.unwrap().coalesce();
1244            assert_eq!(read_small_buf_vec, b"abc");
1245
1246            writer.sync().await.unwrap();
1247
1248            // Verify final state
1249            let (blob_check2, size_check2) = context
1250                .open("partition", b"write_direct_size")
1251                .await
1252                .unwrap();
1253            assert_eq!(size_check2, 13);
1254            let mut reader2 = Read::from_pooler(&context, blob_check2, size_check2, NZUsize!(13));
1255            let read = reader2.read(13).await.unwrap().coalesce();
1256            assert_eq!(&read.as_ref()[10..], b"abc".as_slice());
1257        });
1258    }
1259
1260    #[test_traced]
1261    fn test_write_overwrite_and_extend_in_buffer() {
1262        let executor = deterministic::Runner::default();
1263        executor.start(|context| async move {
1264            // Test complex buffer operations: overwrite and extend within capacity
1265            let (blob, size) = context
1266                .open("partition", b"overwrite_extend_buf")
1267                .await
1268                .unwrap();
1269            let mut writer = Write::from_pooler(&context, blob.clone(), size, NZUsize!(15));
1270
1271            // Write initial data
1272            writer.write_at(0, b"0123456789").await.unwrap();
1273            assert_eq!(writer.size(), 10);
1274
1275            // Overwrite and extend within buffer capacity
1276            writer.write_at(5, b"ABCDEFGHIJ").await.unwrap();
1277            assert_eq!(writer.size(), 15);
1278
1279            // Verify buffer content through writer
1280            let read_buf_vec = writer.read_at(0, 15).await.unwrap().coalesce();
1281            assert_eq!(read_buf_vec, b"01234ABCDEFGHIJ");
1282
1283            writer.sync().await.unwrap();
1284
1285            // Verify persisted result
1286            let (blob_check, size_check) = context
1287                .open("partition", b"overwrite_extend_buf")
1288                .await
1289                .unwrap();
1290            assert_eq!(size_check, 15);
1291            let mut reader = Read::from_pooler(&context, blob_check, size_check, NZUsize!(15));
1292            let read = reader.read(15).await.unwrap().coalesce();
1293            assert_eq!(read.as_ref(), b"01234ABCDEFGHIJ".as_slice());
1294        });
1295    }
1296
1297    #[test_traced]
1298    fn test_write_at_size() {
1299        let executor = deterministic::Runner::default();
1300        executor.start(|context| async move {
1301            // Test writing at the current logical end of the blob
1302            let (blob, size) = context.open("partition", b"write_end").await.unwrap();
1303            let mut writer = Write::from_pooler(&context, blob.clone(), size, NZUsize!(20));
1304
1305            // Write initial data
1306            writer.write_at(0, b"0123456789").await.unwrap();
1307            assert_eq!(writer.size(), 10);
1308            writer.sync().await.unwrap();
1309
1310            // Append at the current size (logical end)
1311            writer.write_at(writer.size(), b"abc").await.unwrap();
1312            assert_eq!(writer.size(), 13);
1313            writer.sync().await.unwrap();
1314
1315            // Verify complete result
1316            let (blob_check, size_check) = context.open("partition", b"write_end").await.unwrap();
1317            assert_eq!(size_check, 13);
1318            let mut reader = Read::from_pooler(&context, blob_check, size_check, NZUsize!(13));
1319            let read = reader.read(13).await.unwrap().coalesce();
1320            assert_eq!(read.as_ref(), b"0123456789abc");
1321        });
1322    }
1323
1324    #[test_traced]
1325    fn test_write_at_size_multiple_appends() {
1326        let executor = deterministic::Runner::default();
1327        executor.start(|context| async move {
1328            // Test multiple appends using writer.size()
1329            let (blob, size) = context
1330                .open("partition", b"write_multiple_appends_at_size")
1331                .await
1332                .unwrap();
1333            let mut writer = Write::from_pooler(&context, blob.clone(), size, NZUsize!(5));
1334
1335            // First write
1336            writer.write_at(0, b"AAA").await.unwrap();
1337            assert_eq!(writer.size(), 3);
1338            writer.sync().await.unwrap();
1339            assert_eq!(writer.size(), 3);
1340
1341            // Append using size()
1342            writer.write_at(writer.size(), b"BBB").await.unwrap();
1343            assert_eq!(writer.size(), 6); // 3 (AAA) + 3 (BBB)
1344            writer.sync().await.unwrap();
1345            assert_eq!(writer.size(), 6);
1346
1347            // Append again using size()
1348            writer.write_at(writer.size(), b"CCC").await.unwrap();
1349            assert_eq!(writer.size(), 9); // 6 + 3 (CCC)
1350            writer.sync().await.unwrap();
1351            assert_eq!(writer.size(), 9);
1352
1353            // Verify final content
1354            let (blob_check, size_check) = context
1355                .open("partition", b"write_multiple_appends_at_size")
1356                .await
1357                .unwrap();
1358            assert_eq!(size_check, 9);
1359            let mut reader = Read::from_pooler(&context, blob_check, size_check, NZUsize!(9));
1360            let read = reader.read(9).await.unwrap().coalesce();
1361            assert_eq!(read.as_ref(), b"AAABBBCCC");
1362        });
1363    }
1364
1365    #[test_traced]
1366    fn test_write_non_contiguous_then_append_at_size() {
1367        let executor = deterministic::Runner::default();
1368        executor.start(|context| async move {
1369            // Test writing non-contiguously, then appending at the new size
1370            let (blob, size) = context
1371                .open("partition", b"write_non_contiguous_then_append")
1372                .await
1373                .unwrap();
1374            let mut writer = Write::from_pooler(&context, blob.clone(), size, NZUsize!(10));
1375
1376            // Initial buffered write
1377            writer.write_at(0, b"INITIAL").await.unwrap(); // 7 bytes
1378            assert_eq!(writer.size(), 7);
1379            // Buffer contains "INITIAL", inner.position = 0
1380
1381            // Non-contiguous write, forces flush of "INITIAL" and direct write of "NONCONTIG"
1382            writer.write_at(20, b"NONCONTIG").await.unwrap();
1383            assert_eq!(writer.size(), 29);
1384            writer.sync().await.unwrap();
1385            assert_eq!(writer.size(), 29);
1386
1387            // Append at the new size
1388            writer.write_at(writer.size(), b"APPEND").await.unwrap();
1389            assert_eq!(writer.size(), 35); // 29 + 6
1390            writer.sync().await.unwrap();
1391            assert_eq!(writer.size(), 35);
1392
1393            // Verify final content
1394            let (blob_check, size_check) = context
1395                .open("partition", b"write_non_contiguous_then_append")
1396                .await
1397                .unwrap();
1398            assert_eq!(size_check, 35);
1399            let mut reader = Read::from_pooler(&context, blob_check, size_check, NZUsize!(35));
1400            let read = reader.read(35).await.unwrap().coalesce();
1401
1402            let mut expected = vec![0u8; 35];
1403            expected[0..7].copy_from_slice(b"INITIAL");
1404            expected[20..29].copy_from_slice(b"NONCONTIG");
1405            expected[29..35].copy_from_slice(b"APPEND");
1406            assert_eq!(read.as_ref(), expected.as_slice());
1407        });
1408    }
1409
1410    #[test_traced]
1411    fn test_write_resize_then_append_at_size() {
1412        let executor = deterministic::Runner::default();
1413        executor.start(|context| async move {
1414            // Test truncating, then appending at the new size
1415            let (blob, size) = context
1416                .open("partition", b"resize_then_append_at_size")
1417                .await
1418                .unwrap();
1419            let mut writer = Write::from_pooler(&context, blob.clone(), size, NZUsize!(10));
1420
1421            // Write initial data and sync
1422            writer.write_at(0, b"0123456789ABCDEF").await.unwrap(); // 16 bytes
1423            assert_eq!(writer.size(), 16);
1424            writer.sync().await.unwrap(); // inner.position = 16, buffer empty
1425            assert_eq!(writer.size(), 16);
1426
1427            // Resize
1428            let resize_to = 5;
1429            writer.resize(resize_to).await.unwrap();
1430            // after resize, inner.position should be `resize_to` (5)
1431            // buffer should be empty
1432            assert_eq!(writer.size(), resize_to);
1433            writer.sync().await.unwrap(); // Ensure truncation is persisted for verify step
1434            assert_eq!(writer.size(), resize_to);
1435
1436            // Append at the new (resized) size
1437            writer.write_at(writer.size(), b"XXXXX").await.unwrap(); // 5 bytes
1438                                                                     // inner.buffer = "XXXXX", inner.position = 5
1439            assert_eq!(writer.size(), 10); // 5 (resized) + 5 (XXXXX)
1440            writer.sync().await.unwrap();
1441            assert_eq!(writer.size(), 10);
1442
1443            // Verify final content
1444            let (blob_check, size_check) = context
1445                .open("partition", b"resize_then_append_at_size")
1446                .await
1447                .unwrap();
1448            assert_eq!(size_check, 10);
1449            let mut reader = Read::from_pooler(&context, blob_check, size_check, NZUsize!(10));
1450            let read = reader.read(10).await.unwrap().coalesce();
1451            assert_eq!(read.as_ref(), b"01234XXXXX");
1452        });
1453    }
1454
1455    // Verifies start_sync flushes current bytes, completes durability, and marks the writer clean.
1456    #[test_traced]
1457    fn test_write_start_sync_persists_and_marks_clean() {
1458        let executor = deterministic::Runner::default();
1459        executor.start(|context| async move {
1460            let blob = SyncTrackingBlob::new();
1461            let mut writer = Write::from_pooler(&context, blob.clone(), 0, NZUsize!(8));
1462
1463            // Start a sync for buffered bytes and wait for the returned handle.
1464            writer.write_at(0, b"abc").await.unwrap();
1465            let handle = writer.start_sync().await;
1466            handle.await.unwrap();
1467
1468            // The buffered write required a full sync because the fresh writer starts dirty.
1469            let (durable, writes, full_syncs, range_syncs) = blob.snapshot();
1470            assert_eq!(durable.as_slice(), b"abc");
1471            assert_eq!(writes, 1);
1472            assert_eq!(full_syncs, 1);
1473            assert_eq!(range_syncs, 0);
1474
1475            // The started sync marked the writer clean, so the next buffered write can use a
1476            // range-scoped sync.
1477            writer.write_at(3, b"d").await.unwrap();
1478            writer.sync().await.unwrap();
1479            let (durable, writes, full_syncs, range_syncs) = blob.snapshot();
1480            assert_eq!(durable.as_slice(), b"abcd");
1481            assert_eq!(writes, 2);
1482            assert_eq!(full_syncs, 1);
1483            assert_eq!(range_syncs, 1);
1484
1485            // Nothing left to sync.
1486            let handle = writer.start_sync().await;
1487            handle.await.unwrap();
1488            let (_, _, full_syncs, range_syncs) = blob.snapshot();
1489            assert_eq!(full_syncs, 1);
1490            assert_eq!(range_syncs, 1);
1491        });
1492    }
1493
1494    // Verifies sync waits for an outstanding start_sync instead of starting new disk work.
1495    #[test_traced]
1496    fn test_write_sync_waits_for_outstanding_start_sync() {
1497        let executor = deterministic::Runner::default();
1498        executor.start(|context| async move {
1499            let inner = SyncTrackingBlob::new();
1500            let (blob, pending) = DelayedSyncBlob::new(inner.clone());
1501            let mut writer = Write::from_pooler(&context, blob, 0, NZUsize!(8));
1502
1503            // Hold the started sync open so a later sync cannot finish right away.
1504            let handle = writer.start_sync().await;
1505            let deferred = next_pending_sync(&pending);
1506
1507            // The attempted sync reaches the pending handle and cannot complete yet.
1508            let mut sync = Box::pin(writer.sync());
1509            assert!(
1510                sync.as_mut().now_or_never().is_none(),
1511                "sync must wait for the outstanding start_sync handle"
1512            );
1513            deferred
1514                .blocked
1515                .await
1516                .expect("sync never waited on start_sync");
1517
1518            let (_, _, full_syncs, range_syncs) = inner.snapshot();
1519            assert_eq!(full_syncs, 0);
1520            assert_eq!(range_syncs, 0);
1521
1522            // Releasing the original handle lets sync observe the completed disk sync.
1523            deferred.release.send(Ok(())).unwrap();
1524            sync.await.unwrap();
1525            handle.await.unwrap();
1526            let (_, _, full_syncs, range_syncs) = inner.snapshot();
1527            assert_eq!(full_syncs, 1);
1528            assert_eq!(range_syncs, 0);
1529        });
1530    }
1531
1532    // Verifies writes made after start_sync wait before they are flushed.
1533    #[test_traced]
1534    fn test_write_sync_after_start_sync_and_small_write_waits_before_range_sync() {
1535        let executor = deterministic::Runner::default();
1536        executor.start(|context| async move {
1537            let inner = SyncTrackingBlob::new();
1538            let (blob, pending) = DelayedSyncBlob::new(inner.clone());
1539            let mut writer = Write::from_pooler(&context, blob, 0, NZUsize!(8));
1540
1541            // Begin syncing the initial dirty state and keep that sync blocked.
1542            let handle = writer.start_sync().await;
1543            let deferred = next_pending_sync(&pending);
1544
1545            // The tip must not reach the blob while the earlier sync is pending.
1546            writer.write_at(0, b"abc").await.unwrap();
1547            let mut sync = Box::pin(writer.sync());
1548            assert!(
1549                sync.as_mut().now_or_never().is_none(),
1550                "sync must wait for the outstanding start_sync before flushing the small write"
1551            );
1552            deferred
1553                .blocked
1554                .await
1555                .expect("sync never waited on start_sync");
1556
1557            let (_, writes, full_syncs, range_syncs) = inner.snapshot();
1558            assert_eq!(writes, 0);
1559            assert_eq!(full_syncs, 0);
1560            assert_eq!(range_syncs, 0);
1561
1562            // After the earlier sync completes, the buffered write can be persisted.
1563            deferred.release.send(Ok(())).unwrap();
1564            sync.await.unwrap();
1565            handle.await.unwrap();
1566
1567            let (durable, writes, full_syncs, range_syncs) = inner.snapshot();
1568            assert_eq!(durable.as_slice(), b"abc");
1569            assert_eq!(writes, 1);
1570            assert_eq!(full_syncs, 1);
1571            assert_eq!(range_syncs, 1);
1572        });
1573    }
1574
1575    // Verifies overlapping writes wait before flushing buffered bytes while start_sync is pending.
1576    #[test_traced]
1577    fn test_write_at_overlap_flush_waits_for_outstanding_start_sync() {
1578        let executor = deterministic::Runner::default();
1579        executor.start(|context| async move {
1580            let inner = SyncTrackingBlob::new();
1581            inner.write_at(0, b"xxx").await.unwrap();
1582
1583            let (blob, pending) = DelayedSyncBlob::new(inner.clone());
1584            let mut writer = Write::from_pooler(&context, blob, inner.size(), NZUsize!(8));
1585
1586            let handle = writer.start_sync().await;
1587            let deferred = next_pending_sync(&pending);
1588
1589            // This append is local while the earlier sync is pending.
1590            writer.write_at(3, b"abc").await.unwrap();
1591
1592            // The drained tip must not reach the blob while the earlier sync is pending.
1593            let mut write = Box::pin(writer.write_at(2, b"ZZ"));
1594            assert!(
1595                write.as_mut().now_or_never().is_none(),
1596                "overlapping write must wait for the outstanding start_sync before flushing"
1597            );
1598            deferred
1599                .blocked
1600                .await
1601                .expect("write never waited on start_sync");
1602
1603            let (_, writes, full_syncs, range_syncs) = inner.snapshot();
1604            assert_eq!(writes, 1);
1605            assert_eq!(full_syncs, 0);
1606            assert_eq!(range_syncs, 0);
1607
1608            // Releasing the sync lets the parked write reach the blob.
1609            deferred.release.send(Ok(())).unwrap();
1610            write.await.unwrap();
1611            handle.await.unwrap();
1612
1613            let (_, writes, full_syncs, range_syncs) = inner.snapshot();
1614            assert_eq!(writes, 3);
1615            assert_eq!(full_syncs, 1);
1616            assert_eq!(range_syncs, 0);
1617        });
1618    }
1619
1620    // Verifies resize does not mutate the blob before an outstanding start_sync completes.
1621    #[test_traced]
1622    fn test_write_resize_waits_for_outstanding_start_sync_before_resizing() {
1623        let executor = deterministic::Runner::default();
1624        executor.start(|context| async move {
1625            let inner = SyncTrackingBlob::new();
1626            inner.write_at(0, b"abcdef").await.unwrap();
1627
1628            let (blob, pending) = DelayedSyncBlob::new(inner.clone());
1629            let mut writer = Write::from_pooler(&context, blob, inner.size(), NZUsize!(8));
1630
1631            let handle = writer.start_sync().await;
1632            let deferred = next_pending_sync(&pending);
1633            let original_size = inner.size();
1634
1635            // Resize must not reach the blob while the earlier sync is pending.
1636            let mut resize = Box::pin(writer.resize(3));
1637            assert!(
1638                resize.as_mut().now_or_never().is_none(),
1639                "resize must wait for the outstanding start_sync handle"
1640            );
1641            deferred
1642                .blocked
1643                .await
1644                .expect("resize never waited on start_sync");
1645            assert_eq!(
1646                inner.size(),
1647                original_size,
1648                "resize must not mutate the blob before the pending sync finishes"
1649            );
1650
1651            // Releasing the sync lets the resize apply.
1652            deferred.release.send(Ok(())).unwrap();
1653            resize.await.unwrap();
1654            handle.await.unwrap();
1655            assert_eq!(writer.size(), 3);
1656            assert_eq!(inner.size(), 3);
1657        });
1658    }
1659
1660    #[test_traced]
1661    fn test_write_sync_uses_range_sync_for_buffer_only_write() {
1662        let executor = deterministic::Runner::default();
1663        executor.start(|context| async move {
1664            let blob = SyncTrackingBlob::new();
1665            let mut writer = Write::from_pooler(&context, blob.clone(), 0, NZUsize!(8));
1666
1667            // A fresh writer preserves one sync barrier for mutations that predate wrapping.
1668            writer.sync().await.unwrap();
1669            let (durable, writes, full_syncs, range_syncs) = blob.snapshot();
1670            assert!(durable.is_empty());
1671            assert_eq!(writes, 0);
1672            assert_eq!(full_syncs, 1);
1673            assert_eq!(range_syncs, 0);
1674
1675            // The write remains entirely buffered, so sync can make just this range durable.
1676            writer.write_at(0, b"abc").await.unwrap();
1677            writer.sync().await.unwrap();
1678
1679            // No prior plain blob mutation required another full sync barrier.
1680            let (durable, writes, full_syncs, range_syncs) = blob.snapshot();
1681            assert_eq!(durable.as_slice(), b"abc");
1682            assert_eq!(writes, 1);
1683            assert_eq!(full_syncs, 1);
1684            assert_eq!(range_syncs, 1);
1685
1686            // The prior sync used write_at_sync, so there is still no pending full-sync barrier.
1687            writer.sync().await.unwrap();
1688            let (durable, writes, full_syncs, range_syncs) = blob.snapshot();
1689            assert_eq!(durable.as_slice(), b"abc");
1690            assert_eq!(writes, 1);
1691            assert_eq!(full_syncs, 1);
1692            assert_eq!(range_syncs, 1);
1693        });
1694    }
1695
1696    #[test_traced]
1697    fn test_write_sync_persists_pre_wrapped_blob_mutation() {
1698        let executor = deterministic::Runner::default();
1699        executor.start(|context| async move {
1700            let blob = SyncTrackingBlob::new();
1701
1702            // Simulate a plain blob mutation before the writer wraps it.
1703            blob.write_at(0, b"abc").await.unwrap();
1704
1705            let mut writer = Write::from_pooler(&context, blob.clone(), 3, NZUsize!(8));
1706            writer.sync().await.unwrap();
1707
1708            // The first sync must use a full barrier to make the pre-wrapped write durable.
1709            let (durable, writes, full_syncs, range_syncs) = blob.snapshot();
1710            assert_eq!(durable.as_slice(), b"abc");
1711            assert_eq!(writes, 1);
1712            assert_eq!(full_syncs, 1);
1713            assert_eq!(range_syncs, 0);
1714
1715            // After the barrier is clear, a buffered tip-only write can use range sync again.
1716            writer.write_at(3, b"d").await.unwrap();
1717            writer.sync().await.unwrap();
1718
1719            let (durable, writes, full_syncs, range_syncs) = blob.snapshot();
1720            assert_eq!(durable.as_slice(), b"abcd");
1721            assert_eq!(writes, 2);
1722            assert_eq!(full_syncs, 1);
1723            assert_eq!(range_syncs, 1);
1724        });
1725    }
1726
1727    #[test_traced]
1728    fn test_write_sync_failed_range_sync_does_not_mark_clean() {
1729        let executor = deterministic::Runner::default();
1730        executor.start(|context| async move {
1731            let name = b"failed_range_sync";
1732            let (blob, size) = context.open("partition", name).await.unwrap();
1733            let mut writer = Write::from_pooler(&context, blob, size, NZUsize!(8));
1734            writer.sync().await.unwrap();
1735
1736            // Keep the write buffered so sync attempts the clean `write_at_sync` path.
1737            writer.write_at(0, b"abc").await.unwrap();
1738
1739            // Removing the blob makes the range-sync flush fail.
1740            context.remove("partition", Some(name)).await.unwrap();
1741            assert!(writer.sync().await.is_err());
1742
1743            // The failed `write_at_sync` must leave a pending full-sync barrier, so a
1744            // later sync cannot report success.
1745            assert!(writer.sync().await.is_err());
1746        });
1747    }
1748
1749    #[test_traced]
1750    fn test_write_sync_persists_prior_direct_flushes_with_buffered_tip() {
1751        let executor = deterministic::Runner::default();
1752        executor.start(|context| async move {
1753            let blob = SyncTrackingBlob::new();
1754            let mut writer = Write::from_pooler(&context, blob.clone(), 0, NZUsize!(4));
1755
1756            // This exceeds the buffer and forces a plain write before the final buffered tip.
1757            writer.write_at(0, b"abcdef").await.unwrap();
1758            writer.write_at(6, b"g").await.unwrap();
1759            writer.sync().await.unwrap();
1760
1761            // The final sync must cover both the prior plain write and the buffered tip.
1762            let (durable, writes, full_syncs, range_syncs) = blob.snapshot();
1763            assert_eq!(durable.as_slice(), b"abcdefg");
1764            assert_eq!(writes, 2);
1765            assert_eq!(full_syncs, 1);
1766            assert_eq!(range_syncs, 0);
1767
1768            // With no new writes, sync has no work left.
1769            writer.sync().await.unwrap();
1770            let (durable, writes, full_syncs, range_syncs) = blob.snapshot();
1771            assert_eq!(durable.as_slice(), b"abcdefg");
1772            assert_eq!(writes, 2);
1773            assert_eq!(full_syncs, 1);
1774            assert_eq!(range_syncs, 0);
1775
1776            // After the full sync, the next buffer-only write can use range sync again.
1777            writer.write_at(7, b"h").await.unwrap();
1778            writer.sync().await.unwrap();
1779
1780            let (durable, writes, full_syncs, range_syncs) = blob.snapshot();
1781            assert_eq!(durable.as_slice(), b"abcdefgh");
1782            assert_eq!(writes, 3);
1783            assert_eq!(full_syncs, 1);
1784            assert_eq!(range_syncs, 1);
1785        });
1786    }
1787
1788    #[test_traced]
1789    fn test_write_sync_uses_full_sync_after_resize() {
1790        let executor = deterministic::Runner::default();
1791        executor.start(|context| async move {
1792            let blob = SyncTrackingBlob::new();
1793            let mut writer = Write::from_pooler(&context, blob.clone(), 0, NZUsize!(8));
1794            writer.sync().await.unwrap();
1795
1796            // Establish already-durable data with a range sync.
1797            writer.write_at(0, b"abcdef").await.unwrap();
1798            writer.sync().await.unwrap();
1799
1800            // Resize alone is an unsynced blob mutation.
1801            writer.resize(4).await.unwrap();
1802            writer.sync().await.unwrap();
1803
1804            // The resized contents require a full sync barrier to become durable.
1805            let (durable, writes, full_syncs, range_syncs) = blob.snapshot();
1806            assert_eq!(durable.as_slice(), b"abcd");
1807            assert_eq!(writes, 1);
1808            assert_eq!(full_syncs, 2);
1809            assert_eq!(range_syncs, 1);
1810        });
1811    }
1812}