Skip to main content

turbo_persistence/
arc_bytes.rs

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