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 1240 : fn default() -> Self {
16 1240 : Self {
17 1240 : allocation: Box::new(
18 1240 : // SAFETY: zeroed memory is a valid [u8; N]
19 1240 : unsafe { MaybeUninit::zeroed().assume_init() },
20 1240 : ),
21 1240 : written: 0,
22 1240 : }
23 1240 : }
24 : }
25 :
26 : impl<const N: usize> Buffer<N> {
27 : #[inline(always)]
28 20468840 : fn invariants(&self) {
29 20468840 : // don't check by default, unoptimized is too expensive even for debug mode
30 20468840 : 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 20468840 : }
34 20468840 : }
35 :
36 653364 : pub fn as_zero_padded_slice(&self) -> &[u8; N] {
37 653364 : &self.allocation
38 653364 : }
39 : }
40 :
41 : impl<const N: usize> crate::virtual_file::owned_buffers_io::write::Buffer for Buffer<N> {
42 : type IoBuf = Self;
43 :
44 20462230 : fn cap(&self) -> usize {
45 20462230 : self.allocation.len()
46 20462230 : }
47 :
48 10227810 : fn extend_from_slice(&mut self, other: &[u8]) {
49 10227810 : self.invariants();
50 10227810 : let remaining = self.allocation.len() - self.written;
51 10227810 : if other.len() > remaining {
52 0 : panic!("calling extend_from_slice() with insufficient remaining capacity");
53 10227810 : }
54 10227810 : self.allocation[self.written..(self.written + other.len())].copy_from_slice(other);
55 10227810 : self.written += other.len();
56 10227810 : self.invariants();
57 10227810 : }
58 :
59 35626836 : fn pending(&self) -> usize {
60 35626836 : self.written
61 35626836 : }
62 :
63 6610 : fn flush(self) -> tokio_epoll_uring::Slice<Self> {
64 6610 : self.invariants();
65 6610 : let written = self.written;
66 6610 : tokio_epoll_uring::BoundedBuf::slice(self, 0..written)
67 6610 : }
68 :
69 6610 : fn reuse_after_flush(iobuf: Self::IoBuf) -> Self {
70 6610 : let Self {
71 6610 : mut allocation,
72 6610 : written,
73 6610 : } = iobuf;
74 6610 : allocation[0..written].fill(0);
75 6610 : let new = Self {
76 6610 : allocation,
77 6610 : written: 0,
78 6610 : };
79 6610 : new.invariants();
80 6610 : new
81 6610 : }
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 102455 : fn stable_ptr(&self) -> *const u8 {
97 102455 : self.allocation.as_ptr()
98 102455 : }
99 :
100 135505 : fn bytes_init(&self) -> usize {
101 135505 : // Yes, N, not self.written; Read the full comment of this impl block!
102 135505 : N
103 135505 : }
104 :
105 19830 : fn bytes_total(&self) -> usize {
106 19830 : N
107 19830 : }
108 : }
|