Skip to main content

turbo_persistence/
arc_bytes.rs

1use std::{
2    borrow::Borrow,
3    fmt::{self, Debug, Formatter},
4    hash::{Hash, Hasher},
5    io::{self, BorrowedCursor},
6    ops::{Deref, Range},
7    sync::Arc,
8};
9
10#[cfg(feature = "mmap")]
11use memmap2::Mmap;
12
13use crate::{
14    Compression,
15    compression::decompress_into_arc,
16    shared_bytes::{INLINE_CAPACITY, SharedBytes, fill_uninit, is_subslice_of},
17};
18/// The representation of an `ArcBytes`.
19///
20/// For the ref-counted variants the handle is never read directly — it exists solely to keep the
21/// backing memory alive while `data` points into it. `Inline` instead owns its bytes, so it has no
22/// `data` pointer: one would dangle as soon as the value moved.
23#[derive(Clone)]
24enum Repr {
25    Arc {
26        data: *const [u8],
27        _backing: Arc<[u8]>,
28    },
29    #[cfg(feature = "mmap")]
30    Mmap {
31        data: *const [u8],
32        _backing: Arc<Mmap>,
33    },
34    /// Bytes stored in place, for slices up to [`INLINE_CAPACITY`].
35    Inline { buf: [u8; INLINE_CAPACITY], len: u8 },
36}
37
38/// An owned byte slice backed by an `Arc<[u8]>`, a memory-mapped file, or — for short slices — an
39/// inline buffer that avoids touching a refcount at all.
40#[derive(Clone)]
41pub struct ArcBytes {
42    repr: Repr,
43}
44
45impl ArcBytes {
46    /// The ref-counted bytes this slice points into, or `None` when stored inline.
47    #[inline]
48    fn backing_bytes(&self) -> Option<&[u8]> {
49        match &self.repr {
50            Repr::Arc { _backing, .. } => Some(_backing),
51            #[cfg(feature = "mmap")]
52            Repr::Mmap { _backing, .. } => Some(_backing),
53            Repr::Inline { .. } => None,
54        }
55    }
56}
57
58unsafe impl Send for ArcBytes {}
59unsafe impl Sync for ArcBytes {}
60
61impl From<Arc<[u8]>> for ArcBytes {
62    fn from(arc: Arc<[u8]>) -> Self {
63        Self {
64            repr: Repr::Arc {
65                data: &*arc as *const [u8],
66                _backing: arc,
67            },
68        }
69    }
70}
71
72impl From<Box<[u8]>> for ArcBytes {
73    fn from(b: Box<[u8]>) -> Self {
74        Self::from(Arc::from(b))
75    }
76}
77
78impl Deref for ArcBytes {
79    type Target = [u8];
80
81    fn deref(&self) -> &Self::Target {
82        match &self.repr {
83            // SAFETY: `data` points into the backing held by the same variant, which keeps it
84            // alive for as long as `self`.
85            Repr::Arc { data, .. } => unsafe { &**data },
86            #[cfg(feature = "mmap")]
87            Repr::Mmap { data, .. } => unsafe { &**data },
88            // Borrowed from `self`, so this is recomputed after a move rather than stored.
89            Repr::Inline { buf, len } => &buf[..*len as usize],
90        }
91    }
92}
93
94impl Borrow<[u8]> for ArcBytes {
95    fn borrow(&self) -> &[u8] {
96        self
97    }
98}
99
100impl Hash for ArcBytes {
101    fn hash<H: Hasher>(&self, state: &mut H) {
102        self.deref().hash(state)
103    }
104}
105
106impl PartialEq for ArcBytes {
107    fn eq(&self, other: &Self) -> bool {
108        self.deref().eq(other.deref())
109    }
110}
111
112impl Debug for ArcBytes {
113    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
114        Debug::fmt(&**self, f)
115    }
116}
117
118impl Eq for ArcBytes {}
119
120impl ArcBytes {
121    /// Returns `true` if this `ArcBytes` is backed by a memory-mapped file.
122    pub fn is_mmap_backed(&self) -> bool {
123        #[cfg(feature = "mmap")]
124        return matches!(self.repr, Repr::Mmap { .. });
125
126        #[cfg(not(feature = "mmap"))]
127        false
128    }
129
130    /// Returns `true` if the backing `Arc` allocation is shared (i.e., there
131    /// are other `Arc` clones referencing the same data outside the cache).
132    /// Always returns `false` for mmap-backed bytes, since the mmap `Arc` is
133    /// shared across all slices from the same file and is not a useful signal.
134    pub fn is_shared_arc(&self) -> bool {
135        match &self.repr {
136            Repr::Arc { _backing, .. } => Arc::strong_count(_backing) > 1,
137            #[cfg(feature = "mmap")]
138            Repr::Mmap { .. } => false,
139            Repr::Inline { .. } => false,
140        }
141    }
142}
143
144impl SharedBytes for ArcBytes {
145    #[cfg(feature = "mmap")]
146    type MmapHandle = Arc<Mmap>;
147
148    fn slice(self, range: Range<usize>) -> Self {
149        let sliced = &self[range];
150        // Inline bytes have no backing to carry over, so re-inline the sub-range.
151        if let Repr::Inline { .. } = self.repr {
152            return Self::from_inline(sliced);
153        }
154        let data = sliced as *const [u8];
155        Self {
156            repr: match self.repr {
157                Repr::Arc { _backing, .. } => Repr::Arc { data, _backing },
158                #[cfg(feature = "mmap")]
159                Repr::Mmap { _backing, .. } => Repr::Mmap { data, _backing },
160                Repr::Inline { .. } => unreachable!("handled above"),
161            },
162        }
163    }
164
165    unsafe fn slice_from_subslice(&self, subslice: &[u8]) -> Self {
166        // Short slices are copied instead of pointed at, so the result owns its bytes and the
167        // caller's backing can be dropped. This is the common case on the lookup path: an inline
168        // value or a key-value tombstone payload, both of which live in a key block, so copying
169        // here is what lets a lookup avoid keeping that block alive.
170        if subslice.len() <= INLINE_CAPACITY {
171            return Self::from_inline(subslice);
172        }
173        debug_assert!(
174            self.backing_bytes()
175                .is_some_and(|backing| is_subslice_of(subslice, backing)),
176            "slice_from_subslice: subslice is not within the backing storage"
177        );
178        let data = subslice as *const [u8];
179        Self {
180            repr: match &self.repr {
181                Repr::Arc { _backing, .. } => Repr::Arc {
182                    data,
183                    _backing: _backing.clone(),
184                },
185                #[cfg(feature = "mmap")]
186                Repr::Mmap { _backing, .. } => Repr::Mmap {
187                    data,
188                    _backing: _backing.clone(),
189                },
190                // Unreachable for a well-formed caller: an inline slice is at most
191                // INLINE_CAPACITY, so it took the branch above.
192                Repr::Inline { .. } => return Self::from_inline(subslice),
193            },
194        }
195    }
196
197    #[cfg(feature = "mmap")]
198    unsafe fn from_mmap(mmap: &Arc<Mmap>, subslice: &[u8]) -> Self {
199        debug_assert!(
200            is_subslice_of(subslice, mmap),
201            "from_mmap: subslice is not within the mmap"
202        );
203        ArcBytes {
204            repr: Repr::Mmap {
205                data: subslice as *const [u8],
206                _backing: mmap.clone(),
207            },
208        }
209    }
210
211    fn from_decompressed(
212        compression: Compression,
213        uncompressed_length: u32,
214        block: &[u8],
215    ) -> anyhow::Result<Self> {
216        Ok(ArcBytes::from(decompress_into_arc(
217            compression,
218            uncompressed_length,
219            block,
220        )?))
221    }
222
223    fn read_filled(
224        len: usize,
225        fill: impl FnOnce(BorrowedCursor<'_, u8>) -> io::Result<()>,
226    ) -> io::Result<Self> {
227        let mut bytes = Arc::<[u8]>::new_uninit_slice(len);
228        fill_uninit(
229            Arc::get_mut(&mut bytes).expect("the Arc was just created"),
230            fill,
231        )?;
232        // SAFETY: `fill_uninit` returned `Ok`, so all bytes were written.
233        Ok(Self::from(unsafe { bytes.assume_init() }))
234    }
235
236    #[inline]
237    fn from_inline(bytes: &[u8]) -> Self {
238        assert!(
239            bytes.len() <= INLINE_CAPACITY,
240            "{} bytes exceeds the {INLINE_CAPACITY} byte inline capacity",
241            bytes.len()
242        );
243        let mut buf = [0u8; INLINE_CAPACITY];
244        buf[..bytes.len()].copy_from_slice(bytes);
245        ArcBytes {
246            repr: Repr::Inline {
247                buf,
248                len: bytes.len() as u8,
249            },
250        }
251    }
252}