1use parking_lot::Mutex;
10use std::sync::Arc;
11
12pub struct BufPool {
14 free: Mutex<Vec<Vec<u8>>>,
15 capacity: usize,
16 buf_hint: usize,
17}
18
19impl BufPool {
20 pub fn new(capacity: usize, buf_hint: usize) -> Arc<Self> {
22 Arc::new(Self {
23 free: Mutex::new(Vec::with_capacity(capacity)),
24 capacity,
25 buf_hint,
26 })
27 }
28
29 pub fn take(&self) -> Vec<u8> {
31 match self.free.lock().pop() {
32 Some(mut b) => {
33 b.clear();
34 b
35 }
36 None => Vec::with_capacity(self.buf_hint),
37 }
38 }
39
40 pub fn put(&self, buf: Vec<u8>) {
42 if buf.capacity() > self.buf_hint * 4 {
45 return;
46 }
47 let mut free = self.free.lock();
48 if free.len() < self.capacity {
49 free.push(buf);
50 }
51 }
52}
53
54pub struct Pooled {
56 buf: Option<Vec<u8>>,
57 pool: Arc<BufPool>,
58}
59
60impl Pooled {
61 pub fn new(pool: Arc<BufPool>) -> Self {
62 let buf = pool.take();
63 Self {
64 buf: Some(buf),
65 pool,
66 }
67 }
68}
69
70impl std::ops::Deref for Pooled {
71 type Target = Vec<u8>;
72 fn deref(&self) -> &Vec<u8> {
73 self.buf.as_ref().expect("buffer taken only on drop")
74 }
75}
76
77impl std::ops::DerefMut for Pooled {
78 fn deref_mut(&mut self) -> &mut Vec<u8> {
79 self.buf.as_mut().expect("buffer taken only on drop")
80 }
81}
82
83impl Drop for Pooled {
84 fn drop(&mut self) {
85 if let Some(b) = self.buf.take() {
86 self.pool.put(b);
87 }
88 }
89}
90
91pub struct ObjPool<T> {
99 free: Mutex<Vec<T>>,
100 capacity: usize,
101}
102
103impl<T> ObjPool<T> {
104 pub fn new(capacity: usize) -> Arc<Self> {
105 Arc::new(Self {
106 free: Mutex::new(Vec::with_capacity(capacity)),
107 capacity,
108 })
109 }
110
111 pub fn take_or<F: FnOnce() -> T>(&self, make: F) -> T {
113 match self.free.lock().pop() {
114 Some(o) => o,
115 None => make(),
116 }
117 }
118
119 pub fn put(&self, obj: T) {
120 let mut free = self.free.lock();
121 if free.len() < self.capacity {
122 free.push(obj);
123 }
124 }
125}
126
127#[cfg(test)]
128mod tests {
129 use super::*;
130
131 #[test]
132 fn obj_pool_reuses_and_bounds() {
133 let pool: Arc<ObjPool<Vec<u32>>> = ObjPool::new(2);
134 let made = std::sync::atomic::AtomicUsize::new(0);
135 let mk = || {
136 made.fetch_add(1, std::sync::atomic::Ordering::SeqCst);
137 Vec::new()
138 };
139 let a = pool.take_or(mk);
140 let b = pool.take_or(mk);
141 assert_eq!(made.load(std::sync::atomic::Ordering::SeqCst), 2);
142 pool.put(a);
143 pool.put(b);
144 let _ = pool.take_or(mk);
146 let _ = pool.take_or(mk);
147 assert_eq!(made.load(std::sync::atomic::Ordering::SeqCst), 2);
148 for _ in 0..8 {
150 pool.put(Vec::new());
151 }
152 assert_eq!(pool.free.lock().len(), 2);
153 }
154
155 #[test]
156 fn buffers_are_recycled_not_reallocated() {
157 let pool = BufPool::new(4, 1024);
158 let ptr = {
159 let mut b = Pooled::new(pool.clone());
160 b.extend_from_slice(&[1u8; 512]);
161 b.as_ptr()
162 };
163 let b2 = Pooled::new(pool.clone());
164 assert!(b2.is_empty(), "recycled buffers come back empty");
165 assert_eq!(b2.as_ptr(), ptr, "the same allocation came back");
166 }
167
168 #[test]
169 fn pool_respects_its_capacity() {
170 let pool = BufPool::new(2, 128);
171 let bufs: Vec<_> = (0..8).map(|_| Pooled::new(pool.clone())).collect();
172 drop(bufs);
173 assert_eq!(pool.free.lock().len(), 2);
174 }
175
176 #[test]
177 fn oversized_buffers_are_dropped_rather_than_retained() {
178 let pool = BufPool::new(4, 100);
179 {
180 let mut b = Pooled::new(pool.clone());
181 b.resize(10_000, 0);
182 }
183 assert_eq!(pool.free.lock().len(), 0);
184 }
185
186 #[test]
187 fn concurrent_take_and_put_is_sound() {
188 let pool = BufPool::new(8, 4096);
189 let threads: Vec<_> = (0..8)
190 .map(|_| {
191 let pool = pool.clone();
192 std::thread::spawn(move || {
193 for _ in 0..1000 {
194 let mut b = Pooled::new(pool.clone());
195 b.extend_from_slice(&[7u8; 100]);
196 assert_eq!(b.len(), 100);
197 }
198 })
199 })
200 .collect();
201 for t in threads {
202 t.join().unwrap();
203 }
204 assert!(pool.free.lock().len() <= 8);
205 }
206}