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 #[cfg(feature = "reactor")]
37 pub(crate) fn placeholder() -> Buffer {
38 Buffer {
39 data: Box::default(),
40 len: 0,
41 class: SizeClass::SMALL,
42 }
43 }
44
45 fn new(class: SizeClass) -> Buffer {
46 Buffer {
47 data: vec![0u8; class.bytes() as usize].into_boxed_slice(),
48 len: 0,
49 class,
50 }
51 }
52
53 #[must_use]
55 pub fn as_slice(&self) -> &[u8] {
56 &self.data[..self.len]
57 }
58
59 pub fn as_mut_slice(&mut self) -> &mut [u8] {
61 &mut self.data[..self.len]
62 }
63
64 pub fn spare(&mut self) -> &mut [u8] {
66 &mut self.data[self.len..]
67 }
68
69 #[must_use]
71 pub fn len(&self) -> usize {
72 self.len
73 }
74
75 #[must_use]
77 pub fn is_empty(&self) -> bool {
78 self.len == 0
79 }
80
81 pub fn set_len(&mut self, n: usize) {
83 self.len = n.min(self.data.len());
84 }
85
86 #[must_use]
88 pub fn capacity(&self) -> usize {
89 self.data.len()
90 }
91
92 #[must_use]
94 pub fn class(&self) -> SizeClass {
95 self.class
96 }
97
98 pub fn clear(&mut self) {
100 self.len = 0;
101 }
102
103 pub(crate) fn wipe(&mut self) {
105 let used = &mut self.data[..self.len];
106 used.fill(0);
107 std::hint::black_box(&*used);
110 self.len = 0;
111 }
112
113 pub(crate) fn compact(&mut self, from: usize) {
115 let from = from.min(self.len);
116 self.data.copy_within(from..self.len, 0);
117 self.len -= from;
118 }
119}
120
121impl fmt::Debug for Buffer {
122 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
123 f.debug_struct("Buffer")
124 .field("len", &self.len)
125 .field("capacity", &self.data.len())
126 .finish()
127 }
128}
129
130#[non_exhaustive]
137#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
138pub struct PoolStats {
139 pub allocated: u64,
141 pub free: u64,
143 pub budget_left: u64,
145 pub budget_refusals: u64,
147}
148
149pub struct BufferPool {
152 lists: Vec<(SizeClass, Vec<Buffer>)>,
153 budget: Arc<AtomicU64>,
154 wipe: bool,
155 allocated: u64,
156 refusals: u64,
157}
158
159impl BufferPool {
160 pub(crate) fn new(budget: Arc<AtomicU64>, wipe: bool) -> BufferPool {
161 BufferPool {
162 lists: Vec::with_capacity(4),
163 budget,
164 wipe,
165 allocated: 0,
166 refusals: 0,
167 }
168 }
169
170 #[must_use]
178 pub fn standalone(bytes: u64) -> BufferPool {
179 BufferPool::new(Arc::new(AtomicU64::new(bytes)), true)
180 }
181
182 pub fn take(&mut self, class: SizeClass) -> Option<Buffer> {
184 if let Some(buf) = self.list(class).pop() {
185 return Some(buf);
186 }
187 let bytes = u64::from(class.bytes());
188 let mut left = self.budget.load(Ordering::Relaxed);
189 loop {
190 if left < bytes {
191 self.refusals += 1;
192 return None;
193 }
194 match self.budget.compare_exchange_weak(
195 left,
196 left - bytes,
197 Ordering::AcqRel,
198 Ordering::Relaxed,
199 ) {
200 Ok(_) => break,
201 Err(actual) => left = actual,
202 }
203 }
204 self.allocated += 1;
205 Some(Buffer::new(class))
206 }
207
208 pub fn put(&mut self, mut buf: Buffer) {
212 if self.wipe {
213 buf.wipe();
214 } else {
215 buf.clear();
216 }
217 let class = buf.class;
218 self.list(class).push(buf);
219 }
220
221 #[must_use]
223 pub fn stats(&self) -> PoolStats {
224 PoolStats {
225 allocated: self.allocated,
226 free: self.lists.iter().map(|(_, l)| l.len() as u64).sum(),
227 budget_left: self.budget.load(Ordering::Relaxed),
228 budget_refusals: self.refusals,
229 }
230 }
231
232 fn list(&mut self, class: SizeClass) -> &mut Vec<Buffer> {
233 let pos = match self.lists.iter().position(|(c, _)| *c == class) {
234 Some(p) => p,
235 None => {
236 self.lists.push((class, Vec::new()));
237 self.lists.len() - 1
238 }
239 };
240 &mut self.lists[pos].1
241 }
242}
243
244impl fmt::Debug for BufferPool {
245 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
246 f.debug_struct("BufferPool")
247 .field("stats", &self.stats())
248 .finish()
249 }
250}
251
252#[cfg(test)]
253mod tests {
254 use super::*;
255
256 #[test]
257 fn test_pool_reuses_and_wipes() {
258 let mut pool = BufferPool::standalone(1 << 20);
259 let mut b = pool.take(SizeClass::SMALL).unwrap();
260 b.spare()[..6].copy_from_slice(b"secret");
261 b.set_len(6);
262 pool.put(b);
263 let mut b = pool.take(SizeClass::SMALL).unwrap();
264 assert_eq!(pool.stats().allocated, 1);
265 assert!(b.is_empty());
266 b.set_len(6);
267 assert_eq!(b.as_slice(), &[0u8; 6]);
268 }
269
270 #[test]
271 fn test_pool_budget_bounds_allocation() {
272 let mut pool = BufferPool::standalone(u64::from(SizeClass::SMALL.bytes()) * 2);
273 let a = pool.take(SizeClass::SMALL).unwrap();
274 let _b = pool.take(SizeClass::SMALL).unwrap();
275 assert!(pool.take(SizeClass::SMALL).is_none());
276 assert_eq!(pool.stats().budget_refusals, 1);
277 pool.put(a);
278 assert!(pool.take(SizeClass::SMALL).is_some());
279 }
280
281 #[test]
282 fn test_set_len_saturates_and_compact_moves_tail() {
283 let mut pool = BufferPool::standalone(1 << 20);
284 let mut b = pool.take(SizeClass::SMALL).unwrap();
285 b.set_len(usize::MAX);
286 assert_eq!(b.len(), b.capacity());
287 b.set_len(0);
288 b.spare()[..4].copy_from_slice(b"abcd");
289 b.set_len(4);
290 b.compact(2);
291 assert_eq!(b.as_slice(), b"cd");
292 b.compact(99);
293 assert!(b.is_empty());
294 }
295
296 #[test]
297 fn test_pool_without_wipe_keeps_bytes_but_empties() {
298 let mut pool = BufferPool::new(Arc::new(AtomicU64::new(1 << 20)), false);
299 let mut b = pool.take(SizeClass::SMALL).unwrap();
300 b.spare()[0] = 9;
301 b.set_len(1);
302 pool.put(b);
303 let b = pool.take(SizeClass::SMALL).unwrap();
304 assert!(b.is_empty());
305 }
306
307 #[test]
309 #[ignore = "a benchmark"]
310 fn bench_wipe() {
311 for (name, used) in [("2 KiB", 2048), ("16 KiB", 16 * 1024)] {
312 let mut b = Buffer::new(SizeClass::MEDIUM);
313 let rounds = 1_000_000u32;
314 let started = std::time::Instant::now();
315 for _ in 0..rounds {
316 b.set_len(used);
317 b.wipe();
318 }
319 let ns = started.elapsed().as_nanos() as f64 / f64::from(rounds);
320 println!("Wipe {name}: {ns:.1} ns");
321 }
322 }
323}