Skip to main content

saluki_io/buf/
vec.rs

1use std::{mem::ManuallyDrop, sync::Arc};
2
3use bytes::{buf::UninitSlice, Buf, BufMut};
4use saluki_core::pooling::{helpers::pooled_newtype, Clearable, ReclaimStrategy};
5use triomphe::{Arc as TriompheArc, UniqueArc};
6
7use super::{ClearableIoBuffer, CollapsibleReadWriteIoBuffer, ReadIoBuffer};
8
9/// A fixed-size bytes buffer.
10///
11/// This is a simple wrapper around a `BytesMut` that provides fixed-size semantics by disallowing writes that extend
12/// beyond the initial capacity. `FixedSizeVec` can't be used directly, and must be interacted with via the
13/// [`Buf`] and [`BufMut`] traits.
14///
15/// Additionally, it's designed for use in object pools (implements [`Clearable`]).
16pub struct FixedSizeVec {
17    data: UniqueArc<Vec<u8>>,
18    read_idx: usize,
19}
20
21impl FixedSizeVec {
22    /// Creates a new `FixedSizeVec` with the given capacity.
23    ///
24    /// The vector won't grow once all available capacity has been consumed, and must be cleared to be reused.
25    pub fn with_capacity(capacity: usize) -> Self {
26        Self {
27            data: UniqueArc::new(Vec::with_capacity(capacity)),
28            read_idx: 0,
29        }
30    }
31
32    fn freeze(self) -> FrozenFixedSizeVec {
33        FrozenFixedSizeVec {
34            data: self.data.shareable(),
35            read_idx: self.read_idx,
36        }
37    }
38}
39
40impl Clearable for FixedSizeVec {
41    fn clear(&mut self) {
42        self.data.clear();
43        self.read_idx = 0;
44    }
45}
46
47struct FrozenFixedSizeVec {
48    data: TriompheArc<Vec<u8>>,
49    read_idx: usize,
50}
51
52impl FrozenFixedSizeVec {
53    fn into_unique(self) -> Option<FixedSizeVec> {
54        TriompheArc::into_unique(self.data).map(|data| FixedSizeVec { data, read_idx: 0 })
55    }
56}
57
58impl Clone for FrozenFixedSizeVec {
59    fn clone(&self) -> Self {
60        Self {
61            data: self.data.clone(),
62            read_idx: 0,
63        }
64    }
65}
66
67pooled_newtype! {
68    outer => BytesBuffer,
69    inner => FixedSizeVec,
70}
71
72impl BytesBuffer {
73    /// Consumes this buffer and returns a read-only version of it.
74    pub fn freeze(mut self) -> FrozenBytesBuffer {
75        let data = self.data.take().unwrap().freeze();
76
77        FrozenBytesBuffer {
78            strategy_ref: Arc::clone(&self.strategy_ref),
79            data: ManuallyDrop::new(data),
80        }
81    }
82}
83
84impl Buf for BytesBuffer {
85    fn remaining(&self) -> usize {
86        self.data().data.len() - self.data().read_idx
87    }
88
89    fn chunk(&self) -> &[u8] {
90        let data = self.data();
91        &data.data[data.read_idx..data.data.len()]
92    }
93
94    fn advance(&mut self, cnt: usize) {
95        let data = self.data_mut();
96        saluki_antithesis::always_le!(data.read_idx + cnt, data.data.len(), "buffer advance within bounds");
97        assert!(data.read_idx + cnt <= data.data.len());
98        data.read_idx += cnt;
99    }
100}
101
102unsafe impl BufMut for BytesBuffer {
103    fn remaining_mut(&self) -> usize {
104        let data = self.data();
105        data.data.capacity() - data.data.len()
106    }
107
108    fn chunk_mut(&mut self) -> &mut UninitSlice {
109        self.data_mut().data.spare_capacity_mut().into()
110    }
111
112    unsafe fn advance_mut(&mut self, cnt: usize) {
113        let new_len = self.data().data.len() + cnt;
114        self.data_mut().data.set_len(new_len);
115    }
116}
117
118impl ReadIoBuffer for BytesBuffer {
119    fn capacity(&self) -> usize {
120        self.data().data.capacity()
121    }
122}
123
124impl CollapsibleReadWriteIoBuffer for BytesBuffer {
125    fn collapse(&mut self) {
126        let remaining = self.remaining();
127
128        // If the buffer is empty, all we have to do is reset the buffer to its initial state.
129        if remaining == 0 {
130            let inner = self.data_mut();
131            inner.read_idx = 0;
132            inner.data.clear();
133            return;
134        }
135
136        // Otherwise, we have to actually shift the remaining data to the front of the buffer and then also update our
137        // buffer state.
138        let inner = self.data_mut();
139
140        let src_start = inner.read_idx;
141        let src_end = inner.data.len();
142        inner.data.copy_within(src_start..src_end, 0);
143        inner.data.truncate(remaining);
144        inner.read_idx = 0;
145    }
146}
147
148impl ClearableIoBuffer for BytesBuffer {
149    fn clear(&mut self) {
150        self.data_mut().clear();
151    }
152}
153
154/// A frozen, read-only version of [`BytesBuffer`].
155///
156/// `FrozenBytesBuffer` can be cheaply cloned, and allows for sharing an underlying [`BytesBuffer`] among multiple
157/// tasks while still maintaining all of the original buffer's object pooling semantics.
158// TODO: it's not great that we're manually emulating the internal structure of `BytesBuffer`, since the whole point is
159// that those bits are auto-generated for us and meant to be functionally transparent to using `BytesBuffer` in the
160// first place... it'd be interesting to consider if we could make this more ergonomic, perhaps by having some sort of
161// convenience helper method for converting pooled objects of type T to U where the `Poolable::Data` is identical
162// between them, almost along lines of `CoerceUnsized` where the underlying data isn't changing, just the representation
163// of it.
164#[derive(Clone)]
165pub struct FrozenBytesBuffer {
166    strategy_ref: Arc<dyn ReclaimStrategy<BytesBuffer> + Send + Sync>,
167    data: ManuallyDrop<FrozenFixedSizeVec>,
168}
169
170impl FrozenBytesBuffer {
171    /// Returns `true` if the buffer is empty.
172    pub fn is_empty(&self) -> bool {
173        self.data.read_idx == self.data.data.len()
174    }
175
176    /// Returns the number of bytes remaining in the buffer.
177    pub fn len(&self) -> usize {
178        self.data.data.len() - self.data.read_idx
179    }
180
181    /// Returns the total capacity of the buffer.
182    pub fn capacity(&self) -> usize {
183        self.data.data.capacity()
184    }
185}
186
187impl Buf for FrozenBytesBuffer {
188    fn remaining(&self) -> usize {
189        self.data.data.len() - self.data.read_idx
190    }
191
192    fn chunk(&self) -> &[u8] {
193        &self.data.data[self.data.read_idx..]
194    }
195
196    fn advance(&mut self, cnt: usize) {
197        saluki_antithesis::always_le!(
198            self.data.read_idx + cnt,
199            self.data.data.len(),
200            "buffer advance within bounds"
201        );
202        assert!(self.data.read_idx + cnt <= self.data.data.len());
203        self.data.read_idx += cnt;
204    }
205}
206
207impl Drop for FrozenBytesBuffer {
208    fn drop(&mut self) {
209        // If we're the last reference to the buffer, we need to reconstitute it back to a `FixedSizeVec`, and reclaim
210        // it to the object pool.
211        //
212        // SAFETY: Nothing else can be using `self.data` since we're dropping.
213        let data = unsafe { ManuallyDrop::take(&mut self.data) };
214        if let Some(data) = data.into_unique() {
215            self.strategy_ref.reclaim(data);
216        }
217    }
218}
219
220#[cfg(test)]
221mod tests {
222    use saluki_core::pooling::helpers::get_pooled_object_via_builder;
223
224    use super::*;
225
226    #[test]
227    fn basic() {
228        let mut buf = get_pooled_object_via_builder::<_, BytesBuffer>(|| FixedSizeVec::with_capacity(13));
229
230        let first_write = b"hello";
231        let second_write = b", worl";
232        let third_write = b"d!";
233
234        // We start out empty:
235        assert_eq!(buf.remaining(), 0);
236        assert_eq!(buf.remaining_mut(), 13);
237
238        // Write the first chunk:
239        buf.put_slice(first_write);
240        assert_eq!(buf.remaining(), 5);
241        assert_eq!(buf.remaining_mut(), 8);
242
243        // Write the second chunk:
244        buf.put_slice(second_write);
245        assert_eq!(buf.remaining(), 11);
246        assert_eq!(buf.remaining_mut(), 2);
247
248        // Read 7 bytes worth:
249        let first_chunk = buf.chunk();
250        assert_eq!(first_chunk.len(), 11);
251        assert_eq!(first_chunk, b"hello, worl");
252
253        buf.advance(7);
254        assert_eq!(buf.remaining(), 4);
255        assert_eq!(buf.remaining_mut(), 2);
256
257        // Write the third chunk:
258        buf.put_slice(third_write);
259        assert_eq!(buf.remaining(), 6);
260        assert_eq!(buf.remaining_mut(), 0);
261
262        // Read the rest:
263        let second_chunk = buf.chunk();
264        assert_eq!(second_chunk.len(), 6);
265        assert_eq!(second_chunk, b"world!");
266
267        buf.advance(6);
268        assert_eq!(buf.remaining(), 0);
269        assert_eq!(buf.remaining_mut(), 0);
270
271        // Clear the buffer:
272        buf.data_mut().clear();
273        assert_eq!(buf.remaining(), 0);
274        assert_eq!(buf.remaining_mut(), 13);
275    }
276
277    #[test]
278    fn collapsible_empty() {
279        let mut buf = get_pooled_object_via_builder::<_, BytesBuffer>(|| FixedSizeVec::with_capacity(13));
280
281        // Buffer is empty.
282        assert_eq!(buf.remaining(), 0);
283        assert_eq!(buf.remaining_mut(), 13);
284
285        buf.collapse();
286
287        // Buffer is still empty.
288        assert_eq!(buf.remaining(), 0);
289        assert_eq!(buf.remaining_mut(), 13);
290    }
291
292    #[test]
293    fn collapsible_remaining_already_collapsed() {
294        let mut buf = get_pooled_object_via_builder::<_, BytesBuffer>(|| FixedSizeVec::with_capacity(24));
295
296        // Write a simple string to the buffer.
297        buf.put_slice(b"hello, world!");
298        assert_eq!(buf.remaining(), 13);
299        assert_eq!(buf.remaining_mut(), 11);
300
301        buf.collapse();
302
303        // Buffer is still the same since we never read anything from the buffer.
304        assert_eq!(buf.remaining(), 13);
305        assert_eq!(buf.remaining_mut(), 11);
306    }
307
308    #[test]
309    fn collapsible_remaining_not_collapsed_no_overlap() {
310        let mut buf = get_pooled_object_via_builder::<_, BytesBuffer>(|| FixedSizeVec::with_capacity(24));
311
312        // Write a simple string to the buffer.
313        buf.put_slice(b"hello, world!");
314        assert_eq!(buf.remaining(), 13);
315        assert_eq!(buf.remaining_mut(), 11);
316        assert_eq!(buf.chunk(), b"hello, world!");
317
318        // Write another simple string to the buffer.
319        buf.put_slice(b"huzzah!");
320        assert_eq!(buf.remaining(), 20);
321        assert_eq!(buf.remaining_mut(), 4);
322        assert_eq!(buf.chunk(), b"hello, world!huzzah!");
323
324        // Simulate reading the first string from the buffer, which will end up leaving a hole in the buffer, prior to
325        // the second string, that is big enough to fit the second string entirely.
326        buf.advance(13);
327        assert_eq!(buf.remaining(), 7);
328        assert_eq!(buf.remaining_mut(), 4);
329        assert_eq!(buf.chunk(), b"huzzah!");
330
331        buf.collapse();
332
333        // Buffer should now be collapsed, with the second string at the beginning of the buffer.
334        assert_eq!(buf.remaining(), 7);
335        assert_eq!(buf.remaining_mut(), 17);
336        assert_eq!(buf.chunk(), b"huzzah!");
337    }
338
339    #[test]
340    fn collapsible_remaining_not_collapsed_with_overlap() {
341        let mut buf = get_pooled_object_via_builder::<_, BytesBuffer>(|| FixedSizeVec::with_capacity(24));
342
343        // Write a simple string to the buffer.
344        buf.put_slice(b"huzzah!");
345        assert_eq!(buf.remaining(), 7);
346        assert_eq!(buf.remaining_mut(), 17);
347        assert_eq!(buf.chunk(), b"huzzah!");
348
349        // Write another simple string to the buffer.
350        buf.put_slice(b"hello, world!");
351        assert_eq!(buf.remaining(), 20);
352        assert_eq!(buf.remaining_mut(), 4);
353        assert_eq!(buf.chunk(), b"huzzah!hello, world!");
354
355        // Simulate reading the first string from the buffer, which will end up leaving a hole in the buffer, prior to
356        // the second string, that isn't big enough to fit the second string entirely.
357        buf.advance(7);
358        assert_eq!(buf.remaining(), 13);
359        assert_eq!(buf.remaining_mut(), 4);
360        assert_eq!(buf.chunk(), b"hello, world!");
361
362        buf.collapse();
363
364        // Buffer should now be collapsed, with the second string at the beginning of the buffer.
365        assert_eq!(buf.remaining(), 13);
366        assert_eq!(buf.remaining_mut(), 11);
367        assert_eq!(buf.chunk(), b"hello, world!");
368    }
369}