1use std::alloc::{alloc, dealloc, Layout};
10use std::{ops::{Deref, DerefMut}, slice};
11use tracing::{debug, error, trace, info};
12use crate::metrics::{GLOBAL_BUFFER_COUNT, GLOBAL_BUFFER_LIMIT, GLOBAL_MEMORY_USAGE_BYTES};
13use std::sync::atomic::{AtomicUsize, Ordering};
14use std::sync::Arc;
15use std::cell::UnsafeCell;
16use crossbeam::queue::ArrayQueue;
17use crate::error::{FxcpError, Result};
18use crate::constants;
19use std::ptr;
20
21pub(crate) struct AlignedBuffer {
22 ptr: *mut u8,
23 layout: Layout,
24 capacity: usize,
25 len: AtomicUsize
26}
27unsafe impl Send for AlignedBuffer {}
31unsafe impl Sync for AlignedBuffer {}
35
36impl AlignedBuffer {
37 #[allow(dead_code, unused_variables)]
40 pub fn try_new_numa(capacity: usize, alignment: usize, numa_node: i32) -> Result<Self> {
41 Self::try_new(capacity, alignment)
46 }
47
48 pub fn try_new(capacity: usize, alignment: usize) -> Result<Self> {
50 let limit_bytes = GLOBAL_BUFFER_LIMIT.get() as u64;
52 let align = alignment.max(constants::MINIMUM_ALIGNMENT_BYTES);
53 let actual_capacity = capacity.max(align);
54
55 let _prev = GLOBAL_BUFFER_COUNT.try_update(
56 Ordering::SeqCst,
57 Ordering::SeqCst,
58 |current| {
59 if current + actual_capacity as u64 > limit_bytes {
60 None
61 } else {
62 Some(current + actual_capacity as u64)
63 }
64 }
65 ).map_err(|_| FxcpError::MemoryExhausted(format!(
66 "Global memory limit exceeded. Refusing allocation of {} bytes.", capacity
67 )))?;
68
69 GLOBAL_MEMORY_USAGE_BYTES.add(actual_capacity as f64);
71
72 let layout = Layout::from_size_align(actual_capacity, align)
73 .map_err(|_e| {
74 GLOBAL_BUFFER_COUNT.fetch_sub(actual_capacity as u64, Ordering::SeqCst);
75 GLOBAL_MEMORY_USAGE_BYTES.sub(actual_capacity as f64);
76 FxcpError::System(nix::Error::from(nix::errno::Errno::EINVAL))
77 })?;
78
79 let ptr = unsafe { alloc(layout) };
82 if ptr.is_null() {
83 GLOBAL_BUFFER_COUNT.fetch_sub(actual_capacity as u64, Ordering::SeqCst);
84 GLOBAL_MEMORY_USAGE_BYTES.sub(actual_capacity as f64);
85 return Err(FxcpError::MemoryExhausted("Physical memory allocation failed".to_string()));
86 }
87 unsafe { ptr::write_bytes(ptr, 0, actual_capacity); }
90
91 if actual_capacity >= 1_048_576
95 && let Some(nn) = std::ptr::NonNull::new(ptr) {
96 let _ = unsafe {
99 nix::sys::mman::madvise(nn.cast(), actual_capacity, nix::sys::mman::MmapAdvise::MADV_POPULATE_WRITE)
100 };
101 }
102 trace!("AlignedBuffer: Allocated {} bytes.", actual_capacity);
103 Ok(Self {
104 ptr,
105 layout,
106 capacity: actual_capacity,
107 len: AtomicUsize::new(0)
108 })
109 }
110 #[inline(always)]
111 pub fn clear(&self) {
112 self.len.store(0, Ordering::Release);
113 }
114 #[inline(always)]
115 pub fn capacity(&self) -> usize { self.capacity }
116 #[inline(always)]
117 #[allow(dead_code)]
118 pub fn set_full_len(&self) {
119 self.len.store(self.capacity, Ordering::Release);
120 }
121 #[inline(always)]
122 pub fn set_len(&self, len: usize) -> Result<()> {
123 if len > self.capacity {
124 return Err(FxcpError::Config(format!(
125 "AlignedBuffer::set_len: {} exceeds capacity {}", len, self.capacity
126 )));
127 }
128 self.len.store(len, Ordering::Release);
129 Ok(())
130 }
131 #[inline(always)]
132 pub fn get_len(&self) -> usize {
133 self.len.load(Ordering::Acquire)
134 }
135 #[inline(always)]
136 pub fn ptr(&self) -> *mut u8 { self.ptr }
137 #[inline(always)]
138 #[allow(dead_code)]
139 pub fn alignment(&self) -> usize { self.layout.align() }
140}
141
142impl Drop for AlignedBuffer {
143 fn drop(&mut self) {
144 let _prev = GLOBAL_BUFFER_COUNT.fetch_sub(self.capacity as u64, Ordering::SeqCst);
145 GLOBAL_MEMORY_USAGE_BYTES.sub(self.capacity as f64);
146 unsafe { dealloc(self.ptr, self.layout); }
149 }
150}
151
152impl Deref for AlignedBuffer {
153 type Target = [u8];
154 fn deref(&self) -> &Self::Target {
155 unsafe { slice::from_raw_parts(self.ptr, self.get_len()) }
158 }
159}
160
161impl DerefMut for AlignedBuffer {
162 fn deref_mut(&mut self) -> &mut Self::Target {
163 unsafe { slice::from_raw_parts_mut(self.ptr, self.get_len()) }
165 }
166}
167
168enum QueueStrategy {
169 Shared(ArrayQueue<u16>),
170 Local(UnsafeCell<Vec<u16>>),
171}
172unsafe impl Send for QueueStrategy {}
176unsafe impl Sync for QueueStrategy {}
177
178struct BufferPoolInner {
179 buffers: Vec<UnsafeCell<AlignedBuffer>>,
180 free_indices: QueueStrategy,
181 capacity: usize,
182 chunk_size: usize,
183 alignment: usize,
184}
185unsafe impl Sync for BufferPoolInner {}
189unsafe impl Send for BufferPoolInner {}
190
191#[derive(Clone)]
193pub struct BufferPool {
194 inner: Arc<BufferPoolInner>,
195}
196
197impl BufferPool {
198 pub fn new(requested_buffers: usize, requested_chunk_size: usize, alignment: usize) -> Result<Self> {
200 Self::create_pool(requested_buffers, requested_chunk_size, alignment, false)
201 }
202 pub fn new_local(requested_buffers: usize, requested_chunk_size: usize, alignment: usize) -> Result<Self> {
204 Self::create_pool(requested_buffers, requested_chunk_size, alignment, true)
205 }
206 fn create_pool(requested_buffers: usize, requested_chunk_size: usize, alignment: usize, local_mode: bool) -> Result<Self> {
207 if requested_buffers > constants::BUFFER_POOL_MAX_REGISTERED_BUFFERS as usize {
208 return Err(FxcpError::Config("BufferPool: max 65535 buffers allowed".to_string()));
209 }
210 let align = alignment.max(constants::MINIMUM_ALIGNMENT_BYTES);
211 let min_chunk_size = constants::BUFFER_POOL_MIN_CHUNK_SIZE;
212 let strategies = [
213 (requested_buffers, requested_chunk_size),
214 (requested_buffers / 2, requested_chunk_size),
215 (requested_buffers / 4, requested_chunk_size),
216 (requested_buffers, requested_chunk_size / 2),
217 (requested_buffers / 2, requested_chunk_size / 2),
218 (8, min_chunk_size),
219 ];
220
221 for (count, size) in strategies {
222 if count < 2 || size < min_chunk_size { continue; }
223 let mut buffers = Vec::with_capacity(count);
224 let mut allocated_successfully = true;
225 for i in 0..count {
226 match AlignedBuffer::try_new(size, align) {
227 Ok(buf) => buffers.push(UnsafeCell::new(buf)),
228 Err(e) => {
229 debug!("BufferPool: Allocation failed at buffer {} of {}: {:?}", i, count, e);
230 allocated_successfully = false;
231 break;
232 }
233 }
234 }
235 if !allocated_successfully { continue; }
236
237 let actual_capacity = buffers.len();
238 let queue = if local_mode {
239 let mut v = Vec::with_capacity(actual_capacity);
240 for i in 0..actual_capacity {
241 v.push(i as u16);
242 }
243 QueueStrategy::Local(UnsafeCell::new(v))
244 } else {
245 let q = ArrayQueue::new(actual_capacity);
246 for i in 0..actual_capacity {
247 let _ = q.push(i as u16);
248 }
249 QueueStrategy::Shared(q)
250 };
251
252 let total_mb = (actual_capacity as u64 * size as u64) / 1024 / 1024;
253 info!("BufferPool: Allocated {} x {}KB buffers ({}MB total, Mode: {})",
254 actual_capacity, size / 1024, total_mb, if local_mode { "Thread-Local" } else { "Shared" });
255 crate::metrics::BUFFER_POOL_CAPACITY.set(actual_capacity as f64);
256 crate::metrics::BUFFER_POOL_CHUNK_SIZE.set(size as f64);
257 crate::metrics::BUFFER_POOL_TOTAL_BYTES.set((actual_capacity as u64 * size as u64) as f64);
258
259 return Ok(Self {
260 inner: Arc::new(BufferPoolInner {
261 buffers,
262 free_indices: queue,
263 capacity: actual_capacity,
264 chunk_size: size,
265 alignment: align,
266 })
267 });
268 }
269 Err(FxcpError::MemoryExhausted("BufferPool: All allocation strategies failed.".to_string()))
270 }
271
272 #[inline(always)]
274 pub fn capacity(&self) -> usize { self.inner.capacity }
275 #[inline(always)]
277 pub fn chunk_size(&self) -> usize { self.inner.chunk_size }
278 #[inline(always)]
280 pub fn alignment(&self) -> usize { self.inner.alignment }
281 #[inline(always)]
283 pub fn get_ptr(&self, index: u16) -> Option<*mut u8> {
284 self.inner.buffers.get(index as usize).map(|cell| {
285 unsafe { (*cell.get()).ptr() }
289 })
290 }
291 #[inline(always)]
293 pub fn get_len(&self, index: u16) -> usize {
294 if let Some(cell) = self.inner.buffers.get(index as usize) {
295 unsafe { (*cell.get()).get_len() }
298 } else {
299 0
300 }
301 }
302 #[inline(always)]
304 pub fn set_len(&self, index: u16, len: usize) -> Result<()> {
305 if let Some(cell) = self.inner.buffers.get(index as usize) {
306 unsafe {
309 let buf = &*cell.get();
310 buf.set_len(len)?;
311 }
312 }
313 Ok(())
314 }
315 pub fn as_io_vecs(&mut self) -> Vec<libc::iovec> {
317 self.inner.buffers.iter().map(|cell| {
318 unsafe {
321 let buf = &*cell.get();
322 libc::iovec { iov_base: buf.ptr() as _, iov_len: buf.capacity() }
323 }
324 }).collect()
325 }
326 #[inline]
328 pub fn acquire(&self) -> Option<u16> {
329 match &self.inner.free_indices {
330 QueueStrategy::Shared(q) => q.pop(),
331 QueueStrategy::Local(cell) => {
332 let vec = unsafe { &mut *cell.get() };
335 vec.pop()
336 }
337 }
338 }
339 #[inline]
341 pub fn release(&self, index: u16) {
342 if let Some(cell) = self.inner.buffers.get(index as usize) {
343 unsafe { (*cell.get()).clear(); }
346 match &self.inner.free_indices {
347 QueueStrategy::Shared(q) => {
348 let _ = q.push(index);
349 },
350 QueueStrategy::Local(c) => {
351 let vec = unsafe { &mut *c.get() };
353 vec.push(index);
354 }
355 }
356 } else {
357 error!("BufferPool: Attempted to release invalid index {}", index);
358 }
359 }
360 #[inline]
362 pub fn free_count(&self) -> usize {
363 match &self.inner.free_indices {
364 QueueStrategy::Shared(q) => q.len(),
365 QueueStrategy::Local(c) => unsafe { (*c.get()).len() },
367 }
368 }
369}
370
371#[cfg(test)]
372mod tests {
373 #![allow(clippy::unwrap_used, clippy::expect_used, clippy::panic)]
374 use super::*;
375 use std::sync::Mutex;
376
377 static BUFFER_LIMIT_LOCK: Mutex<()> = Mutex::new(());
380
381 #[test]
382 fn test_buffer_set_len_overflow_returns_error() {
383 let _guard = BUFFER_LIMIT_LOCK.lock().unwrap();
384 let saved = GLOBAL_BUFFER_LIMIT.get();
385 GLOBAL_BUFFER_LIMIT.set(1024.0 * 1024.0);
386
387 let buf = AlignedBuffer::try_new(16, 4096).unwrap();
388 let cap = buf.capacity();
389 assert!(buf.set_len(cap + 1).is_err(), "set_len beyond capacity should return Err");
390 assert!(buf.set_len(cap).is_ok(), "set_len at exact capacity should succeed");
391 assert!(buf.set_len(0).is_ok(), "set_len to zero should succeed");
392
393 GLOBAL_BUFFER_LIMIT.set(saved);
394 }
395
396 #[test]
397 fn test_large_buffer_madv_populate() {
398 let _guard = BUFFER_LIMIT_LOCK.lock().unwrap();
399 let saved = GLOBAL_BUFFER_LIMIT.get();
400 GLOBAL_BUFFER_LIMIT.set(64.0 * 1024.0 * 1024.0);
401
402 let buf = AlignedBuffer::try_new(1_048_576, 4096);
403 assert!(buf.is_ok(), "1MB buffer allocation should succeed");
404 let buf = buf.unwrap();
405 assert!(buf.capacity() >= 1_048_576);
406
407 GLOBAL_BUFFER_LIMIT.set(saved);
408 }
409
410 #[test]
411 fn test_small_buffer_no_populate() {
412 let _guard = BUFFER_LIMIT_LOCK.lock().unwrap();
413 let saved = GLOBAL_BUFFER_LIMIT.get();
414 GLOBAL_BUFFER_LIMIT.set(64.0 * 1024.0 * 1024.0);
415
416 let buf = AlignedBuffer::try_new(4096, 4096);
417 assert!(buf.is_ok(), "4KB buffer allocation should succeed");
418 assert!(buf.unwrap().capacity() >= 4096);
419
420 GLOBAL_BUFFER_LIMIT.set(saved);
421 }
422}