ruapc_bufpool/
allocator.rs

1//! Memory allocator trait and default implementation.
2//!
3//! This module provides the [`Allocator`] trait that defines the interface for memory
4//! allocation backends, and [`DefaultAllocator`] which uses the standard library's
5//! global allocator.
6
7use std::alloc::{Layout, alloc, dealloc};
8use std::io::{Error, ErrorKind, Result};
9
10/// Trait for memory allocation backends.
11///
12/// Implementations of this trait provide the low-level memory allocation and
13/// deallocation operations used by the buffer pool. The default implementation
14/// uses the standard library's global allocator.
15///
16/// # Safety
17///
18/// Implementations must ensure:
19/// - `allocate` returns a valid, properly aligned pointer for the requested size
20/// - `deallocate` is only called with pointers previously returned by `allocate`
21/// - The allocated memory remains valid until `deallocate` is called
22///
23/// # Example
24///
25/// ```rust
26/// use ruapc_bufpool::Allocator;
27/// use std::io::Result;
28///
29/// struct MyAllocator;
30///
31/// impl Allocator for MyAllocator {
32///     fn allocate(&self, size: usize) -> Result<*mut u8> {
33///         // Custom allocation logic
34///         # unimplemented!()
35///     }
36///
37///     unsafe fn deallocate(&self, ptr: *mut u8, size: usize) {
38///         // Custom deallocation logic
39///         # unimplemented!()
40///     }
41/// }
42/// ```
43pub trait Allocator: Send + Sync {
44    /// Allocates memory of the specified size.
45    ///
46    /// # Arguments
47    ///
48    /// * `size` - The number of bytes to allocate. Must be greater than 0.
49    ///
50    /// # Returns
51    ///
52    /// Returns a pointer to the allocated memory on success, or an error if
53    /// allocation fails.
54    ///
55    /// # Errors
56    ///
57    /// Returns an error if the allocation fails due to memory exhaustion or
58    /// invalid size.
59    fn allocate(&self, size: usize) -> Result<*mut u8>;
60
61    /// Deallocates memory previously allocated by this allocator.
62    ///
63    /// # Arguments
64    ///
65    /// * `ptr` - Pointer to the memory to deallocate. Must have been returned
66    ///   by a previous call to `allocate` on this allocator.
67    /// * `size` - The size that was passed to the original `allocate` call.
68    ///
69    /// # Safety
70    ///
71    /// The caller must ensure:
72    /// - `ptr` was returned by a previous call to `allocate` on this allocator
73    /// - `size` matches the size passed to the original `allocate` call
74    /// - The memory has not already been deallocated
75    unsafe fn deallocate(&self, ptr: *mut u8, size: usize);
76}
77
78/// Default allocator using the standard library's global allocator.
79///
80/// This allocator uses `std::alloc::alloc` and `std::alloc::dealloc` for memory
81/// management. It aligns allocations to page size for optimal performance with large buffers.
82///
83/// # Alignment
84///
85/// - On 64-bit systems: Uses 2MiB alignment for potential huge page support
86/// - On other systems: Uses 4KiB page alignment
87#[derive(Debug, Default, Clone, Copy)]
88pub struct DefaultAllocator;
89
90impl DefaultAllocator {
91    /// Returns the alignment size for this platform.
92    #[cfg(target_pointer_width = "64")]
93    const fn alignment() -> usize {
94        2 * 1024 * 1024 // 2MiB
95    }
96
97    /// Returns the alignment size for this platform.
98    #[cfg(not(target_pointer_width = "64"))]
99    const fn alignment() -> usize {
100        4096 // 4KiB
101    }
102
103    /// Creates a new default allocator.
104    #[must_use]
105    pub const fn new() -> Self {
106        Self
107    }
108}
109
110impl Allocator for DefaultAllocator {
111    fn allocate(&self, size: usize) -> Result<*mut u8> {
112        if size == 0 {
113            return Err(Error::new(ErrorKind::InvalidInput, "size must be > 0"));
114        }
115
116        // SAFETY: size is non-zero and alignment is a power of 2
117        let layout = Layout::from_size_align(size, Self::alignment())
118            .map_err(|e| Error::new(ErrorKind::InvalidInput, e))?;
119
120        // SAFETY: layout is valid (non-zero size, valid alignment)
121        let ptr = unsafe { alloc(layout) };
122
123        if ptr.is_null() {
124            Err(Error::new(
125                ErrorKind::OutOfMemory,
126                "failed to allocate memory",
127            ))
128        } else {
129            Ok(ptr)
130        }
131    }
132
133    unsafe fn deallocate(&self, ptr: *mut u8, size: usize) {
134        if size == 0 || ptr.is_null() {
135            return;
136        }
137
138        // SAFETY: size is non-zero and alignment is a power of 2
139        if let Ok(layout) = Layout::from_size_align(size, Self::alignment()) {
140            // SAFETY: ptr was allocated with this layout by allocate()
141            unsafe { dealloc(ptr, layout) };
142        }
143    }
144}
145
146#[cfg(test)]
147mod tests {
148    use super::*;
149
150    #[test]
151    fn test_default_allocator_basic() {
152        let allocator = DefaultAllocator::new();
153
154        // Allocate 1MiB
155        let size = 1024 * 1024;
156        let ptr = allocator.allocate(size).unwrap();
157        assert!(!ptr.is_null());
158
159        // Write and read back
160        unsafe {
161            std::ptr::write_bytes(ptr, 0xAB, size);
162            assert_eq!(*ptr, 0xAB);
163            assert_eq!(*ptr.add(size - 1), 0xAB);
164        }
165
166        // Deallocate
167        unsafe {
168            allocator.deallocate(ptr, size);
169        }
170    }
171
172    #[test]
173    fn test_default_allocator_zero_size() {
174        let allocator = DefaultAllocator::new();
175        let result = allocator.allocate(0);
176        assert!(result.is_err());
177    }
178
179    #[test]
180    fn test_default_allocator_large_allocation() {
181        let allocator = DefaultAllocator::new();
182
183        // Allocate 64MiB
184        let size = 64 * 1024 * 1024;
185        let ptr = allocator.allocate(size).unwrap();
186        assert!(!ptr.is_null());
187
188        unsafe {
189            allocator.deallocate(ptr, size);
190        }
191    }
192}