LCOV - code coverage report
Current view: top level - pageserver/src/tenant/ephemeral_file - zero_padded_read_write.rs (source / functions) Coverage Total Hit
Test: 12c2fc96834f59604b8ade5b9add28f1dce41ec6.info Lines: 81.4 % 70 57
Test Date: 2024-07-03 15:33:13 Functions: 87.5 % 8 7

            Line data    Source code
       1              : //! The heart of how [`super::EphemeralFile`] does its reads and writes.
       2              : //!
       3              : //! # Writes
       4              : //!
       5              : //! [`super::EphemeralFile`] writes small, borrowed buffers using [`RW::write_all_borrowed`].
       6              : //! The [`RW`] batches these into [`TAIL_SZ`] bigger writes, using [`owned_buffers_io::write::BufferedWriter`].
       7              : //!
       8              : //! # Reads
       9              : //!
      10              : //! [`super::EphemeralFile`] always reads full [`PAGE_SZ`]ed blocks using [`RW::read_blk`].
      11              : //!
      12              : //! The [`RW`] serves these reads either from the buffered writer's in-memory buffer
      13              : //! or redirects the caller to read from the underlying [`OwnedAsyncWriter`]
      14              : //! if the read is for the prefix that has already been flushed.
      15              : //!
      16              : //! # Current Usage
      17              : //!
      18              : //! The current user of this module is [`super::page_caching::RW`].
      19              : 
      20              : mod zero_padded;
      21              : 
      22              : use crate::{
      23              :     context::RequestContext,
      24              :     page_cache::PAGE_SZ,
      25              :     virtual_file::owned_buffers_io::{
      26              :         self,
      27              :         write::{Buffer, OwnedAsyncWriter},
      28              :     },
      29              : };
      30              : 
      31              : const TAIL_SZ: usize = 64 * 1024;
      32              : 
      33              : /// See module-level comment.
      34              : pub struct RW<W: OwnedAsyncWriter> {
      35              :     buffered_writer: owned_buffers_io::write::BufferedWriter<
      36              :         zero_padded::Buffer<TAIL_SZ>,
      37              :         owned_buffers_io::util::size_tracking_writer::Writer<W>,
      38              :     >,
      39              : }
      40              : 
      41              : pub enum ReadResult<'a, W> {
      42              :     NeedsReadFromWriter { writer: &'a W },
      43              :     ServedFromZeroPaddedMutableTail { buffer: &'a [u8; PAGE_SZ] },
      44              : }
      45              : 
      46              : impl<W> RW<W>
      47              : where
      48              :     W: OwnedAsyncWriter,
      49              : {
      50         1246 :     pub fn new(writer: W) -> Self {
      51         1246 :         let bytes_flushed_tracker =
      52         1246 :             owned_buffers_io::util::size_tracking_writer::Writer::new(writer);
      53         1246 :         let buffered_writer = owned_buffers_io::write::BufferedWriter::new(
      54         1246 :             bytes_flushed_tracker,
      55         1246 :             zero_padded::Buffer::default(),
      56         1246 :         );
      57         1246 :         Self { buffered_writer }
      58         1246 :     }
      59              : 
      60      4303610 :     pub(crate) fn as_writer(&self) -> &W {
      61      4303610 :         self.buffered_writer.as_inner().as_inner()
      62      4303610 :     }
      63              : 
      64     10221312 :     pub async fn write_all_borrowed(
      65     10221312 :         &mut self,
      66     10221312 :         buf: &[u8],
      67     10221312 :         ctx: &RequestContext,
      68     10221312 :     ) -> std::io::Result<usize> {
      69     10221312 :         self.buffered_writer.write_buffered_borrowed(buf, ctx).await
      70     10221312 :     }
      71              : 
      72     10202350 :     pub fn bytes_written(&self) -> u64 {
      73     10202350 :         let flushed_offset = self.buffered_writer.as_inner().bytes_written();
      74     10202350 :         let buffer: &zero_padded::Buffer<TAIL_SZ> = self.buffered_writer.inspect_buffer();
      75     10202350 :         flushed_offset + u64::try_from(buffer.pending()).unwrap()
      76     10202350 :     }
      77              : 
      78              :     /// Get a slice of all blocks that [`Self::read_blk`] would return as [`ReadResult::ServedFromZeroPaddedMutableTail`].
      79            0 :     pub fn get_tail_zero_padded(&self) -> &[u8] {
      80            0 :         let buffer: &zero_padded::Buffer<TAIL_SZ> = self.buffered_writer.inspect_buffer();
      81            0 :         let buffer_written_up_to = buffer.pending();
      82              :         // pad to next page boundary
      83            0 :         let read_up_to = if buffer_written_up_to % PAGE_SZ == 0 {
      84            0 :             buffer_written_up_to
      85              :         } else {
      86            0 :             buffer_written_up_to
      87            0 :                 .checked_add(PAGE_SZ - (buffer_written_up_to % PAGE_SZ))
      88            0 :                 .unwrap()
      89              :         };
      90            0 :         &buffer.as_zero_padded_slice()[0..read_up_to]
      91            0 :     }
      92              : 
      93      4955837 :     pub(crate) async fn read_blk(&self, blknum: u32) -> Result<ReadResult<'_, W>, std::io::Error> {
      94      4955837 :         let flushed_offset = self.buffered_writer.as_inner().bytes_written();
      95      4955837 :         let buffer: &zero_padded::Buffer<TAIL_SZ> = self.buffered_writer.inspect_buffer();
      96      4955837 :         let buffered_offset = flushed_offset + u64::try_from(buffer.pending()).unwrap();
      97      4955837 :         let read_offset = (blknum as u64) * (PAGE_SZ as u64);
      98              : 
      99              :         // The trailing page ("block") might only be partially filled,
     100              :         // yet the blob_io code relies on us to return a full PAGE_SZed slice anyway.
     101              :         // Moreover, it has to be zero-padded, because when we still had
     102              :         // a write-back page cache, it provided pre-zeroed pages, and blob_io came to rely on it.
     103              :         // DeltaLayer probably has the same issue, not sure why it needs no special treatment.
     104              :         // => check here that the read doesn't go beyond this potentially trailing
     105              :         // => the zero-padding is done in the `else` branch below
     106      4955837 :         let blocks_written = if buffered_offset % (PAGE_SZ as u64) == 0 {
     107           64 :             buffered_offset / (PAGE_SZ as u64)
     108              :         } else {
     109      4955773 :             (buffered_offset / (PAGE_SZ as u64)) + 1
     110              :         };
     111      4955837 :         if (blknum as u64) >= blocks_written {
     112            0 :             return Err(std::io::Error::new(std::io::ErrorKind::Other, anyhow::anyhow!("read past end of ephemeral_file: read=0x{read_offset:x} buffered=0x{buffered_offset:x} flushed=0x{flushed_offset}")));
     113      4955837 :         }
     114      4955837 : 
     115      4955837 :         // assertions for the `if-else` below
     116      4955837 :         assert_eq!(
     117      4955837 :             flushed_offset % (TAIL_SZ as u64), 0,
     118            0 :             "we only use write_buffered_borrowed to write to the buffered writer, so it's guaranteed that flushes happen buffer.cap()-sized chunks"
     119              :         );
     120      4955837 :         assert_eq!(
     121      4955837 :             flushed_offset % (PAGE_SZ as u64),
     122              :             0,
     123            0 :             "the logic below can't handle if the page is spread across the flushed part and the buffer"
     124              :         );
     125              : 
     126      4955837 :         if read_offset < flushed_offset {
     127      4302492 :             assert!(read_offset + (PAGE_SZ as u64) <= flushed_offset);
     128      4302492 :             Ok(ReadResult::NeedsReadFromWriter {
     129      4302492 :                 writer: self.as_writer(),
     130      4302492 :             })
     131              :         } else {
     132       653345 :             let read_offset_in_buffer = read_offset
     133       653345 :                 .checked_sub(flushed_offset)
     134       653345 :                 .expect("would have taken `if` branch instead of this one");
     135       653345 :             let read_offset_in_buffer = usize::try_from(read_offset_in_buffer).unwrap();
     136       653345 :             let zero_padded_slice = buffer.as_zero_padded_slice();
     137       653345 :             let page = &zero_padded_slice[read_offset_in_buffer..(read_offset_in_buffer + PAGE_SZ)];
     138       653345 :             Ok(ReadResult::ServedFromZeroPaddedMutableTail {
     139       653345 :                 buffer: page
     140       653345 :                     .try_into()
     141       653345 :                     .expect("the slice above got it as page-size slice"),
     142       653345 :             })
     143              :         }
     144      4955837 :     }
     145              : }
        

Generated by: LCOV version 2.1-beta