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}