Skip to main content

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}