Skip to main content

conmonrs/streaming_server/
budget.rs

1//! The bytes a streaming connection holds for the client. The streaming
2//! protocols have no flow control, so a connection bounds what it buffers
3//! for all of its streams together and stops reading the client once that
4//! is used up. The data gives its share back once it got taken.
5
6use bytes::{Bytes, BytesMut};
7use std::{ops::Deref, sync::Arc};
8use tokio::sync::{OwnedSemaphorePermit, Semaphore};
9
10/// The bytes a connection may hold. Clones share them.
11#[derive(Clone, Debug)]
12pub(super) struct Budget {
13    bytes: Arc<Semaphore>,
14    max: u32,
15}
16
17impl Budget {
18    /// A budget of `max` bytes.
19    pub(super) fn new(max: u32) -> Self {
20        Self {
21            bytes: Arc::new(Semaphore::new(max as usize)),
22            max,
23        }
24    }
25
26    /// Hold data within the budget, waits until there is room for it. Data
27    /// larger than the whole budget takes all of it, so that every hold
28    /// counts.
29    pub(super) async fn hold(&self, data: Bytes) -> Held {
30        let len = data.len();
31        self.hold_as(data, len).await
32    }
33
34    /// Hold data which counts as `len` bytes, see [`Self::hold`].
35    pub(super) async fn hold_as(&self, data: Bytes, len: usize) -> Held {
36        let len = u32::try_from(len).map_or(self.max, |len| len.min(self.max));
37        // The semaphore never gets closed.
38        let permit = self.bytes.clone().acquire_many_owned(len).await.ok();
39        Held { data, permit }
40    }
41
42    /// The bytes which are not held right now.
43    #[cfg(test)]
44    pub(super) fn available(&self) -> usize {
45        self.bytes.available_permits()
46    }
47}
48
49/// Data which counts against a [`Budget`] until it gets dropped.
50#[derive(Debug)]
51pub(super) struct Held {
52    data: Bytes,
53    permit: Option<OwnedSemaphorePermit>,
54}
55
56impl Held {
57    /// The data, its share of the budget is given back.
58    pub(super) fn into_bytes(self) -> Bytes {
59        self.data
60    }
61}
62
63impl Deref for Held {
64    type Target = Bytes;
65
66    fn deref(&self) -> &Bytes {
67        &self.data
68    }
69}
70
71/// Held data of one [`Budget`] in a single buffer, which counts against the
72/// budget with all of its capacity until it gets dropped. Unlike a queue of
73/// [`Held`], small data does not take an allocation of its own, which the
74/// budget does not count.
75#[derive(Debug, Default)]
76pub(super) struct HeldBuf {
77    data: BytesMut,
78    permit: Option<OwnedSemaphorePermit>,
79}
80
81impl HeldBuf {
82    /// What appending `len` bytes counts against the budget, the capacity
83    /// the buffer grows by, see [`Self::push`].
84    pub(super) fn cost(&self, len: usize) -> usize {
85        self.capacity_for(len) - self.data.capacity()
86    }
87
88    /// Append data, which has to be held by the same budget as the data
89    /// before, as its [`Self::cost`].
90    pub(super) fn push(&mut self, held: Held) {
91        let capacity = self.capacity_for(held.len());
92        if capacity > self.data.capacity() {
93            let mut data = BytesMut::with_capacity(capacity);
94            data.extend_from_slice(&self.data);
95            self.data = data;
96        }
97        self.data.extend_from_slice(&held.data);
98        match (&mut self.permit, held.permit) {
99            (Some(permit), Some(other)) => permit.merge(other),
100            (permit, other) => *permit = permit.take().or(other),
101        }
102    }
103
104    /// The capacity for `len` more bytes. It at least doubles once the
105    /// buffer is full, so that the data does not get copied for each push.
106    fn capacity_for(&self, len: usize) -> usize {
107        let (len, capacity) = (self.data.len() + len, self.data.capacity());
108        if len <= capacity {
109            capacity
110        } else {
111            len.max(2 * capacity)
112        }
113    }
114}
115
116impl Deref for HeldBuf {
117    type Target = [u8];
118
119    fn deref(&self) -> &[u8] {
120        &self.data
121    }
122}
123
124/// Data outside of any budget.
125#[cfg(test)]
126impl From<Bytes> for Held {
127    fn from(data: Bytes) -> Self {
128        Self { data, permit: None }
129    }
130}
131
132#[cfg(test)]
133mod tests {
134    use super::*;
135    use tokio::time::{self, Duration};
136
137    #[tokio::test(start_paused = true)]
138    async fn data_waits_for_room() {
139        let budget = Budget::new(10);
140        let first = budget.hold(Bytes::from_static(b"0123456")).await;
141        assert_eq!(budget.available(), 3);
142        assert!(
143            time::timeout(
144                Duration::from_secs(1),
145                budget.hold(Bytes::from_static(b"0123"))
146            )
147            .await
148            .is_err()
149        );
150        assert_eq!(first.into_bytes(), Bytes::from_static(b"0123456"));
151        let second = budget.hold(Bytes::from_static(b"0123")).await;
152        assert_eq!(&second[..], b"0123");
153        assert_eq!(budget.available(), 6);
154        drop(second);
155        assert_eq!(budget.available(), 10);
156    }
157
158    async fn push(budget: &Budget, buf: &mut HeldBuf, data: &'static [u8]) {
159        let cost = buf.cost(data.len());
160        buf.push(budget.hold_as(Bytes::from_static(data), cost).await);
161    }
162
163    #[tokio::test]
164    async fn held_data_gets_collected() {
165        let budget = Budget::new(10);
166        let mut buf = HeldBuf::default();
167        assert!(buf.is_empty());
168        for data in [&b"012"[..], b"", b"3456"] {
169            push(&budget, &mut buf, data).await;
170        }
171        assert_eq!(&buf[..], b"0123456");
172        assert_eq!(budget.available(), 10 - buf.data.capacity());
173        drop(buf);
174        assert_eq!(budget.available(), 10);
175    }
176
177    #[tokio::test]
178    async fn held_buffers_count_their_capacity() {
179        let budget = Budget::new(1 << 20);
180        let mut buf = HeldBuf::default();
181        for _ in 0..1000 {
182            push(&budget, &mut buf, b"0123456789").await;
183            assert_eq!(budget.available(), (1 << 20) - buf.data.capacity());
184        }
185        assert_eq!(buf.len(), 10_000);
186        // It grew by doubling, without copying the data for each push.
187        assert!(buf.data.capacity() < 20_000, "{}", buf.data.capacity());
188        drop(buf);
189        assert_eq!(budget.available(), 1 << 20);
190    }
191
192    #[tokio::test]
193    async fn large_data_takes_the_whole_budget() {
194        let budget = Budget::new(4);
195        let held = budget.hold(Bytes::from_static(b"0123456789")).await;
196        assert_eq!(budget.available(), 0);
197        drop(held);
198        assert_eq!(budget.available(), 4);
199    }
200}