1use std::future::Future;
5use std::pin::Pin;
6use std::sync::Arc;
7use std::task::Context;
8use std::task::Poll;
9use std::task::ready;
10
11use futures::Stream;
12use futures::stream::FuturesUnordered;
13use pin_project_lite::pin_project;
14use tokio::sync::OwnedSemaphorePermit;
15use tokio::sync::Semaphore;
16use tokio::sync::TryAcquireError;
17use vortex_error::VortexExpect;
18
19pin_project! {
20 struct SizedFut<Fut> {
25 #[pin]
26 inner: Fut,
27 _permits: OwnedSemaphorePermit,
29 }
30}
31
32impl<Fut: Future> Future for SizedFut<Fut> {
33 type Output = Fut::Output;
34
35 fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
36 let this = self.project();
37 let result = ready!(this.inner.poll(cx));
38 Poll::Ready(result)
39 }
40}
41
42pin_project! {
43 pub struct SizeLimitedStream<Fut> {
53 #[pin]
54 inflight: FuturesUnordered<SizedFut<Fut>>,
55 bytes_available: Arc<Semaphore>,
56 }
57}
58
59impl<Fut> SizeLimitedStream<Fut> {
60 pub fn new(max_bytes: usize) -> Self {
61 Self {
62 inflight: FuturesUnordered::new(),
63 bytes_available: Arc::new(Semaphore::new(max_bytes)),
64 }
65 }
66
67 pub fn bytes_available(&self) -> usize {
68 self.bytes_available.available_permits()
69 }
70}
71
72impl<Fut> SizeLimitedStream<Fut>
73where
74 Fut: Future,
75{
76 pub async fn push(&self, fut: Fut, bytes: usize) {
81 let permits = Arc::clone(&self.bytes_available)
85 .acquire_many_owned(bytes.try_into().vortex_expect("bytes must fit in u32"))
86 .await
87 .unwrap_or_else(|_| unreachable!("pushing to closed semaphore"));
88
89 let sized_fut = SizedFut {
90 inner: fut,
91 _permits: permits,
92 };
93
94 self.inflight.push(sized_fut);
96 }
97
98 pub fn try_push(&self, fut: Fut, bytes: usize) -> Result<(), Fut> {
103 match Arc::clone(&self.bytes_available)
104 .try_acquire_many_owned(bytes.try_into().vortex_expect("bytes must fit in u32"))
105 {
106 Ok(permits) => {
107 let sized_fut = SizedFut {
108 inner: fut,
109 _permits: permits,
110 };
111
112 self.inflight.push(sized_fut);
113 Ok(())
114 }
115 Err(acquire_err) => match acquire_err {
116 TryAcquireError::Closed => {
117 unreachable!("try_pushing to closed semaphore");
118 }
119
120 TryAcquireError::NoPermits => Err(fut),
123 },
124 }
125 }
126}
127
128impl<Fut> Stream for SizeLimitedStream<Fut>
129where
130 Fut: Future,
131{
132 type Item = Fut::Output;
133
134 fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
135 let this = self.project();
136 match ready!(this.inflight.poll_next(cx)) {
137 None => Poll::Ready(None),
138 Some(result) => {
139 Poll::Ready(Some(result))
142 }
143 }
144 }
145}
146
147#[cfg(test)]
148mod tests {
149 use std::future;
150 use std::io;
151 use std::sync::Arc;
152
153 use bytes::Bytes;
154 use futures::FutureExt;
155 use futures::StreamExt;
156 use futures::future::BoxFuture;
157
158 use crate::limit::SizeLimitedStream;
159
160 async fn make_future(len: usize) -> Bytes {
161 "a".as_bytes().iter().copied().cycle().take(len).collect()
162 }
163
164 #[tokio::test]
165 async fn test_size_limit() {
166 let mut size_limited = SizeLimitedStream::new(10);
167 size_limited.push(make_future(5), 5).await;
168 size_limited.push(make_future(5), 5).await;
169
170 assert!(size_limited.try_push(make_future(1), 1).is_err());
172
173 assert!(size_limited.next().await.is_some());
175 assert!(size_limited.try_push(make_future(1), 1).is_ok());
176 }
177
178 #[tokio::test]
179 async fn test_does_not_leak_permits() {
180 let bad_fut: BoxFuture<'static, io::Result<Bytes>> =
181 future::ready(Err(io::Error::other("badness"))).boxed();
182
183 let good_fut: BoxFuture<'static, io::Result<Bytes>> =
184 future::ready(Ok(Bytes::from("aaaaa"))).boxed();
185
186 let mut size_limited = SizeLimitedStream::new(10);
187 size_limited.push(bad_fut, 10).await;
188
189 let good_fut = size_limited
191 .try_push(good_fut, 5)
192 .expect_err("try_push should fail");
193
194 let next = size_limited.next().await.unwrap();
197 assert!(next.is_err());
198
199 assert_eq!(size_limited.bytes_available(), 10);
200 assert!(size_limited.try_push(good_fut, 5).is_ok());
201 }
202
203 #[tokio::test]
204 async fn test_size_limited_stream_zero_capacity() {
205 let stream = SizeLimitedStream::new(0);
206
207 let result = stream.try_push(async { vec![1u8] }, 1);
209 assert!(result.is_err());
210 }
211
212 #[tokio::test]
213 async fn test_size_limited_stream_dropped_future_releases_permits() {
214 use futures::future::BoxFuture;
215
216 let stream = SizeLimitedStream::<BoxFuture<'static, Vec<u8>>>::new(10);
217
218 stream
220 .push(
221 Box::pin(async {
222 futures::future::pending::<Vec<u8>>().await
224 }),
225 5,
226 )
227 .await;
228
229 stream.push(Box::pin(async { vec![1u8; 3] }), 3).await;
231
232 assert_eq!(stream.bytes_available(), 2);
234
235 let permits = Arc::clone(&stream.bytes_available);
236 drop(stream);
237 assert_eq!(permits.available_permits(), 10);
238 }
239
240 #[tokio::test]
241 async fn test_size_limited_stream_exact_capacity() {
242 use futures::future::BoxFuture;
243
244 let mut stream = SizeLimitedStream::<BoxFuture<'static, Vec<u8>>>::new(10);
245
246 stream.push(Box::pin(async { vec![0u8; 10] }), 10).await;
248
249 let result = stream.try_push(Box::pin(async { vec![1u8] }), 1);
251 assert!(result.is_err());
252
253 stream
255 .next()
256 .await
257 .expect("The 10 byte vector ought to be in there!");
258 assert_eq!(stream.bytes_available(), 10);
259
260 let result = stream.try_push(Box::pin(async { vec![1u8; 5] }), 5);
261 assert!(result.is_ok());
262 }
263
264 #[tokio::test]
265 async fn test_size_limited_stream_multiple_small_pushes() {
266 let mut stream = SizeLimitedStream::new(100);
267
268 for i in 0..10 {
270 #[expect(clippy::cast_possible_truncation)]
271 stream.push(async move { vec![i as u8; 5] }, 5).await;
272 }
273
274 assert_eq!(stream.bytes_available(), 50);
276
277 let mut count = 0;
279 while stream.next().await.is_some() {
280 count += 1;
281 if count == 10 {
282 break;
283 }
284 }
285
286 assert_eq!(count, 10);
287 assert_eq!(stream.bytes_available(), 100);
288 }
289
290 #[cfg(target_pointer_width = "64")]
291 #[test]
292 #[should_panic(expected = "bytes must fit in u32")]
293 fn test_size_overflow_protection() {
294 let stream = SizeLimitedStream::new(100);
295 let _result = stream.try_push(async {}, (u32::MAX as usize) + 1);
296 }
297}