Skip to main content

santh_bufpool/
buffer.rs

1use std::fmt;
2use std::ops::{Deref, DerefMut};
3use std::ptr::NonNull;
4use std::slice;
5use std::sync::atomic::Ordering;
6use std::sync::Arc;
7
8use crate::size_class::{zero_buffer, BufferAllocation, SizeClassPool};
9use crate::stats::PoolStats;
10use crate::tls::store_tls_buffer;
11
12/// An immutable, shared buffer view.
13///
14/// `FrozenBuffer` can be sent across threads and shared because it only
15/// permits read access. The underlying memory returns to the pool when the
16/// last reference is dropped.
17///
18/// # Example
19///
20/// ```rust
21/// use santh_bufpool::{BufferPool, PoolConfig};
22///
23/// let pool = BufferPool::new(PoolConfig::default());
24/// let buffer = pool.checkout(8).unwrap();
25/// let frozen = buffer.freeze();
26/// assert_eq!(frozen.len(), 8);
27/// ```
28pub struct FrozenBuffer {
29    ptr: NonNull<u8>,
30    len: usize,
31    capacity: usize,
32    owner: Arc<SizeClassPool>,
33    stats: Option<Arc<PoolStats>>,
34}
35
36// SAFETY: FrozenBuffer provides only immutable access to the pointed-to bytes.
37unsafe impl Send for FrozenBuffer {}
38unsafe impl Sync for FrozenBuffer {}
39
40impl Deref for FrozenBuffer {
41    type Target = [u8];
42
43    fn deref(&self) -> &Self::Target {
44        // SAFETY: `ptr` is valid for `len` bytes and the allocation is owned
45        // by this struct for the duration of the borrow.
46        unsafe { slice::from_raw_parts(self.ptr.as_ptr().cast_const(), self.len) }
47    }
48}
49
50impl AsRef<[u8]> for FrozenBuffer {
51    fn as_ref(&self) -> &[u8] {
52        self
53    }
54}
55
56impl Drop for FrozenBuffer {
57    fn drop(&mut self) {
58        if let Some(stats) = &self.stats {
59            stats.checked_out.fetch_sub(1, Ordering::Relaxed);
60            stats
61                .bytes_checked_out
62                .fetch_sub(self.len, Ordering::Relaxed);
63        }
64        zero_buffer(self.ptr, self.capacity);
65        let allocation = BufferAllocation { ptr: self.ptr };
66        self.owner.recycle_or_free(allocation);
67    }
68}
69
70/// A borrowed buffer from the pool.
71///
72/// Automatically returns to the pool on drop. Supports `Deref` and `DerefMut`
73/// for transparent access as a byte slice.
74///
75/// # Example
76///
77/// ```rust
78/// use santh_bufpool::{BufferPool, PoolConfig};
79///
80/// let pool = BufferPool::new(PoolConfig::default());
81/// let mut buffer = pool.checkout(4).unwrap();
82/// buffer.copy_from_slice(&[1, 2, 3, 4]);
83/// assert_eq!(&*buffer, &[1, 2, 3, 4]);
84/// ```
85pub struct PoolBuffer {
86    pub(crate) ptr: NonNull<u8>,
87    pub(crate) len: usize,
88    pub(crate) capacity: usize,
89    pub(crate) owner: Arc<SizeClassPool>,
90    pub(crate) stats: Option<Arc<PoolStats>>,
91}
92
93// SAFETY: PoolBuffer owns its allocation exclusively until dropped or frozen.
94unsafe impl Send for PoolBuffer {}
95unsafe impl Sync for PoolBuffer {}
96
97impl PoolBuffer {
98    /// Freeze this buffer into an immutable, `Send + Sync` view.
99    ///
100    /// The frozen buffer can be shared across threads. The underlying
101    /// memory returns to the pool when the last clone is dropped.
102    ///
103    /// # Example
104    ///
105    /// ```rust
106    /// use santh_bufpool::{BufferPool, PoolConfig};
107    ///
108    /// let pool = BufferPool::new(PoolConfig::default());
109    /// let buffer = pool.checkout(4).unwrap();
110    /// let frozen = buffer.freeze();
111    /// std::thread::spawn(move || {
112    ///     assert_eq!(frozen.len(), 4);
113    /// })
114    /// .join()
115    /// .unwrap();
116    /// ```
117    #[must_use]
118    pub fn freeze(self) -> FrozenBuffer {
119        let this = std::mem::ManuallyDrop::new(self);
120        FrozenBuffer {
121            ptr: this.ptr,
122            len: this.len,
123            capacity: this.capacity,
124            owner: unsafe { std::ptr::read(&raw const this.owner) },
125            stats: unsafe { std::ptr::read(&raw const this.stats) },
126        }
127    }
128
129    /// Return the logical slice length of this buffer.
130    ///
131    /// # Example
132    ///
133    /// ```rust
134    /// use santh_bufpool::{BufferPool, PoolConfig};
135    ///
136    /// let pool = BufferPool::new(PoolConfig::default());
137    /// let buffer = pool.checkout(42).unwrap();
138    /// assert_eq!(buffer.len(), 42);
139    /// ```
140    #[must_use]
141    pub fn len(&self) -> usize {
142        self.len
143    }
144
145    /// Return `true` if the logical length is zero.
146    ///
147    /// # Example
148    ///
149    /// ```rust
150    /// use santh_bufpool::{BufferPool, PoolConfig};
151    ///
152    /// let pool = BufferPool::new(PoolConfig::default());
153    /// let buffer = pool.checkout(0).unwrap();
154    /// assert!(buffer.is_empty());
155    /// ```
156    #[must_use]
157    pub fn is_empty(&self) -> bool {
158        self.len == 0
159    }
160
161    /// Return the true physical capacity of the underlying allocation.
162    ///
163    /// # Example
164    ///
165    /// ```rust
166    /// use santh_bufpool::{BufferPool, PoolConfig};
167    ///
168    /// let pool = BufferPool::new(PoolConfig::default());
169    /// let buffer = pool.checkout(1).unwrap();
170    /// assert!(buffer.capacity() >= 1);
171    /// ```
172    #[must_use]
173    pub fn capacity(&self) -> usize {
174        self.capacity
175    }
176
177    /// Returns a raw pointer to the buffer's contents.
178    #[must_use]
179    pub fn as_ptr(&self) -> *const u8 {
180        self.ptr.as_ptr().cast_const()
181    }
182
183    /// Returns an unsafe mutable pointer to the buffer's contents.
184    #[must_use]
185    pub fn as_mut_ptr(&mut self) -> *mut u8 {
186        self.ptr.as_ptr()
187    }
188}
189
190impl AsRef<[u8]> for PoolBuffer {
191    fn as_ref(&self) -> &[u8] {
192        self
193    }
194}
195
196impl AsMut<[u8]> for PoolBuffer {
197    fn as_mut(&mut self) -> &mut [u8] {
198        self
199    }
200}
201
202impl Deref for PoolBuffer {
203    type Target = [u8];
204
205    fn deref(&self) -> &Self::Target {
206        // SAFETY: `ptr` always comes from an allocation whose capacity is at
207        // least `len`, and `len` is validated before checkout. The allocation
208        // remains owned by `self` for the lifetime of the returned slice.
209        unsafe { slice::from_raw_parts(self.ptr.as_ptr().cast_const(), self.len) }
210    }
211}
212
213impl DerefMut for PoolBuffer {
214    fn deref_mut(&mut self) -> &mut Self::Target {
215        // SAFETY: `ptr` always comes from an allocation whose capacity is at
216        // least `len`, and `self` has unique access while the mutable slice is
217        // borrowed.
218        unsafe { slice::from_raw_parts_mut(self.ptr.as_ptr(), self.len) }
219    }
220}
221
222impl fmt::Debug for PoolBuffer {
223    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
224        formatter
225            .debug_struct("PoolBuffer")
226            .field("len", &self.len)
227            .field("capacity", &self.capacity)
228            .finish_non_exhaustive()
229    }
230}
231
232impl Drop for PoolBuffer {
233    fn drop(&mut self) {
234        if let Some(stats) = &self.stats {
235            stats.checked_out.fetch_sub(1, Ordering::Relaxed);
236            stats
237                .bytes_checked_out
238                .fetch_sub(self.len, Ordering::Relaxed);
239        }
240        zero_buffer(self.ptr, self.capacity);
241        if !store_tls_buffer(self.capacity, self.ptr, Arc::clone(&self.owner)) {
242            let allocation = BufferAllocation { ptr: self.ptr };
243            self.owner.recycle_or_free(allocation);
244        }
245    }
246}