Skip to main content

net_io/
buf.rs

1//! Pooled buffers with a total budget, wiped on release.
2
3use std::fmt;
4use std::sync::Arc;
5use std::sync::atomic::{AtomicU64, Ordering};
6
7use crate::limits::SizeClass;
8
9/// A byte buffer from a pool: a fixed capacity (its size class) and a filled length.
10///
11/// # Examples
12///
13/// ```
14/// use net_io::{BufferPool, SizeClass};
15/// let mut pool = BufferPool::standalone(1 << 20);
16/// let mut buf = pool.take(SizeClass::SMALL).unwrap();
17/// buf.spare()[..5].copy_from_slice(b"hello");
18/// buf.set_len(5);
19/// assert_eq!(buf.as_slice(), b"hello");
20/// pool.put(buf);
21/// ```
22pub struct Buffer {
23    data: Box<[u8]>,
24    len: usize,
25    class: SizeClass,
26}
27
28impl Buffer {
29    /// A buffer not drawn from any pool's free list (the caller already charged the budget).
30    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    /// The filled part.
43    #[must_use]
44    pub fn as_slice(&self) -> &[u8] {
45        &self.data[..self.len]
46    }
47
48    /// The filled part, mutable.
49    pub fn as_mut_slice(&mut self) -> &mut [u8] {
50        &mut self.data[..self.len]
51    }
52
53    /// The unfilled tail.
54    pub fn spare(&mut self) -> &mut [u8] {
55        &mut self.data[self.len..]
56    }
57
58    /// The filled length.
59    #[must_use]
60    pub fn len(&self) -> usize {
61        self.len
62    }
63
64    /// Whether nothing is filled.
65    #[must_use]
66    pub fn is_empty(&self) -> bool {
67        self.len == 0
68    }
69
70    /// Sets the filled length; saturates at the capacity.
71    pub fn set_len(&mut self, n: usize) {
72        self.len = n.min(self.data.len());
73    }
74
75    /// The capacity in bytes.
76    #[must_use]
77    pub fn capacity(&self) -> usize {
78        self.data.len()
79    }
80
81    /// The size class.
82    #[must_use]
83    pub fn class(&self) -> SizeClass {
84        self.class
85    }
86
87    /// Empties the buffer (does not wipe; the pool wipes on release).
88    pub fn clear(&mut self) {
89        self.len = 0;
90    }
91
92    /// Overwrites the filled part with zeroes and empties the buffer.
93    pub(crate) fn wipe(&mut self) {
94        let used = &mut self.data[..self.len];
95        used.fill(0);
96        // The buffer stays reachable and is read again by the next owner, so the
97        // stores cannot be removed as dead; black_box makes that explicit.
98        std::hint::black_box(&*used);
99        self.len = 0;
100    }
101
102    /// Moves the bytes `from..` to the front.
103    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/// Counts of one pool.
120///
121/// ```
122/// let pool = net_io::BufferPool::standalone(1 << 20);
123/// assert_eq!(pool.stats().allocated, 0);
124/// ```
125#[non_exhaustive]
126#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
127pub struct PoolStats {
128    /// Buffers this pool allocated.
129    pub allocated: u64,
130    /// Buffers in its free lists.
131    pub free: u64,
132    /// Bytes left in the shared budget.
133    pub budget_left: u64,
134    /// Takes refused because the budget was spent.
135    pub budget_refusals: u64,
136}
137
138/// A worker's buffer pool: free lists per size class, drawing new buffers from a
139/// budget shared by every worker of a server.
140pub 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    /// A pool with its own budget of `bytes`, for use outside a server.
160    ///
161    /// ```
162    /// let mut pool = net_io::BufferPool::standalone(64 * 1024);
163    /// assert!(pool.take(net_io::SizeClass::LARGE).is_some());
164    /// assert!(pool.take(net_io::SizeClass::LARGE).is_none()); // budget spent
165    /// ```
166    #[must_use]
167    pub fn standalone(bytes: u64) -> BufferPool {
168        BufferPool::new(Arc::new(AtomicU64::new(bytes)), true)
169    }
170
171    /// A buffer of `class`, or `None` when the free list is empty and the budget is spent.
172    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    /// Returns a buffer; its used part is wiped first unless wiping is off.
198    ///
199    /// A buffer from another worker's pool is accepted (it is just memory).
200    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    /// Counts of this pool.
211    #[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}