heed/
reserved_space.rs

1use std::mem::MaybeUninit;
2use std::{fmt, io};
3
4use crate::mdb::ffi;
5
6/// A structure that is used to improve the write speed in LMDB.
7///
8/// You must write the exact amount of bytes, no less, no more.
9pub struct ReservedSpace<'a> {
10    bytes: &'a mut [MaybeUninit<u8>],
11    /// Number of bytes which have been written: all bytes in `0..written`.
12    written: usize,
13    /// Index of the byte which should be written next.
14    ///
15    /// # Safety
16    ///
17    /// To ensure there are no unwritten gaps in the buffer this should be kept in the range
18    /// `0..=written` at all times.
19    write_head: usize,
20}
21
22impl ReservedSpace<'_> {
23    pub(crate) unsafe fn from_val<'a>(val: ffi::MDB_val) -> ReservedSpace<'a> {
24        let len = val.mv_size;
25        let ptr = val.mv_data;
26
27        ReservedSpace {
28            bytes: std::slice::from_raw_parts_mut(ptr.cast(), len),
29            written: 0,
30            write_head: 0,
31        }
32    }
33
34    /// The total number of bytes that this memory buffer has.
35    #[inline]
36    pub fn size(&self) -> usize {
37        self.bytes.len()
38    }
39
40    /// The remaining number of bytes that this memory buffer has.
41    #[inline]
42    pub fn remaining(&self) -> usize {
43        self.bytes.len() - self.write_head
44    }
45
46    /// Get a slice of all the bytes that have previously been written.
47    ///
48    /// This can be used to write information which cannot be known until the very end of
49    /// serialization. For example, this method can be used to serialize a value, then compute a
50    /// checksum over the bytes, and then write that checksum to a header at the start of the
51    /// reserved space.
52    #[inline]
53    pub fn written_mut(&mut self) -> &mut [u8] {
54        let ptr = self.bytes.as_mut_ptr();
55        let len = self.written;
56        unsafe { std::slice::from_raw_parts_mut(ptr.cast(), len) }
57    }
58
59    /// Fills the remaining reserved space with zeroes.
60    ///
61    /// This can be used together with [`written_mut`](Self::written_mut) to get a mutable view of
62    /// the entire reserved space.
63    ///
64    /// ### Note
65    ///
66    /// After calling this function, the entire space is considered to be filled and any
67    /// further attempt to [`write`](std::io::Write::write) anything else will fail.
68    #[inline]
69    pub fn fill_zeroes(&mut self) {
70        self.bytes[self.write_head..].fill(MaybeUninit::new(0));
71        self.written = self.bytes.len();
72        self.write_head = self.bytes.len();
73    }
74
75    /// Get a slice of bytes corresponding to the *entire* reserved space.
76    ///
77    /// It is safe to write to any byte within the slice. However, for a write past the end of the
78    /// prevously written bytes to take effect, [`assume_written`](Self::assume_written) has to be
79    /// called to mark those bytes as initialized.
80    ///
81    /// # Safety
82    ///
83    /// As the memory comes from within the database itself, the bytes may not yet be
84    /// initialized. Thus, it is up to the caller to ensure that only initialized memory is read
85    /// (ensured by the [`MaybeUninit`] API).
86    #[inline]
87    pub fn as_uninit_mut(&mut self) -> &mut [MaybeUninit<u8>] {
88        self.bytes
89    }
90
91    /// Marks the bytes in the range `0..len` as being initialized by advancing the internal write
92    /// pointer.
93    ///
94    /// # Safety
95    ///
96    /// The caller guarantees that all bytes in the range have been initialized.
97    #[inline]
98    pub unsafe fn assume_written(&mut self, len: usize) {
99        debug_assert!(len <= self.bytes.len());
100        self.written = len;
101        self.write_head = len;
102    }
103}
104
105impl io::Write for ReservedSpace<'_> {
106    #[inline]
107    fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
108        self.write_all(buf)?;
109        Ok(buf.len())
110    }
111
112    #[inline]
113    fn write_all(&mut self, buf: &[u8]) -> io::Result<()> {
114        let remaining = unsafe { self.bytes.get_unchecked_mut(self.write_head..) };
115
116        if buf.len() > remaining.len() {
117            return Err(io::Error::from(io::ErrorKind::WriteZero));
118        }
119
120        unsafe {
121            // SAFETY: we can always cast `T` -> `MaybeUninit<T>` as it's a transparent wrapper
122            let buf_uninit = std::slice::from_raw_parts(buf.as_ptr().cast(), buf.len());
123            remaining.as_mut_ptr().copy_from_nonoverlapping(buf_uninit.as_ptr(), buf.len());
124        }
125
126        self.write_head += buf.len();
127        self.written = usize::max(self.written, self.write_head);
128
129        Ok(())
130    }
131
132    #[inline(always)]
133    fn flush(&mut self) -> io::Result<()> {
134        Ok(())
135    }
136}
137
138/// ## Note
139///
140/// May only seek within the previously written space.
141/// Attempts to do otherwise will result in an error.
142impl io::Seek for ReservedSpace<'_> {
143    #[inline]
144    fn seek(&mut self, pos: io::SeekFrom) -> io::Result<u64> {
145        let (base, offset) = match pos {
146            io::SeekFrom::Start(start) => (start, 0),
147            io::SeekFrom::End(offset) => (self.written as u64, offset),
148            io::SeekFrom::Current(offset) => (self.write_head as u64, offset),
149        };
150
151        let Some(new_pos) = base.checked_add_signed(offset) else {
152            return Err(std::io::Error::new(
153                std::io::ErrorKind::InvalidInput,
154                "cannot seek before start of reserved space",
155            ));
156        };
157
158        if new_pos > self.written as u64 {
159            return Err(std::io::Error::new(
160                std::io::ErrorKind::InvalidInput,
161                "cannot seek past end of reserved space",
162            ));
163        }
164
165        self.write_head = new_pos as usize;
166
167        Ok(new_pos)
168    }
169
170    #[inline]
171    fn rewind(&mut self) -> io::Result<()> {
172        self.write_head = 0;
173        Ok(())
174    }
175
176    #[inline]
177    fn stream_position(&mut self) -> io::Result<u64> {
178        Ok(self.write_head as u64)
179    }
180}
181
182impl fmt::Debug for ReservedSpace<'_> {
183    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
184        f.debug_struct("ReservedSpace").finish()
185    }
186}