ruapc_bufpool/buffer.rs
1//! Buffer type that automatically returns to the pool on drop.
2//!
3//! This module provides the [`Buffer`] type, which represents an allocated memory
4//! region from the buffer pool. When a `Buffer` is dropped, its memory is
5//! automatically returned to the pool for reuse.
6//!
7//! # Memory Layout
8//!
9//! The `Buffer` struct is optimized for minimal memory footprint:
10//! - `ptr`: 8 bytes (pointer to memory)
11//! - `pool`: 8 bytes (Arc reference to pool)
12//! - `block`: 8 bytes (pointer to buddy block)
13//! - `level`: 1 byte (0-3, only 4 possible values)
14//! - `index`: 1 byte (0-63, max index at level 0)
15//! - Padding: 6 bytes for alignment
16//!
17//! Total: 32 bytes per Buffer.
18
19use std::ops::{Deref, DerefMut};
20use std::ptr::NonNull;
21use std::sync::Arc;
22
23use crate::BufferPool;
24use crate::buddy::{BuddyBlock, FreeNode};
25
26/// A buffer allocated from the pool.
27///
28/// This type provides read/write access to a contiguous memory region. When dropped,
29/// the buffer is automatically returned to the pool for reuse.
30///
31/// The buffer holds an `Arc<BufferPool>` reference to ensure the pool remains valid
32/// for the lifetime of the buffer. This guarantees memory safety even if the original
33/// pool handle is dropped.
34///
35/// # Memory Efficiency
36///
37/// Buffer fields are packed to minimize memory usage:
38/// - `level` uses `u8` (only values 0-3 are valid)
39/// - `index` uses `u8` (maximum 63 for level 0)
40///
41/// # Example
42///
43/// ```rust
44/// use ruapc_bufpool::BufferPoolBuilder;
45///
46/// # fn main() -> std::io::Result<()> {
47/// let pool = BufferPoolBuilder::new().build();
48/// let mut buffer = pool.allocate(1024 * 1024)?;
49///
50/// // Write to the buffer
51/// buffer[0] = 42;
52/// buffer[1] = 43;
53///
54/// // Read from the buffer
55/// assert_eq!(buffer[0], 42);
56///
57/// // Buffer is returned to the pool when dropped
58/// # Ok(())
59/// # }
60/// ```
61pub struct Buffer {
62 /// Pointer to the allocated memory.
63 ptr: NonNull<u8>,
64
65 /// Reference to the pool for returning the buffer.
66 /// This ensures the pool stays alive while any buffer exists.
67 pool: Arc<BufferPool>,
68
69 /// Pointer to the buddy block this buffer belongs to.
70 block: NonNull<BuddyBlock>,
71
72 /// The allocation level (0-3).
73 /// Packed as u8 to minimize struct size.
74 level: u8,
75
76 /// Index within the level in the buddy block.
77 /// Maximum value is 63 (for level 0), fits in u8.
78 index: u8,
79}
80
81// SAFETY: Buffer can be sent between threads as it only contains
82// raw pointers that are owned by the pool
83unsafe impl Send for Buffer {}
84
85// SAFETY: Buffer can be shared between threads as it provides
86// exclusive access to its memory region
87unsafe impl Sync for Buffer {}
88
89impl Buffer {
90 /// Creates a new buffer.
91 ///
92 /// # Arguments
93 ///
94 /// * `ptr` - Pointer to the allocated memory region
95 /// * `level` - Allocation level (0-3)
96 /// * `index` - Index within the level (0-63 for level 0)
97 /// * `block` - Pointer to the owning `BuddyBlock`
98 /// * `pool` - Arc reference to the pool for automatic return on drop
99 ///
100 /// # Safety
101 ///
102 /// The caller must ensure:
103 /// - `ptr` points to a valid memory region of the appropriate size for `level`
104 /// - The memory will remain valid until the buffer is dropped
105 /// - `block` points to a valid `BuddyBlock`
106 /// - `level` is in range 0-3
107 /// - `index` is valid for the given level
108 pub(crate) unsafe fn new(
109 ptr: NonNull<u8>,
110 level: usize,
111 index: usize,
112 block: NonNull<BuddyBlock>,
113 pool: Arc<BufferPool>,
114 ) -> Self {
115 debug_assert!(level < crate::buddy::NUM_LEVELS, "level must be 0-3");
116 debug_assert!(
117 index < crate::buddy::NODES_PER_LEVEL[0],
118 "index must be less than 64"
119 );
120 Self {
121 ptr,
122 pool,
123 block,
124 #[allow(clippy::cast_possible_truncation)]
125 level: level as u8, // Safe: level is validated to be < NUM_LEVELS
126 #[allow(clippy::cast_possible_truncation)]
127 index: index as u8, // Safe: index is validated to be < NODES_PER_LEVEL[0]
128 }
129 }
130
131 /// Returns the length of the buffer in bytes.
132 ///
133 /// The length is derived from the allocation level:
134 /// - Level 0: 1 MiB
135 /// - Level 1: 4 MiB
136 /// - Level 2: 16 MiB
137 /// - Level 3: 64 MiB
138 #[inline]
139 #[must_use]
140 pub const fn len(&self) -> usize {
141 crate::buddy::LEVEL_SIZES[self.level as usize]
142 }
143
144 /// Returns `true` if the buffer is empty.
145 ///
146 /// Note: Buffers from this pool are never empty (minimum 1 MiB).
147 #[inline]
148 #[must_use]
149 pub const fn is_empty(&self) -> bool {
150 false // Minimum allocation is 1 MiB, never empty
151 }
152
153 /// Returns a raw pointer to the buffer's memory.
154 #[inline]
155 #[must_use]
156 pub const fn as_ptr(&self) -> *const u8 {
157 self.ptr.as_ptr()
158 }
159
160 /// Returns a mutable raw pointer to the buffer's memory.
161 #[inline]
162 #[must_use]
163 pub const fn as_mut_ptr(&mut self) -> *mut u8 {
164 self.ptr.as_ptr()
165 }
166
167 /// Returns the buffer as a byte slice.
168 #[inline]
169 #[must_use]
170 pub const fn as_slice(&self) -> &[u8] {
171 // SAFETY: ptr is valid for len bytes
172 unsafe { std::slice::from_raw_parts(self.ptr.as_ptr(), self.len()) }
173 }
174
175 /// Returns the buffer as a mutable byte slice.
176 #[inline]
177 #[must_use]
178 pub const fn as_mut_slice(&mut self) -> &mut [u8] {
179 // SAFETY: ptr is valid for len bytes and we have exclusive access
180 unsafe { std::slice::from_raw_parts_mut(self.ptr.as_ptr(), self.len()) }
181 }
182
183 /// Returns the allocation level of this buffer (0-3).
184 #[inline]
185 #[allow(dead_code)]
186 pub(crate) const fn level(&self) -> usize {
187 self.level as usize
188 }
189
190 /// Returns the index within the level (0-63).
191 #[inline]
192 #[allow(dead_code)]
193 pub(crate) const fn index(&self) -> usize {
194 self.index as usize
195 }
196
197 /// Returns the buddy block pointer.
198 #[inline]
199 #[allow(dead_code)]
200 pub(crate) const fn block(&self) -> NonNull<BuddyBlock> {
201 self.block
202 }
203
204 /// Returns a pointer to the free node for this buffer.
205 ///
206 /// # Safety
207 ///
208 /// The caller must ensure that block pointer is still valid.
209 #[allow(dead_code)]
210 pub(crate) unsafe fn free_node(&self) -> NonNull<FreeNode> {
211 // SAFETY: block is valid and level/index are within bounds
212 unsafe {
213 (*self.block.as_ptr()).get_free_node_mut(self.level as usize, self.index as usize)
214 }
215 }
216}
217
218impl Drop for Buffer {
219 fn drop(&mut self) {
220 // Return the buffer directly to the pool with O(1) operation.
221 // This acquires the pool's mutex lock to perform the deallocation.
222 // Since buddy merging is O(1), this is efficient and avoids the
223 // latency spikes that could occur with a batched channel approach.
224 self.pool
225 .return_buffer(self.level as usize, self.index as usize, self.block);
226 }
227}
228
229impl Deref for Buffer {
230 type Target = [u8];
231
232 #[inline]
233 fn deref(&self) -> &Self::Target {
234 self.as_slice()
235 }
236}
237
238impl DerefMut for Buffer {
239 #[inline]
240 fn deref_mut(&mut self) -> &mut Self::Target {
241 self.as_mut_slice()
242 }
243}
244
245impl AsRef<[u8]> for Buffer {
246 #[inline]
247 fn as_ref(&self) -> &[u8] {
248 self.as_slice()
249 }
250}
251
252impl AsMut<[u8]> for Buffer {
253 #[inline]
254 fn as_mut(&mut self) -> &mut [u8] {
255 self.as_mut_slice()
256 }
257}
258
259impl std::fmt::Debug for Buffer {
260 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
261 f.debug_struct("Buffer")
262 .field("ptr", &self.ptr)
263 .field("len", &self.len())
264 .field("level", &self.level)
265 .field("index", &self.index)
266 .finish_non_exhaustive()
267 }
268}
269
270#[cfg(test)]
271mod tests {
272 use crate::BufferPoolBuilder;
273
274 #[test]
275 fn test_buffer_basic_operations() {
276 let pool = BufferPoolBuilder::new().build();
277 let mut buffer = pool.allocate(1024).unwrap();
278
279 // Test len
280 assert!(buffer.len() >= 1024);
281 assert!(!buffer.is_empty());
282
283 // Test write and read
284 buffer[0] = 0xAB;
285 buffer[1] = 0xCD;
286 assert_eq!(buffer[0], 0xAB);
287 assert_eq!(buffer[1], 0xCD);
288
289 // Test as_slice
290 let slice = buffer.as_slice();
291 assert_eq!(slice[0], 0xAB);
292
293 // Test as_mut_slice
294 buffer.as_mut_slice()[2] = 0xEF;
295 assert_eq!(buffer[2], 0xEF);
296 }
297
298 #[test]
299 fn test_buffer_deref() {
300 let pool = BufferPoolBuilder::new().build();
301 let mut buffer = pool.allocate(1024).unwrap();
302
303 // Fill with pattern
304 for (i, byte) in buffer.iter_mut().take(100).enumerate() {
305 *byte = i as u8;
306 }
307
308 // Read back
309 for i in 0..100 {
310 assert_eq!(buffer[i], i as u8);
311 }
312 }
313
314 #[test]
315 fn test_buffer_debug() {
316 let pool = BufferPoolBuilder::new().build();
317 let buffer = pool.allocate(1024).unwrap();
318
319 let debug_str = format!("{buffer:?}");
320 assert!(debug_str.contains("Buffer"));
321 assert!(debug_str.contains("len"));
322 }
323}