1use super::{CHECKSUM_SIZE, CacheRef, Replay, read::PageReader, view::View};
16use crate::{Blob, Error, IoBuf, IoBufMut, IoBufs, ReadOptions};
17use commonware_utils::Widen;
18use std::{num::NonZeroUsize, sync::Arc};
19
20pub struct Sealed<B: Blob> {
23 inner: Arc<SealedInner<B>>,
24}
25
26impl<B: Blob> Clone for Sealed<B> {
27 fn clone(&self) -> Self {
28 Self {
29 inner: self.inner.clone(),
30 }
31 }
32}
33
34struct SealedInner<B: Blob> {
35 blob: B,
37
38 size: u64,
40
41 partial_page: Option<IoBuf>,
45
46 cache_ref: CacheRef,
48
49 id: u64,
53}
54
55impl<B: Blob> Sealed<B> {
56 pub(super) fn new(
58 blob: B,
59 size: u64,
60 partial_page: Option<IoBuf>,
61 cache_ref: CacheRef,
62 id: u64,
63 ) -> Self {
64 Self {
65 inner: Arc::new(SealedInner {
66 blob,
67 size,
68 partial_page,
69 cache_ref,
70 id,
71 }),
72 }
73 }
74
75 pub fn size(&self) -> u64 {
77 self.inner.size
78 }
79
80 fn partial_offset(&self) -> u64 {
83 self.inner.size
84 - self
85 .inner
86 .partial_page
87 .as_ref()
88 .map_or(0, |p| p.len() as u64)
89 }
90
91 fn view(&self) -> View<'_, B> {
93 View {
94 blob: &self.inner.blob,
95 cache_ref: &self.inner.cache_ref,
96 id: self.inner.id,
97 size: self.inner.size,
98 tail_offset: self.partial_offset(),
99 tail: self
100 .inner
101 .partial_page
102 .as_ref()
103 .map_or(&[][..], |p| p.as_ref()),
104 }
105 }
106
107 pub async fn read_at(&self, offset: u64, len: usize) -> Result<IoBufs, Error> {
109 self.view().read_at(offset, len).await
110 }
111
112 pub fn try_read_sync_into(&self, buf: &mut [u8], offset: u64) -> bool {
116 self.view().try_read_sync_into(buf, offset)
117 }
118
119 pub async fn read_into(&self, buf: &mut [u8], offset: u64) -> Result<(), Error> {
121 self.view().read_into(buf, offset).await
122 }
123
124 pub async fn read_up_to(
129 &self,
130 offset: u64,
131 len: usize,
132 bufs: impl Into<IoBufMut> + Send,
133 ) -> Result<(IoBufMut, usize), Error> {
134 self.view().read_up_to(offset, len, bufs).await
135 }
136
137 pub async fn read_many_into(
145 &self,
146 buf: &mut [u8],
147 offsets: &[u64],
148 item_size: NonZeroUsize,
149 ) -> Result<usize, Error> {
150 self.view().read_many_into(buf, offsets, item_size).await
151 }
152
153 pub fn try_read_many_sync_into(
156 &self,
157 buf: &mut [u8],
158 offsets: &[u64],
159 item_size: NonZeroUsize,
160 ) -> Vec<usize> {
161 self.view().try_read_many_sync_into(buf, offsets, item_size)
162 }
163
164 pub fn try_read_ranges_sync_into(&self, buf: &mut [u8], ranges: &[(u64, usize)]) -> Vec<usize> {
167 self.view().try_read_ranges_sync_into(buf, ranges)
168 }
169
170 pub fn replay(
176 &self,
177 buffer_size: NonZeroUsize,
178 read_options: ReadOptions,
179 ) -> Result<Replay<B>, Error> {
180 let page_size_nz = self.inner.cache_ref.page_size();
181 let page_size: u64 = page_size_nz.widen();
182 let physical_page_size = page_size
183 .checked_add(CHECKSUM_SIZE)
184 .ok_or(Error::OffsetOverflow)?;
185 let prefetch_pages = (buffer_size.get() / physical_page_size as usize).max(1);
186
187 let partial_len = self
188 .inner
189 .partial_page
190 .as_ref()
191 .map_or(0, |p| p.len() as u64);
192 let full_pages = (self.inner.size - partial_len) / page_size;
193 let pages = full_pages + u64::from(partial_len > 0);
194 let physical_blob_size = physical_page_size
195 .checked_mul(pages)
196 .ok_or(Error::OffsetOverflow)?;
197 let logical_blob_size = self.inner.size;
198
199 let reader = PageReader::new(
200 self.inner.blob.clone(),
201 physical_blob_size,
202 logical_blob_size,
203 prefetch_pages,
204 page_size_nz,
205 read_options,
206 );
207 Ok(Replay::new(reader))
208 }
209
210 #[cfg(test)]
213 pub(super) fn cache_id(&self) -> u64 {
214 self.inner.id
215 }
216}
217
218#[cfg(test)]
219mod tests {
220 use super::*;
221 use crate::{
222 Buf, Runner as _, Storage as _,
223 buffer::{paged::Writer, tests::SyncTrackingBlob},
224 deterministic,
225 mocks::{DelayedSyncBlob, next_pending_sync},
226 };
227 use commonware_macros::test_traced;
228 use commonware_utils::{NZU16, NZUsize};
229 use std::num::NonZeroU16;
230
231 const PAGE_SIZE: NonZeroU16 = NZU16!(103); const BUFFER_SIZE: usize = PAGE_SIZE.get() as usize * 2;
233
234 #[test_traced("DEBUG")]
236 fn test_seal_starts_sync() {
237 let executor = deterministic::Runner::default();
238 executor.start(|context: deterministic::Context| async move {
239 let blob = SyncTrackingBlob::new();
240 let cache_ref =
241 super::CacheRef::from_pooler(&context, PAGE_SIZE, NZUsize!(BUFFER_SIZE));
242 let mut append = Writer::new(blob.clone(), 0, BUFFER_SIZE, cache_ref)
243 .await
244 .unwrap();
245
246 let data: Vec<u8> = (0u8..=255).cycle().take(300).collect();
248 append.append(&data).await.unwrap();
249
250 let (durable_before, _writes_before, full_before, range_before) = blob.snapshot();
251 assert!(
252 durable_before.is_empty(),
253 "no bytes should be durable before the seal's sync"
254 );
255
256 let (sealed, sync) = append.seal().await.unwrap();
257 sync.await.unwrap();
258
259 let (durable_after, _writes_after, full_after, range_after) = blob.snapshot();
260 assert_eq!(full_after, full_before + 1);
261 assert!(
262 !durable_after.is_empty(),
263 "the seal's sync handle must make the appended bytes durable"
264 );
265 assert_eq!(
266 range_after, range_before,
267 "seal must not invoke a range-scoped write"
268 );
269
270 assert_eq!(sealed.size(), 300);
271 });
272 }
273
274 #[test_traced("DEBUG")]
277 fn test_seal_succeeds_with_readers() {
278 let executor = deterministic::Runner::default();
279 executor.start(|context: deterministic::Context| async move {
280 let (blob, blob_size) = context.open("test_partition", b"readers").await.unwrap();
281 let cache_ref =
282 super::CacheRef::from_pooler(&context, PAGE_SIZE, NZUsize!(BUFFER_SIZE));
283 let mut writer = Writer::new(blob, blob_size, BUFFER_SIZE, cache_ref)
284 .await
285 .unwrap();
286 writer.append(b"hello world").await.unwrap();
287
288 let reader = writer.snapshot().await.unwrap();
290 let reader_clone = reader.clone();
291 assert_eq!(reader.size(), 11);
292
293 let (sealed, sync) = writer.seal().await.unwrap();
295 sync.await.unwrap();
296 assert_eq!(sealed.size(), 11);
297
298 for r in [&reader, &reader_clone] {
300 assert_eq!(r.size(), 11);
301 let via_reader = r.read_at(0, 11).await.unwrap().coalesce();
302 let via_sealed = sealed.read_at(0, 11).await.unwrap().coalesce();
303 assert_eq!(via_reader.as_ref(), b"hello world");
304 assert_eq!(via_sealed.as_ref(), via_reader.as_ref());
305 }
306 });
307 }
308
309 #[test_traced("DEBUG")]
312 fn test_reader_full_pages_after_seal() {
313 let executor = deterministic::Runner::default();
314 executor.start(|context: deterministic::Context| async move {
315 let (blob, blob_size) = context.open("test_partition", b"rdr_pages").await.unwrap();
316 let cache_ref = super::CacheRef::from_pooler(&context, PAGE_SIZE, NZUsize!(1));
318 let mut writer = Writer::new(blob, blob_size, BUFFER_SIZE, cache_ref)
319 .await
320 .unwrap();
321
322 let page_size = PAGE_SIZE.get() as usize;
323 let total = page_size * 3 + 7;
324 let data: Vec<u8> = (0u8..=255).cycle().take(total).collect();
325 writer.append(&data).await.unwrap();
326
327 let reader = writer.snapshot().await.unwrap();
328 let (sealed, sync) = writer.seal().await.unwrap();
329 sync.await.unwrap();
330 assert_eq!(reader.size(), total as u64);
331
332 let cases = [
335 (0u64, total),
336 (page_size as u64 - 3, 6),
337 ((page_size * 3) as u64, 7),
338 ];
339 for (offset, len) in cases {
340 let via_reader = reader.read_at(offset, len).await.unwrap().coalesce();
341 let via_sealed = sealed.read_at(offset, len).await.unwrap().coalesce();
342 assert_eq!(
343 via_reader.as_ref(),
344 &data[offset as usize..offset as usize + len]
345 );
346 assert_eq!(via_sealed.as_ref(), via_reader.as_ref());
347 }
348 });
349 }
350
351 #[test_traced("DEBUG")]
353 fn test_seal_preserves_cache_id() {
354 let executor = deterministic::Runner::default();
355 executor.start(|context: deterministic::Context| async move {
356 let (blob, blob_size) = context.open("test_partition", b"cache_id").await.unwrap();
357 let cache_ref =
358 super::CacheRef::from_pooler(&context, PAGE_SIZE, NZUsize!(BUFFER_SIZE));
359 let append = Writer::new(blob, blob_size, BUFFER_SIZE, cache_ref)
360 .await
361 .unwrap();
362 let append_id = append.cache_id();
363 let (sealed, sync) = append.seal().await.unwrap();
364 sync.await.unwrap();
365 assert_eq!(sealed.cache_id(), append_id);
366 });
367 }
368
369 #[test_traced("DEBUG")]
371 fn test_seal_empty_blob() {
372 let executor = deterministic::Runner::default();
373 executor.start(|context: deterministic::Context| async move {
374 let (blob, blob_size) = context.open("test_partition", b"empty").await.unwrap();
375 let cache_ref =
376 super::CacheRef::from_pooler(&context, PAGE_SIZE, NZUsize!(BUFFER_SIZE));
377 let append = Writer::new(blob, blob_size, BUFFER_SIZE, cache_ref)
378 .await
379 .unwrap();
380 let (sealed, sync) = append.seal().await.unwrap();
381 sync.await.unwrap();
382
383 assert_eq!(sealed.size(), 0);
384
385 let mut buf = [0u8; 1];
387 let err = sealed.read_into(&mut buf, 0).await.unwrap_err();
388 assert!(matches!(err, Error::BlobInsufficientLength));
389 });
390 }
391
392 #[test_traced("DEBUG")]
394 fn test_seal_full_pages_only() {
395 let executor = deterministic::Runner::default();
396 executor.start(|context: deterministic::Context| async move {
397 let (blob, blob_size) = context.open("test_partition", b"full").await.unwrap();
398 let cache_ref =
399 super::CacheRef::from_pooler(&context, PAGE_SIZE, NZUsize!(BUFFER_SIZE));
400 let mut append = Writer::new(blob, blob_size, BUFFER_SIZE, cache_ref)
401 .await
402 .unwrap();
403
404 let page_size = PAGE_SIZE.get() as usize;
406 let data: Vec<u8> = (0u8..=255).cycle().take(page_size * 2).collect();
407 append.append(&data).await.unwrap();
408 let (sealed, sync) = append.seal().await.unwrap();
409 sync.await.unwrap();
410
411 assert_eq!(sealed.size(), data.len() as u64);
412
413 let mut buf = vec![0u8; data.len()];
415 sealed.read_into(&mut buf, 0).await.unwrap();
416 assert_eq!(buf, data);
417 });
418 }
419
420 #[test_traced("DEBUG")]
422 fn test_seal_partial_only() {
423 let executor = deterministic::Runner::default();
424 executor.start(|context: deterministic::Context| async move {
425 let (blob, blob_size) = context.open("test_partition", b"partial").await.unwrap();
426 let cache_ref =
427 super::CacheRef::from_pooler(&context, PAGE_SIZE, NZUsize!(BUFFER_SIZE));
428 let mut append = Writer::new(blob, blob_size, BUFFER_SIZE, cache_ref)
429 .await
430 .unwrap();
431
432 let data: Vec<u8> = (0u8..=50).collect();
434 append.append(&data).await.unwrap();
435 let (sealed, sync) = append.seal().await.unwrap();
436 sync.await.unwrap();
437
438 assert_eq!(sealed.size(), data.len() as u64);
439
440 let mut buf = vec![0u8; data.len()];
441 sealed.read_into(&mut buf, 0).await.unwrap();
442 assert_eq!(buf, data);
443 });
444 }
445
446 #[test_traced("DEBUG")]
448 fn test_seal_full_plus_partial_straddle() {
449 let executor = deterministic::Runner::default();
450 executor.start(|context: deterministic::Context| async move {
451 let (blob, blob_size) = context.open("test_partition", b"straddle").await.unwrap();
452 let cache_ref =
453 super::CacheRef::from_pooler(&context, PAGE_SIZE, NZUsize!(BUFFER_SIZE));
454 let mut append = Writer::new(blob, blob_size, BUFFER_SIZE, cache_ref)
455 .await
456 .unwrap();
457
458 let page_size = PAGE_SIZE.get() as usize;
460 let total = page_size + 17;
461 let data: Vec<u8> = (0u8..=255).cycle().take(total).collect();
462 append.append(&data).await.unwrap();
463 let (sealed, sync) = append.seal().await.unwrap();
464 sync.await.unwrap();
465
466 assert_eq!(sealed.size(), total as u64);
467
468 let off = (page_size - 5) as u64;
470 let len = 15usize;
471 let mut buf = vec![0u8; len];
472 sealed.read_into(&mut buf, off).await.unwrap();
473 assert_eq!(buf, data[page_size - 5..page_size - 5 + len]);
474
475 let off = page_size as u64;
477 let mut buf = vec![0u8; 10];
478 sealed.read_into(&mut buf, off).await.unwrap();
479 assert_eq!(buf, data[page_size..page_size + 10]);
480
481 let mut buf = vec![0u8; 20];
483 sealed.read_into(&mut buf, 0).await.unwrap();
484 assert_eq!(buf, data[..20]);
485 });
486 }
487
488 #[test_traced("DEBUG")]
490 fn test_sealed_read_at() {
491 let executor = deterministic::Runner::default();
492 executor.start(|context: deterministic::Context| async move {
493 let (blob, blob_size) = context.open("test_partition", b"read_at").await.unwrap();
494 let cache_ref =
495 super::CacheRef::from_pooler(&context, PAGE_SIZE, NZUsize!(BUFFER_SIZE));
496 let mut append = Writer::new(blob, blob_size, BUFFER_SIZE, cache_ref)
497 .await
498 .unwrap();
499
500 let data: Vec<u8> = (0u8..=255).cycle().take(250).collect();
501 append.append(&data).await.unwrap();
502 let (sealed, sync) = append.seal().await.unwrap();
503 sync.await.unwrap();
504
505 let bufs = sealed.read_at(0, data.len()).await.unwrap();
506 let coalesced = bufs.coalesce();
507 assert_eq!(coalesced.as_ref(), data.as_slice());
508 });
509 }
510
511 #[test_traced("DEBUG")]
513 fn test_sealed_read_many_into() {
514 let executor = deterministic::Runner::default();
515 executor.start(|context: deterministic::Context| async move {
516 let (blob, blob_size) = context.open("test_partition", b"rmany").await.unwrap();
517 let cache_ref =
518 super::CacheRef::from_pooler(&context, PAGE_SIZE, NZUsize!(BUFFER_SIZE));
519 let mut append = Writer::new(blob, blob_size, BUFFER_SIZE, cache_ref)
520 .await
521 .unwrap();
522
523 let page_size = PAGE_SIZE.get() as usize;
525 let total = page_size + 50;
526 let data: Vec<u8> = (0u8..=255).cycle().take(total).collect();
527 append.append(&data).await.unwrap();
528 let (sealed, sync) = append.seal().await.unwrap();
529 sync.await.unwrap();
530
531 let offsets = [0u64, (page_size - 2) as u64, (page_size + 10) as u64];
533 let item_size = 4usize;
534 let mut out = vec![0u8; offsets.len() * item_size];
535 sealed
536 .read_many_into(&mut out, &offsets, NZUsize!(item_size))
537 .await
538 .unwrap();
539
540 for (i, &off) in offsets.iter().enumerate() {
541 assert_eq!(
542 &out[i * item_size..(i + 1) * item_size],
543 &data[off as usize..off as usize + item_size],
544 );
545 }
546 });
547 }
548
549 #[test_traced("DEBUG")]
552 fn test_sealed_try_read_many_sync_into() {
553 let executor = deterministic::Runner::default();
554 executor.start(|context: deterministic::Context| async move {
555 let (blob, blob_size) = context.open("test_partition", b"rmany_sync").await.unwrap();
556 let cache_ref = super::CacheRef::from_pooler(&context, PAGE_SIZE, NZUsize!(1));
559 let mut append = Writer::new(blob, blob_size, BUFFER_SIZE, cache_ref)
560 .await
561 .unwrap();
562
563 let page_size = PAGE_SIZE.get() as usize;
565 let total = page_size * 2 + 50;
566 let data: Vec<u8> = (0u8..=255).cycle().take(total).collect();
567 append.append(&data).await.unwrap();
568 let (sealed, sync) = append.seal().await.unwrap();
569 sync.await.unwrap();
570
571 let offsets = [
573 0u64,
574 page_size as u64,
575 (page_size * 2 - 2) as u64,
576 (page_size * 2 + 10) as u64,
577 ];
578 let item_size = 4usize;
579 let check = |out: &[u8], indices: &[usize]| {
580 for &i in indices {
581 let off = offsets[i] as usize;
582 assert_eq!(
583 &out[i * item_size..(i + 1) * item_size],
584 &data[off..off + item_size],
585 );
586 }
587 };
588
589 sealed.read_at(0, page_size).await.unwrap();
592 let mut out = vec![0u8; offsets.len() * item_size];
593 let misses = sealed.try_read_many_sync_into(&mut out, &offsets, NZUsize!(item_size));
594 assert_eq!(misses, vec![1, 2]);
595 check(&out, &[0, 3]);
596
597 sealed.read_at(page_size as u64, page_size).await.unwrap();
599 let mut out = vec![0u8; offsets.len() * item_size];
600 let misses = sealed.try_read_many_sync_into(&mut out, &offsets, NZUsize!(item_size));
601 assert_eq!(misses, vec![0]);
602 check(&out, &[1, 2, 3]);
603 });
604 }
605
606 #[test_traced("DEBUG")]
610 fn test_sealed_try_read_ranges_sync_into() {
611 let executor = deterministic::Runner::default();
612 executor.start(|context: deterministic::Context| async move {
613 let (blob, blob_size) = context
614 .open("test_partition", b"rranges_sync")
615 .await
616 .unwrap();
617 let cache_ref = super::CacheRef::from_pooler(&context, PAGE_SIZE, NZUsize!(1));
620 let mut append = Writer::new(blob, blob_size, BUFFER_SIZE, cache_ref)
621 .await
622 .unwrap();
623
624 let page_size = PAGE_SIZE.get() as usize;
626 let total = page_size * 2 + 50;
627 let data: Vec<u8> = (0u8..=255).cycle().take(total).collect();
628 append.append(&data).await.unwrap();
629 let (sealed, sync) = append.seal().await.unwrap();
630 sync.await.unwrap();
631
632 let ranges = [
635 (0u64, 3usize),
636 (page_size as u64 + 5, 0),
637 (page_size as u64 + 5, 7),
638 ((page_size * 2 - 2) as u64, 4),
639 ((page_size * 2 + 10) as u64, 4),
640 ];
641 let total_len: usize = ranges.iter().map(|&(_, len)| len).sum();
642 let check = |out: &[u8], indices: &[usize]| {
643 let mut start = 0;
644 for (i, &(off, len)) in ranges.iter().enumerate() {
645 if indices.contains(&i) {
646 let off = off as usize;
647 assert_eq!(&out[start..start + len], &data[off..off + len]);
648 }
649 start += len;
650 }
651 };
652
653 sealed.read_at(0, page_size).await.unwrap();
656 let mut out = vec![0u8; total_len];
657 let misses = sealed.try_read_ranges_sync_into(&mut out, &ranges);
658 assert_eq!(misses, vec![2, 3]);
659 check(&out, &[0, 4]);
660
661 sealed.read_at(page_size as u64, page_size).await.unwrap();
663 let mut out = vec![0u8; total_len];
664 let misses = sealed.try_read_ranges_sync_into(&mut out, &ranges);
665 assert_eq!(misses, vec![0]);
666 check(&out, &[1, 2, 3, 4]);
667 });
668 }
669
670 #[test_traced("DEBUG")]
672 fn test_sealed_read_many_into_cache_miss() {
673 let executor = deterministic::Runner::default();
674 executor.start(|context: deterministic::Context| async move {
675 let (blob, blob_size) = context.open("test_partition", b"rmany_miss").await.unwrap();
676 let cache_ref = super::CacheRef::from_pooler(&context, PAGE_SIZE, NZUsize!(1));
677 let mut append = Writer::new(blob, blob_size, BUFFER_SIZE, cache_ref)
678 .await
679 .unwrap();
680
681 let page_size = PAGE_SIZE.get() as usize;
682 let data: Vec<u8> = (0u8..=255).cycle().take(page_size * 2).collect();
683 append.append(&data).await.unwrap();
684 let (sealed, sync) = append.seal().await.unwrap();
685 sync.await.unwrap();
686
687 let offsets = [0u64, page_size as u64];
688 let item_size = 4usize;
689 let mut out = vec![0u8; offsets.len() * item_size];
690 sealed
691 .read_many_into(&mut out, &offsets, NZUsize!(item_size))
692 .await
693 .unwrap();
694
695 for (i, &off) in offsets.iter().enumerate() {
696 assert_eq!(
697 &out[i * item_size..(i + 1) * item_size],
698 &data[off as usize..off as usize + item_size],
699 );
700 }
701 });
702 }
703
704 #[test_traced("DEBUG")]
705 #[should_panic(expected = "ranges must be sorted and non-overlapping")]
706 fn test_sealed_read_many_into_rejects_unsorted_offsets() {
707 let executor = deterministic::Runner::default();
708 executor.start(|context: deterministic::Context| async move {
709 let (blob, blob_size) = context.open("test_partition", b"rmany_bad").await.unwrap();
710 let cache_ref =
711 super::CacheRef::from_pooler(&context, PAGE_SIZE, NZUsize!(BUFFER_SIZE));
712 let mut append = Writer::new(blob, blob_size, BUFFER_SIZE, cache_ref)
713 .await
714 .unwrap();
715 append.append(&[7; 32]).await.unwrap();
716 let (sealed, sync) = append.seal().await.unwrap();
717 sync.await.unwrap();
718
719 let mut out = vec![0u8; 8];
720 let _ = sealed.read_many_into(&mut out, &[8, 4], NZUsize!(4)).await;
721 });
722 }
723
724 #[test_traced("DEBUG")]
726 fn test_sealed_read_many_into_rejects_invalid_offsets() {
727 let executor = deterministic::Runner::default();
728 executor.start(|context: deterministic::Context| async move {
729 let (blob, blob_size) = context.open("test_partition", b"rmany_bad").await.unwrap();
730 let cache_ref =
731 super::CacheRef::from_pooler(&context, PAGE_SIZE, NZUsize!(BUFFER_SIZE));
732 let mut append = Writer::new(blob, blob_size, BUFFER_SIZE, cache_ref)
733 .await
734 .unwrap();
735 append.append(&[7; 32]).await.unwrap();
736 let (sealed, sync) = append.seal().await.unwrap();
737 sync.await.unwrap();
738
739 let mut out = vec![0u8; 8];
740 let err = sealed
741 .read_many_into(&mut out, &[u64::MAX - 1, 8], NZUsize!(4))
742 .await
743 .unwrap_err();
744 assert!(matches!(err, Error::OffsetOverflow));
745
746 let err = sealed
747 .read_many_into(&mut out, &[28, 32], NZUsize!(4))
748 .await
749 .unwrap_err();
750 assert!(matches!(err, Error::BlobInsufficientLength));
751 });
752 }
753
754 #[test_traced("DEBUG")]
756 fn test_sealed_try_read_sync_partial() {
757 let executor = deterministic::Runner::default();
758 executor.start(|context: deterministic::Context| async move {
759 let (blob, blob_size) = context
760 .open("test_partition", b"trs_partial")
761 .await
762 .unwrap();
763 let cache_ref =
764 super::CacheRef::from_pooler(&context, PAGE_SIZE, NZUsize!(BUFFER_SIZE));
765 let mut append = Writer::new(blob, blob_size, BUFFER_SIZE, cache_ref)
766 .await
767 .unwrap();
768
769 let page_size = PAGE_SIZE.get() as usize;
770 let total = page_size + 30;
771 let data: Vec<u8> = (0u8..=255).cycle().take(total).collect();
772 append.append(&data).await.unwrap();
773 let (sealed, sync) = append.seal().await.unwrap();
774 sync.await.unwrap();
775
776 let mut buf = vec![0u8; 10];
778 assert!(sealed.try_read_sync_into(&mut buf, page_size as u64));
779 assert_eq!(buf, data[page_size..page_size + 10]);
780
781 let mut buf = vec![0u8; 10];
783 assert!(!sealed.try_read_sync_into(&mut buf, total as u64));
784 });
785 }
786
787 #[test_traced("DEBUG")]
789 fn test_sealed_try_read_sync_straddles_cached_and_partial() {
790 let executor = deterministic::Runner::default();
791 executor.start(|context: deterministic::Context| async move {
792 let (blob, blob_size) = context
793 .open("test_partition", b"trs_straddle")
794 .await
795 .unwrap();
796 let cache_ref =
797 super::CacheRef::from_pooler(&context, PAGE_SIZE, NZUsize!(BUFFER_SIZE));
798 let mut append = Writer::new(blob, blob_size, BUFFER_SIZE, cache_ref)
799 .await
800 .unwrap();
801
802 let page_size = PAGE_SIZE.get() as usize;
803 let total = page_size + 30;
804 let data: Vec<u8> = (0u8..=255).cycle().take(total).collect();
805 append.append(&data).await.unwrap();
806 let (sealed, sync) = append.seal().await.unwrap();
807 sync.await.unwrap();
808
809 let mut buf = vec![0u8; 12];
810 assert!(sealed.try_read_sync_into(&mut buf, (page_size - 4) as u64));
811 assert_eq!(buf, data[page_size - 4..page_size + 8]);
812 });
813 }
814
815 #[test_traced("DEBUG")]
817 fn test_sealed_try_read_sync_out_of_bounds() {
818 let executor = deterministic::Runner::default();
819 executor.start(|context: deterministic::Context| async move {
820 let (blob, blob_size) = context.open("test_partition", b"trs_fail").await.unwrap();
821 let cache_ref =
822 super::CacheRef::from_pooler(&context, PAGE_SIZE, NZUsize!(BUFFER_SIZE));
823 let mut append = Writer::new(blob, blob_size, BUFFER_SIZE, cache_ref)
824 .await
825 .unwrap();
826
827 let page_size = PAGE_SIZE.get() as usize;
828 let data: Vec<u8> = (0u8..=255).cycle().take(page_size + 5).collect();
829 append.append(&data).await.unwrap();
830 let (sealed, sync) = append.seal().await.unwrap();
831 sync.await.unwrap();
832
833 let mut buf = vec![9u8; 10];
834 assert!(!sealed.try_read_sync_into(&mut buf, data.len() as u64));
835 });
836 }
837
838 #[test_traced("DEBUG")]
840 fn test_sealed_replay() {
841 let executor = deterministic::Runner::default();
842 executor.start(|context: deterministic::Context| async move {
843 let (blob, blob_size) = context.open("test_partition", b"replay").await.unwrap();
844 let cache_ref =
845 super::CacheRef::from_pooler(&context, PAGE_SIZE, NZUsize!(BUFFER_SIZE));
846 let mut append = Writer::new(blob, blob_size, BUFFER_SIZE, cache_ref)
847 .await
848 .unwrap();
849
850 let page_size = PAGE_SIZE.get() as usize;
852 let total = page_size * 2 + 25;
853 let data: Vec<u8> = (0u8..=255).cycle().take(total).collect();
854 append.append(&data).await.unwrap();
855 append.sync().await.unwrap();
856 let (sealed, sync) = append.seal().await.unwrap();
857 sync.await.unwrap();
858
859 let mut replay = sealed
860 .replay(NZUsize!(BUFFER_SIZE), ReadOptions::default())
861 .unwrap();
862 assert_eq!(replay.blob_size(), total as u64);
863
864 let mut out = Vec::with_capacity(total);
866 while replay.ensure(1).await.unwrap() {
867 let chunk = replay.chunk();
868 let copy_len = chunk.len();
869 out.extend_from_slice(chunk);
870 replay.advance(copy_len);
871 }
872 assert_eq!(out, data);
873 });
874 }
875
876 #[test_traced("DEBUG")]
879 fn test_snapshot_replay_stays_frozen_after_writer_growth() {
880 let executor = deterministic::Runner::default();
881 executor.start(|context: deterministic::Context| async move {
882 let (blob, blob_size) = context
883 .open("test_partition", b"snapshot_replay_growth")
884 .await
885 .unwrap();
886 let cache_ref =
887 super::CacheRef::from_pooler(&context, PAGE_SIZE, NZUsize!(BUFFER_SIZE));
888 let mut writer = Writer::new(blob, blob_size, BUFFER_SIZE, cache_ref)
889 .await
890 .unwrap();
891
892 let page_size = PAGE_SIZE.get() as usize;
893 let mut original = vec![0xAA; page_size];
894 original.extend_from_slice(b"old");
895 writer.append(&original).await.unwrap();
896 writer.sync().await.unwrap();
897
898 let snapshot = writer.snapshot().await.unwrap();
899 let snapshot_bytes = snapshot
900 .read_at(0, snapshot.size() as usize)
901 .await
902 .unwrap()
903 .coalesce();
904 let mut replay = snapshot
905 .replay(NZUsize!(BUFFER_SIZE), ReadOptions::default())
906 .unwrap();
907 assert_eq!(replay.blob_size(), original.len() as u64);
908
909 writer.append(b"newtail").await.unwrap();
910 writer.sync().await.unwrap();
911
912 let mut out = Vec::new();
913 while replay.ensure(1).await.unwrap() {
914 let chunk = replay.chunk();
915 let copy_len = chunk.len();
916 out.extend_from_slice(chunk);
917 replay.advance(copy_len);
918 }
919
920 assert_eq!(out.as_slice(), snapshot_bytes.as_ref());
921 assert_eq!(out, original);
922 });
923 }
924
925 #[test_traced("DEBUG")]
928 fn test_seal_replay_without_sync() {
929 let executor = deterministic::Runner::default();
930 executor.start(|context: deterministic::Context| async move {
931 let blob = SyncTrackingBlob::new();
932 let cache_ref =
933 super::CacheRef::from_pooler(&context, PAGE_SIZE, NZUsize!(BUFFER_SIZE));
934 let mut append = Writer::new(blob.clone(), 0, BUFFER_SIZE, cache_ref)
935 .await
936 .unwrap();
937
938 let page_size = PAGE_SIZE.get() as usize;
939 let total = page_size * 2 + 25;
940 let data: Vec<u8> = (0u8..=255).cycle().take(total).collect();
941 append.append(&data).await.unwrap();
942 let (sealed, sync) = append.seal().await.unwrap();
943
944 let (_durable, _writes, full_syncs, range_syncs) = blob.snapshot();
945 assert_eq!(full_syncs, 1);
946 assert_eq!(range_syncs, 0);
947 sync.await.unwrap();
948
949 let mut replay = sealed
950 .replay(NZUsize!(BUFFER_SIZE), ReadOptions::default())
951 .unwrap();
952 assert_eq!(replay.blob_size(), total as u64);
953
954 let mut out = Vec::with_capacity(total);
955 while replay.ensure(1).await.unwrap() {
956 let chunk = replay.chunk();
957 let copy_len = chunk.len();
958 out.extend_from_slice(chunk);
959 replay.advance(copy_len);
960 }
961 assert_eq!(out, data);
962 });
963 }
964
965 #[test_traced("DEBUG")]
968 fn test_sealed_reads_while_seal_sync_pending() {
969 let executor = deterministic::Runner::default();
970 executor.start(|context: deterministic::Context| async move {
971 let inner = SyncTrackingBlob::new();
972 let (blob, pending) = DelayedSyncBlob::new(inner);
973 let cache_ref =
974 super::CacheRef::from_pooler(&context, PAGE_SIZE, NZUsize!(BUFFER_SIZE));
975 let mut append = Writer::new(blob, 0, BUFFER_SIZE, cache_ref).await.unwrap();
976
977 let page_size = PAGE_SIZE.get() as usize;
978 let total = page_size * 2 + 50;
979 let data: Vec<u8> = (0u8..=255).cycle().take(total).collect();
980 append.append(&data).await.unwrap();
981
982 let (sealed, sync) = append.seal().await.unwrap();
984 assert_eq!(pending.lock().len(), 1, "the seal sync should be parked");
985
986 let read = sealed.read_at(0, total).await.unwrap().coalesce();
987 assert_eq!(read.as_ref(), &data[..]);
988
989 let mut replay = sealed
990 .replay(NZUsize!(BUFFER_SIZE), ReadOptions::default())
991 .unwrap();
992 assert_eq!(replay.blob_size(), total as u64);
993 let mut replayed = Vec::new();
994 while replay.ensure(1).await.unwrap() {
995 let n = {
996 let chunk = replay.chunk();
997 replayed.extend_from_slice(chunk);
998 chunk.len()
999 };
1000 replay.advance(n);
1001 }
1002 assert_eq!(replayed, data);
1003 assert_eq!(
1004 pending.lock().len(),
1005 1,
1006 "reads must not consume the pending sync"
1007 );
1008
1009 next_pending_sync(&pending).release.send(Ok(())).unwrap();
1011 sync.await.unwrap();
1012 });
1013 }
1014
1015 #[test_traced("DEBUG")]
1017 fn test_seal_recovered_synced_partial_page_no_write() {
1018 let executor = deterministic::Runner::default();
1019 executor.start(|context: deterministic::Context| async move {
1020 let blob = SyncTrackingBlob::new();
1021 let cache_ref =
1022 super::CacheRef::from_pooler(&context, PAGE_SIZE, NZUsize!(BUFFER_SIZE));
1023 let data: Vec<u8> = (0u8..=255)
1024 .cycle()
1025 .take(PAGE_SIZE.get() as usize - 17)
1026 .collect();
1027
1028 {
1029 let mut writer = Writer::new(blob.clone(), 0, BUFFER_SIZE, cache_ref.clone())
1030 .await
1031 .unwrap();
1032 writer.append(&data).await.unwrap();
1033 writer.sync().await.unwrap();
1034 }
1035
1036 let (_, writes, full_syncs, range_syncs) = blob.snapshot();
1037 let mut recovered = Writer::new(blob.clone(), blob.size(), BUFFER_SIZE, cache_ref)
1038 .await
1039 .unwrap();
1040 assert_eq!(recovered.size(), data.len() as u64);
1041
1042 recovered.sync().await.unwrap();
1043 let (_, writes_after_sync, full_after_sync, range_after_sync) = blob.snapshot();
1044 assert_eq!(
1045 writes_after_sync, writes,
1046 "syncing an unchanged recovered partial page must not rewrite it"
1047 );
1048 assert_eq!(full_after_sync, full_syncs + 1);
1049 assert_eq!(range_after_sync, range_syncs);
1050
1051 let (sealed, sync) = recovered.seal().await.unwrap();
1052 sync.await.unwrap();
1053 let (_, writes_after_seal, full_after_seal, range_after_seal) = blob.snapshot();
1054 assert_eq!(
1055 writes_after_seal, writes_after_sync,
1056 "sealing an unchanged recovered partial page must not rewrite it"
1057 );
1058 assert_eq!(full_after_seal, full_after_sync);
1059 assert_eq!(range_after_seal, range_after_sync);
1060
1061 let read = sealed.read_at(0, data.len()).await.unwrap().coalesce();
1062 assert_eq!(read.as_ref(), data.as_slice());
1063 });
1064 }
1065}