Line data Source code
1 : //! A [`crate::virtual_file::owned_buffers_io::write::Buffer`] whose
2 : //! unwritten range is guaranteed to be zero-initialized.
3 : //! This is used by [`crate::tenant::ephemeral_file::zero_padded_read_write::RW::read_blk`]
4 : //! to serve page-sized reads of the trailing page when the trailing page has only been partially filled.
5 :
6 : use std::mem::MaybeUninit;
7 :
8 : /// See module-level comment.
9 : pub struct Buffer<const N: usize> {
10 : allocation: Box<[u8; N]>,
11 : written: usize,
12 : }
13 :
14 : impl<const N: usize> Default for Buffer<N> {
15 1052 : fn default() -> Self {
16 1052 : Self {
17 1052 : allocation: Box::new(
18 1052 : // SAFETY: zeroed memory is a valid [u8; N]
19 1052 : unsafe { MaybeUninit::zeroed().assume_init() },
20 1052 : ),
21 1052 : written: 0,
22 1052 : }
23 1052 : }
24 : }
25 :
26 : impl<const N: usize> Buffer<N> {
27 : #[inline(always)]
28 20371294 : fn invariants(&self) {
29 20371294 : // don't check by default, unoptimized is too expensive even for debug mode
30 20371294 : if false {
31 0 : debug_assert!(self.written <= N, "{}", self.written);
32 0 : debug_assert!(self.allocation[self.written..N].iter().all(|v| *v == 0));
33 20371294 : }
34 20371294 : }
35 :
36 649156 : pub fn as_zero_padded_slice(&self) -> &[u8; N] {
37 649156 : &self.allocation
38 649156 : }
39 : }
40 :
41 : impl<const N: usize> crate::virtual_file::owned_buffers_io::write::Buffer for Buffer<N> {
42 : type IoBuf = Self;
43 :
44 20364708 : fn cap(&self) -> usize {
45 20364708 : self.allocation.len()
46 20364708 : }
47 :
48 10179061 : fn extend_from_slice(&mut self, other: &[u8]) {
49 10179061 : self.invariants();
50 10179061 : let remaining = self.allocation.len() - self.written;
51 10179061 : if other.len() > remaining {
52 0 : panic!("calling extend_from_slice() with insufficient remaining capacity");
53 10179061 : }
54 10179061 : self.allocation[self.written..(self.written + other.len())].copy_from_slice(other);
55 10179061 : self.written += other.len();
56 10179061 : self.invariants();
57 10179061 : }
58 :
59 40234558 : fn pending(&self) -> usize {
60 40234558 : self.written
61 40234558 : }
62 :
63 6586 : fn flush(self) -> tokio_epoll_uring::Slice<Self> {
64 6586 : self.invariants();
65 6586 : let written = self.written;
66 6586 : tokio_epoll_uring::BoundedBuf::slice(self, 0..written)
67 6586 : }
68 :
69 6586 : fn reuse_after_flush(iobuf: Self::IoBuf) -> Self {
70 6586 : let Self {
71 6586 : mut allocation,
72 6586 : written,
73 6586 : } = iobuf;
74 6586 : allocation[0..written].fill(0);
75 6586 : let new = Self {
76 6586 : allocation,
77 6586 : written: 0,
78 6586 : };
79 6586 : new.invariants();
80 6586 : new
81 6586 : }
82 : }
83 :
84 : /// We have this trait impl so that the `flush` method in the `Buffer` impl above can produce a
85 : /// [`tokio_epoll_uring::BoundedBuf::slice`] of the [`Self::written`] range of the data.
86 : ///
87 : /// Remember that bytes_init is generally _not_ a tracker of the amount
88 : /// of valid data in the io buffer; we use `Slice` for that.
89 : /// The `IoBuf` is _only_ for keeping track of uninitialized memory, a bit like MaybeUninit.
90 : ///
91 : /// SAFETY:
92 : ///
93 : /// The [`Self::allocation`] is stable becauses boxes are stable.
94 : /// The memory is zero-initialized, so, bytes_init is always N.
95 : unsafe impl<const N: usize> tokio_epoll_uring::IoBuf for Buffer<N> {
96 102083 : fn stable_ptr(&self) -> *const u8 {
97 102083 : self.allocation.as_ptr()
98 102083 : }
99 :
100 135013 : fn bytes_init(&self) -> usize {
101 135013 : // Yes, N, not self.written; Read the full comment of this impl block!
102 135013 : N
103 135013 : }
104 :
105 19758 : fn bytes_total(&self) -> usize {
106 19758 : N
107 19758 : }
108 : }
|