1use std::fmt;
4use std::sync::Arc;
5use std::sync::atomic::{AtomicU64, Ordering};
6
7use crate::limits::SizeClass;
8
9pub struct Buffer {
23 data: Box<[u8]>,
24 len: usize,
25 class: SizeClass,
26}
27
28impl Buffer {
29 pub(crate) fn detached(class: SizeClass) -> Buffer {
31 Buffer::new(class)
32 }
33
34 fn new(class: SizeClass) -> Buffer {
35 Buffer {
36 data: vec![0u8; class.bytes() as usize].into_boxed_slice(),
37 len: 0,
38 class,
39 }
40 }
41
42 #[must_use]
44 pub fn as_slice(&self) -> &[u8] {
45 &self.data[..self.len]
46 }
47
48 pub fn as_mut_slice(&mut self) -> &mut [u8] {
50 &mut self.data[..self.len]
51 }
52
53 pub fn spare(&mut self) -> &mut [u8] {
55 &mut self.data[self.len..]
56 }
57
58 #[must_use]
60 pub fn len(&self) -> usize {
61 self.len
62 }
63
64 #[must_use]
66 pub fn is_empty(&self) -> bool {
67 self.len == 0
68 }
69
70 pub fn set_len(&mut self, n: usize) {
72 self.len = n.min(self.data.len());
73 }
74
75 #[must_use]
77 pub fn capacity(&self) -> usize {
78 self.data.len()
79 }
80
81 #[must_use]
83 pub fn class(&self) -> SizeClass {
84 self.class
85 }
86
87 pub fn clear(&mut self) {
89 self.len = 0;
90 }
91
92 pub(crate) fn wipe(&mut self) {
94 let used = &mut self.data[..self.len];
95 used.fill(0);
96 std::hint::black_box(&*used);
99 self.len = 0;
100 }
101
102 pub(crate) fn compact(&mut self, from: usize) {
104 let from = from.min(self.len);
105 self.data.copy_within(from..self.len, 0);
106 self.len -= from;
107 }
108}
109
110impl fmt::Debug for Buffer {
111 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
112 f.debug_struct("Buffer")
113 .field("len", &self.len)
114 .field("capacity", &self.data.len())
115 .finish()
116 }
117}
118
119#[non_exhaustive]
126#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
127pub struct PoolStats {
128 pub allocated: u64,
130 pub free: u64,
132 pub budget_left: u64,
134 pub budget_refusals: u64,
136}
137
138pub struct BufferPool {
141 lists: Vec<(SizeClass, Vec<Buffer>)>,
142 budget: Arc<AtomicU64>,
143 wipe: bool,
144 allocated: u64,
145 refusals: u64,
146}
147
148impl BufferPool {
149 pub(crate) fn new(budget: Arc<AtomicU64>, wipe: bool) -> BufferPool {
150 BufferPool {
151 lists: Vec::with_capacity(4),
152 budget,
153 wipe,
154 allocated: 0,
155 refusals: 0,
156 }
157 }
158
159 #[must_use]
167 pub fn standalone(bytes: u64) -> BufferPool {
168 BufferPool::new(Arc::new(AtomicU64::new(bytes)), true)
169 }
170
171 pub fn take(&mut self, class: SizeClass) -> Option<Buffer> {
173 if let Some(buf) = self.list(class).pop() {
174 return Some(buf);
175 }
176 let bytes = u64::from(class.bytes());
177 let mut left = self.budget.load(Ordering::Relaxed);
178 loop {
179 if left < bytes {
180 self.refusals += 1;
181 return None;
182 }
183 match self.budget.compare_exchange_weak(
184 left,
185 left - bytes,
186 Ordering::AcqRel,
187 Ordering::Relaxed,
188 ) {
189 Ok(_) => break,
190 Err(actual) => left = actual,
191 }
192 }
193 self.allocated += 1;
194 Some(Buffer::new(class))
195 }
196
197 pub fn put(&mut self, mut buf: Buffer) {
201 if self.wipe {
202 buf.wipe();
203 } else {
204 buf.clear();
205 }
206 let class = buf.class;
207 self.list(class).push(buf);
208 }
209
210 #[must_use]
212 pub fn stats(&self) -> PoolStats {
213 PoolStats {
214 allocated: self.allocated,
215 free: self.lists.iter().map(|(_, l)| l.len() as u64).sum(),
216 budget_left: self.budget.load(Ordering::Relaxed),
217 budget_refusals: self.refusals,
218 }
219 }
220
221 fn list(&mut self, class: SizeClass) -> &mut Vec<Buffer> {
222 let pos = match self.lists.iter().position(|(c, _)| *c == class) {
223 Some(p) => p,
224 None => {
225 self.lists.push((class, Vec::new()));
226 self.lists.len() - 1
227 }
228 };
229 &mut self.lists[pos].1
230 }
231}
232
233impl fmt::Debug for BufferPool {
234 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
235 f.debug_struct("BufferPool")
236 .field("stats", &self.stats())
237 .finish()
238 }
239}
240
241#[cfg(test)]
242mod tests {
243 use super::*;
244
245 #[test]
246 fn test_pool_reuses_and_wipes() {
247 let mut pool = BufferPool::standalone(1 << 20);
248 let mut b = pool.take(SizeClass::SMALL).unwrap();
249 b.spare()[..6].copy_from_slice(b"secret");
250 b.set_len(6);
251 pool.put(b);
252 let mut b = pool.take(SizeClass::SMALL).unwrap();
253 assert_eq!(pool.stats().allocated, 1);
254 assert!(b.is_empty());
255 b.set_len(6);
256 assert_eq!(b.as_slice(), &[0u8; 6]);
257 }
258
259 #[test]
260 fn test_pool_budget_bounds_allocation() {
261 let mut pool = BufferPool::standalone(u64::from(SizeClass::SMALL.bytes()) * 2);
262 let a = pool.take(SizeClass::SMALL).unwrap();
263 let _b = pool.take(SizeClass::SMALL).unwrap();
264 assert!(pool.take(SizeClass::SMALL).is_none());
265 assert_eq!(pool.stats().budget_refusals, 1);
266 pool.put(a);
267 assert!(pool.take(SizeClass::SMALL).is_some());
268 }
269
270 #[test]
271 fn test_set_len_saturates_and_compact_moves_tail() {
272 let mut pool = BufferPool::standalone(1 << 20);
273 let mut b = pool.take(SizeClass::SMALL).unwrap();
274 b.set_len(usize::MAX);
275 assert_eq!(b.len(), b.capacity());
276 b.set_len(0);
277 b.spare()[..4].copy_from_slice(b"abcd");
278 b.set_len(4);
279 b.compact(2);
280 assert_eq!(b.as_slice(), b"cd");
281 b.compact(99);
282 assert!(b.is_empty());
283 }
284
285 #[test]
286 fn test_pool_without_wipe_keeps_bytes_but_empties() {
287 let mut pool = BufferPool::new(Arc::new(AtomicU64::new(1 << 20)), false);
288 let mut b = pool.take(SizeClass::SMALL).unwrap();
289 b.spare()[0] = 9;
290 b.set_len(1);
291 pool.put(b);
292 let b = pool.take(SizeClass::SMALL).unwrap();
293 assert!(b.is_empty());
294 }
295}