1#[cfg(feature = "std")]
8use alloc::sync::Arc;
9
10#[cfg(feature = "std")]
11use bytes::BytesMut;
12#[cfg(feature = "std")]
13use crossbeam_queue::ArrayQueue;
14#[cfg(feature = "std")]
15use thread_local::ThreadLocal;
16
17#[cfg(feature = "std")]
18pub const DEFAULT_POOL_PER_WORKER: usize = 256;
19#[cfg(feature = "std")]
20pub const DEFAULT_BUFFER_BYTES: usize = 16 * 1024;
21
22#[cfg(feature = "std")]
26pub struct BufferPool {
27 inner: Arc<BufferPoolInner>,
28}
29
30#[cfg(feature = "std")]
31struct BufferPoolInner {
32 per_worker: ThreadLocal<ArrayQueue<BytesMut>>,
33 capacity_per_worker: usize,
34 buffer_bytes: usize,
35}
36
37#[cfg(feature = "std")]
38impl BufferPool {
39 #[must_use]
40 pub fn new() -> Self {
41 Self::with_config(DEFAULT_POOL_PER_WORKER, DEFAULT_BUFFER_BYTES)
42 }
43
44 #[must_use]
45 pub fn with_config(per_worker_capacity: usize, buffer_bytes: usize) -> Self {
46 Self {
47 inner: Arc::new(BufferPoolInner {
48 per_worker: ThreadLocal::new(),
49 capacity_per_worker: per_worker_capacity.max(1),
50 buffer_bytes: buffer_bytes.max(1),
51 }),
52 }
53 }
54
55 pub fn acquire(&self) -> PooledBuf {
56 let queue = self
57 .inner
58 .per_worker
59 .get_or(|| ArrayQueue::new(self.inner.capacity_per_worker));
60 let buffer = queue
61 .pop()
62 .map(|mut buf| {
63 buf.clear();
64 buf
65 })
66 .unwrap_or_else(|| BytesMut::with_capacity(self.inner.buffer_bytes));
67 PooledBuf {
68 inner: buffer,
69 pool: self.inner.clone(),
70 return_on_drop: true,
71 }
72 }
73
74 #[must_use]
75 pub fn buffer_bytes(&self) -> usize {
76 self.inner.buffer_bytes
77 }
78
79 #[must_use]
80 pub fn local_inventory(&self) -> usize {
81 match self.inner.per_worker.get() {
82 Some(queue) => queue.len(),
83 None => 0,
84 }
85 }
86}
87
88#[cfg(feature = "std")]
89impl Default for BufferPool {
90 fn default() -> Self {
91 Self::new()
92 }
93}
94
95#[cfg(feature = "std")]
96impl Clone for BufferPool {
97 fn clone(&self) -> Self {
98 Self {
99 inner: self.inner.clone(),
100 }
101 }
102}
103
104#[cfg(feature = "std")]
105pub struct PooledBuf {
106 inner: BytesMut,
107 pool: Arc<BufferPoolInner>,
108 return_on_drop: bool,
109}
110
111#[cfg(feature = "std")]
112impl PooledBuf {
113 pub fn buffer_mut(&mut self) -> &mut BytesMut {
116 &mut self.inner
117 }
118
119 pub fn take(mut self) -> BytesMut {
122 self.return_on_drop = false;
123 core::mem::take(&mut self.inner)
124 }
125}
126
127#[cfg(feature = "std")]
128impl Drop for PooledBuf {
129 fn drop(&mut self) {
130 if !self.return_on_drop {
131 return;
132 }
133 let buffer = core::mem::take(&mut self.inner);
134 let queue = self
135 .pool
136 .per_worker
137 .get_or(|| ArrayQueue::new(self.pool.capacity_per_worker));
138 let _ = queue.push(buffer);
141 }
142}
143
144#[cfg(all(test, feature = "std"))]
145#[allow(clippy::unwrap_used, clippy::expect_used)]
146mod tests {
147 use super::*;
148
149 #[test]
150 fn acquire_returns_buffer_with_zero_length() {
151 let pool = BufferPool::new();
152 let buf = pool.acquire();
153 assert_eq!(buf.inner.len(), 0);
154 }
155
156 #[test]
157 fn returned_buffer_is_reused_by_next_acquire_on_same_thread() {
158 let pool = BufferPool::with_config(4, 64);
159 let mut first = pool.acquire();
160 first.buffer_mut().extend_from_slice(b"abc");
161 let initial_capacity = first.inner.capacity();
162 drop(first);
163 assert_eq!(pool.local_inventory(), 1);
164 let second = pool.acquire();
165 assert_eq!(
166 second.inner.capacity(),
167 initial_capacity,
168 "returned buffer must be reused with its allocated capacity",
169 );
170 assert_eq!(pool.local_inventory(), 0);
171 }
172
173 #[test]
174 fn capacity_overflow_drops_extra_buffers() {
175 let pool = BufferPool::with_config(2, 64);
176 let one = pool.acquire();
177 let two = pool.acquire();
178 let three = pool.acquire();
179 drop(one);
180 drop(two);
181 drop(three);
182 assert!(
183 pool.local_inventory() <= 2,
184 "local queue must respect its capacity"
185 );
186 }
187
188 #[test]
189 fn take_consumes_buffer_without_returning_to_pool() {
190 let pool = BufferPool::with_config(4, 64);
191 let buf = pool.acquire();
192 let _owned = buf.take();
193 assert_eq!(pool.local_inventory(), 0);
194 }
195}