oximedia_core/alloc/buffer_pool.rs
1//! Buffer pool for zero-copy memory management.
2//!
3//! This module provides a [`BufferPool`] for efficient buffer reuse,
4//! avoiding allocation overhead in performance-critical paths.
5
6use std::sync::{Arc, RwLock};
7
8/// A pool of reusable buffers for zero-copy operations.
9///
10/// `BufferPool` manages a collection of fixed-size buffers that can be
11/// acquired and released. This helps reduce allocation overhead in
12/// hot paths like frame decoding.
13///
14/// # Thread Safety
15///
16/// `BufferPool` is thread-safe and can be shared across threads.
17/// Acquired buffers are wrapped in `Arc<RwLock<_>>` for safe concurrent access.
18///
19/// # Examples
20///
21/// ```
22/// use oximedia_core::alloc::BufferPool;
23///
24/// // Create a pool with 4 buffers of 1MB each
25/// let pool = BufferPool::new(4, 1024 * 1024);
26///
27/// // Acquire a buffer
28/// let buffer = pool.acquire();
29/// assert!(buffer.is_some());
30///
31/// // Write to the buffer
32/// {
33/// let mut guard = buffer.as_ref().expect("buffer present").write().expect("lock ok");
34/// guard[0] = 42;
35/// }
36///
37/// // Release it back to the pool
38/// pool.release(buffer.expect("buffer present"));
39/// ```
40#[derive(Debug)]
41pub struct BufferPool {
42 /// Available buffers in the pool.
43 buffers: RwLock<Vec<Arc<RwLock<Vec<u8>>>>>,
44 /// Size of each buffer in bytes.
45 buffer_size: usize,
46 /// Maximum number of buffers allowed in the pool.
47 max_buffers: usize,
48}
49
50impl BufferPool {
51 /// Creates a new buffer pool.
52 ///
53 /// # Arguments
54 ///
55 /// * `count` - Initial number of buffers to allocate
56 /// * `buffer_size` - Size of each buffer in bytes
57 ///
58 /// # Examples
59 ///
60 /// ```
61 /// use oximedia_core::alloc::BufferPool;
62 ///
63 /// let pool = BufferPool::new(8, 4096);
64 /// ```
65 #[must_use]
66 pub fn new(count: usize, buffer_size: usize) -> Self {
67 let buffers: Vec<_> = (0..count)
68 .map(|_| Arc::new(RwLock::new(vec![0u8; buffer_size])))
69 .collect();
70
71 Self {
72 buffers: RwLock::new(buffers),
73 buffer_size,
74 max_buffers: count,
75 }
76 }
77
78 /// Creates a new buffer pool with a specified maximum capacity.
79 ///
80 /// The pool starts empty and allocates buffers on demand up to `max_buffers`.
81 ///
82 /// # Arguments
83 ///
84 /// * `max_buffers` - Maximum number of buffers the pool can hold
85 /// * `buffer_size` - Size of each buffer in bytes
86 ///
87 /// # Examples
88 ///
89 /// ```
90 /// use oximedia_core::alloc::BufferPool;
91 ///
92 /// let pool = BufferPool::with_capacity(16, 8192);
93 /// ```
94 #[must_use]
95 pub fn with_capacity(max_buffers: usize, buffer_size: usize) -> Self {
96 Self {
97 buffers: RwLock::new(Vec::with_capacity(max_buffers)),
98 buffer_size,
99 max_buffers,
100 }
101 }
102
103 /// Acquires a buffer from the pool.
104 ///
105 /// Returns `None` if no buffers are available. Use [`acquire_or_alloc`](Self::acquire_or_alloc)
106 /// if you want to allocate a new buffer when the pool is empty.
107 ///
108 /// # Examples
109 ///
110 /// ```
111 /// use oximedia_core::alloc::BufferPool;
112 ///
113 /// let pool = BufferPool::new(2, 1024);
114 /// let buf1 = pool.acquire();
115 /// let buf2 = pool.acquire();
116 /// let buf3 = pool.acquire(); // Returns None, pool exhausted
117 /// assert!(buf1.is_some());
118 /// assert!(buf2.is_some());
119 /// assert!(buf3.is_none());
120 /// ```
121 #[must_use]
122 pub fn acquire(&self) -> Option<Arc<RwLock<Vec<u8>>>> {
123 self.buffers.write().ok()?.pop()
124 }
125
126 /// Acquires a buffer from the pool, allocating a new one if necessary.
127 ///
128 /// If the pool is empty, allocates a new buffer. This is useful when
129 /// you need a buffer regardless of pool state.
130 ///
131 /// # Examples
132 ///
133 /// ```
134 /// use oximedia_core::alloc::BufferPool;
135 ///
136 /// let pool = BufferPool::new(0, 1024); // Empty pool
137 /// let buffer = pool.acquire_or_alloc();
138 /// assert_eq!(buffer.read().expect("lock ok").len(), 1024);
139 /// ```
140 #[must_use]
141 pub fn acquire_or_alloc(&self) -> Arc<RwLock<Vec<u8>>> {
142 self.acquire()
143 .unwrap_or_else(|| Arc::new(RwLock::new(vec![0u8; self.buffer_size])))
144 }
145
146 /// Releases a buffer back to the pool.
147 ///
148 /// The buffer should have been previously acquired from this pool.
149 /// If the pool is at capacity, the buffer is dropped.
150 ///
151 /// # Arguments
152 ///
153 /// * `buffer` - The buffer to return to the pool
154 ///
155 /// # Examples
156 ///
157 /// ```
158 /// use oximedia_core::alloc::BufferPool;
159 ///
160 /// let pool = BufferPool::new(2, 1024);
161 /// let buffer = pool.acquire().expect("buffer available");
162 /// // Use the buffer...
163 /// pool.release(buffer);
164 /// ```
165 pub fn release(&self, buffer: Arc<RwLock<Vec<u8>>>) {
166 if let Ok(mut buffers) = self.buffers.write() {
167 if buffers.len() < self.max_buffers {
168 // Clear the buffer for security and consistency
169 if let Ok(mut guard) = buffer.write() {
170 guard.fill(0);
171 }
172 buffers.push(buffer);
173 }
174 // If at capacity, the buffer is simply dropped
175 }
176 }
177
178 /// Returns the number of buffers currently available in the pool.
179 ///
180 /// # Examples
181 ///
182 /// ```
183 /// use oximedia_core::alloc::BufferPool;
184 ///
185 /// let pool = BufferPool::new(4, 1024);
186 /// assert_eq!(pool.available(), 4);
187 /// let _buf = pool.acquire();
188 /// assert_eq!(pool.available(), 3);
189 /// ```
190 #[must_use]
191 pub fn available(&self) -> usize {
192 self.buffers.read().map(|b| b.len()).unwrap_or(0)
193 }
194
195 /// Returns the size of each buffer in the pool.
196 ///
197 /// # Examples
198 ///
199 /// ```
200 /// use oximedia_core::alloc::BufferPool;
201 ///
202 /// let pool = BufferPool::new(2, 4096);
203 /// assert_eq!(pool.buffer_size(), 4096);
204 /// ```
205 #[must_use]
206 pub fn buffer_size(&self) -> usize {
207 self.buffer_size
208 }
209
210 /// Returns the maximum number of buffers the pool can hold.
211 ///
212 /// # Examples
213 ///
214 /// ```
215 /// use oximedia_core::alloc::BufferPool;
216 ///
217 /// let pool = BufferPool::new(8, 1024);
218 /// assert_eq!(pool.max_buffers(), 8);
219 /// ```
220 #[must_use]
221 pub fn max_buffers(&self) -> usize {
222 self.max_buffers
223 }
224}
225
226impl Default for BufferPool {
227 fn default() -> Self {
228 Self::new(4, 4096)
229 }
230}
231
232#[cfg(test)]
233mod tests {
234 use super::*;
235
236 #[test]
237 fn test_new() {
238 let pool = BufferPool::new(4, 1024);
239 assert_eq!(pool.available(), 4);
240 assert_eq!(pool.buffer_size(), 1024);
241 assert_eq!(pool.max_buffers(), 4);
242 }
243
244 #[test]
245 fn test_with_capacity() {
246 let pool = BufferPool::with_capacity(8, 2048);
247 assert_eq!(pool.available(), 0);
248 assert_eq!(pool.buffer_size(), 2048);
249 assert_eq!(pool.max_buffers(), 8);
250 }
251
252 #[test]
253 fn test_acquire_release() {
254 let pool = BufferPool::new(2, 1024);
255 assert_eq!(pool.available(), 2);
256
257 let buf1 = pool.acquire().expect("acquire should succeed");
258 assert_eq!(pool.available(), 1);
259
260 let buf2 = pool.acquire().expect("acquire should succeed");
261 assert_eq!(pool.available(), 0);
262
263 assert!(pool.acquire().is_none());
264
265 pool.release(buf1);
266 assert_eq!(pool.available(), 1);
267
268 pool.release(buf2);
269 assert_eq!(pool.available(), 2);
270 }
271
272 #[test]
273 fn test_acquire_or_alloc() {
274 let pool = BufferPool::new(0, 1024);
275 assert_eq!(pool.available(), 0);
276
277 let buffer = pool.acquire_or_alloc();
278 assert_eq!(buffer.read().expect("read lock should succeed").len(), 1024);
279 }
280
281 #[test]
282 fn test_buffer_contents() {
283 let pool = BufferPool::new(1, 64);
284 let buffer = pool.acquire().expect("acquire should succeed");
285
286 // Write to buffer
287 {
288 let mut guard = buffer.write().expect("write lock should succeed");
289 guard[0] = 42;
290 guard[63] = 255;
291 }
292
293 // Read from buffer
294 {
295 let guard = buffer.read().expect("read lock should succeed");
296 assert_eq!(guard[0], 42);
297 assert_eq!(guard[63], 255);
298 }
299
300 // Release and reacquire - buffer should be zeroed
301 pool.release(buffer);
302 let buffer = pool.acquire().expect("acquire should succeed");
303 {
304 let guard = buffer.read().expect("read lock should succeed");
305 assert_eq!(guard[0], 0);
306 assert_eq!(guard[63], 0);
307 }
308 }
309
310 #[test]
311 fn test_default() {
312 let pool = BufferPool::default();
313 assert_eq!(pool.available(), 4);
314 assert_eq!(pool.buffer_size(), 4096);
315 }
316
317 #[test]
318 fn test_release_at_capacity() {
319 let pool = BufferPool::new(2, 1024);
320 let extra_buffer = Arc::new(RwLock::new(vec![0u8; 1024]));
321
322 // Pool is full, releasing should not add more buffers
323 pool.release(extra_buffer);
324 assert_eq!(pool.available(), 2); // Still 2, not 3
325 }
326}