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}