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}