1use std::pin::Pin;
4use std::task::{Context, Poll};
5
6use futures::stream::Stream;
7
8pub trait StreamExt: Stream + Sized {
10 fn until_overridden_by<Other: Stream<Item = Self::Item>>(
14 self,
15 other: Other,
16 ) -> UntilOverriddenBy<Self, Other>;
17
18 fn breaker(self) -> Breaker<Self>;
20
21 fn sync(self, capacity: std::num::NonZero<usize>) -> SyncStream<Result<Self::Item, Truncated>>
33 where
34 Self: 'static,
35 Self::Item: Send + 'static;
36}
37
38impl<S: Stream> StreamExt for S {
39 fn until_overridden_by<Other: Stream<Item = Self::Item>>(
40 self,
41 other: Other,
42 ) -> UntilOverriddenBy<Self, Other> {
43 UntilOverriddenBy {
44 a: self.breaker(),
45 b: other.breaker(),
46 }
47 }
48
49 fn breaker(self) -> Breaker<Self> {
50 Breaker { inner: Some(self) }
51 }
52
53 fn sync(self, capacity: std::num::NonZero<usize>) -> SyncStream<Result<Self::Item, Truncated>>
54 where
55 Self: 'static,
56 Self::Item: Send + 'static,
57 {
58 let (pump, stream) = SyncStream::new(self, capacity);
59 tokio::task::spawn_local(pump);
60 stream
61 }
62}
63
64pin_project_lite::pin_project! {
65pub struct Breaker<S : Stream>{
67#[pin]
68inner: Option<S>
69}
70}
71
72impl<S: Stream> Stream for Breaker<S> {
73 type Item = S::Item;
74
75 fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
76 let Some(s) = self.as_mut().project().inner.as_pin_mut() else {
77 return Poll::Ready(None);
78 };
79
80 let result = s.poll_next(cx);
81
82 if matches!(result, Poll::Ready(None)) {
83 self.trip();
84 }
85
86 result
87 }
88}
89
90impl<S: Stream> futures::stream::FusedStream for Breaker<S> {
91 fn is_terminated(&self) -> bool {
92 self.inner.is_none()
93 }
94}
95
96impl<S: Stream> Breaker<S> {
97 pub fn trip(self: Pin<&mut Self>) {
100 self.project().inner.set(None)
101 }
102}
103
104pin_project_lite::pin_project! {
105pub struct UntilOverriddenBy<A, B>
107where
108 A: Stream,
109 B: Stream<Item = A::Item>,
110{
111 #[pin]
112 a: Breaker<A>,
113 #[pin]
114 b: Breaker<B>,
115}
116}
117
118impl<A, B> Stream for UntilOverriddenBy<A, B>
119where
120 A: Stream,
121 B: Stream<Item = A::Item>,
122{
123 type Item = A::Item;
124
125 fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
126 let mut this = self.project();
127
128 let from_b = this.b.as_mut().poll_next(cx);
133 if let Poll::Ready(Some(_)) = from_b {
134 this.a.as_mut().trip();
135 return from_b;
136 }
137
138 let from_a = this.a.as_mut().poll_next(cx);
142 if let Poll::Ready(Some(_)) = from_a {
143 return from_a;
144 }
145
146 match (from_a, from_b) {
151 (Poll::Ready(None), Poll::Ready(None)) => Poll::Ready(None),
152 _ => Poll::Pending,
153 }
154 }
155}
156
157impl<A: Stream, B: Stream<Item = A::Item>> futures::stream::FusedStream
158 for UntilOverriddenBy<A, B>
159{
160 fn is_terminated(&self) -> bool {
161 self.a.is_terminated() && self.b.is_terminated()
162 }
163}
164
165#[derive(Debug, Clone, Copy, PartialEq, Eq)]
169pub struct Truncated;
170
171impl std::fmt::Display for Truncated {
172 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
173 f.write_str("the stream was truncated: its pump was dropped before the source finished")
174 }
175}
176
177impl std::error::Error for Truncated {}
178
179pub struct SyncStream<T> {
187 receiver: tokio::sync::mpsc::Receiver<T>,
188}
189
190impl<U> SyncStream<Result<U, Truncated>> {
191 #[must_use = "drive the returned pump (e.g. with spawn_local); dropping it undriven makes the stream yield only Err(Truncated)"]
200 pub fn new<S>(
201 source: S,
202 capacity: std::num::NonZero<usize>,
203 ) -> (impl std::future::Future<Output = ()>, Self)
204 where
205 S: Stream<Item = U> + 'static,
206 U: Send + 'static,
207 {
208 let (sender, receiver) = tokio::sync::mpsc::channel(capacity.get().saturating_add(1));
211
212 let mut truncate_on_drop = crate::misc::drop_ext::Bomb::new({
217 let permit = sender.clone().try_reserve_owned().ok();
218 move || {
219 if let Some(permit) = permit {
220 permit.send(Err(Truncated));
221 }
222 }
223 });
224
225 let pump = async move {
226 use futures::StreamExt as _;
227
228 let mut source = std::pin::pin!(source);
229 while let Some(item) = source.next().await {
230 if sender.send(Ok(item)).await.is_err() {
233 truncate_on_drop.defuse();
234 return;
235 }
236 }
237 truncate_on_drop.defuse();
239 };
240
241 (pump, Self { receiver })
242 }
243}
244
245impl<T> Stream for SyncStream<T> {
250 type Item = T;
251
252 fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<T>> {
253 self.get_mut().receiver.poll_recv(cx)
254 }
255}
256
257#[cfg(test)]
258mod tests {
259 use super::*;
260 use futures::StreamExt as _;
261
262 fn chan(
263 items: &[i32],
264 ) -> (
265 futures::channel::mpsc::UnboundedSender<i32>,
266 impl Stream<Item = i32>,
267 ) {
268 let (tx, rx) = futures::channel::mpsc::unbounded();
269 for &item in items {
270 tx.unbounded_send(item).unwrap();
271 }
272 (tx, rx)
273 }
274
275 #[test]
277 fn b_overrides_a() {
278 let (a_tx, a) = chan(&[1, 2, 3]); let (b_tx, b) = chan(&[]);
280 let mut s = a.until_overridden_by(b);
281
282 tokio_test::assert_ready_eq!(tokio_test::task::spawn(s.next()).poll(), Some(1));
283 tokio_test::assert_ready_eq!(tokio_test::task::spawn(s.next()).poll(), Some(2));
284
285 b_tx.unbounded_send(10).unwrap();
286 drop(b_tx);
287
288 tokio_test::assert_ready_eq!(tokio_test::task::spawn(s.next()).poll(), Some(10));
289 tokio_test::assert_ready_eq!(tokio_test::task::spawn(s.next()).poll(), None);
290
291 assert!(a_tx.unbounded_send(99).is_err()); }
293
294 #[test]
297 fn b_keeps_overriding_across_a_pending_gap() {
298 let (a_tx, a) = chan(&[1, 2]); let (b_tx, b) = chan(&[]);
300 let mut s = a.until_overridden_by(b);
301
302 tokio_test::assert_ready_eq!(tokio_test::task::spawn(s.next()).poll(), Some(1));
303
304 b_tx.unbounded_send(10).unwrap(); tokio_test::assert_ready_eq!(tokio_test::task::spawn(s.next()).poll(), Some(10));
306
307 tokio_test::assert_pending!(tokio_test::task::spawn(s.next()).poll());
309
310 b_tx.unbounded_send(20).unwrap();
311 tokio_test::assert_ready_eq!(tokio_test::task::spawn(s.next()).poll(), Some(20));
312
313 drop(b_tx);
314 tokio_test::assert_ready_eq!(tokio_test::task::spawn(s.next()).poll(), None);
315
316 assert!(a_tx.unbounded_send(99).is_err()); }
318
319 #[test]
321 fn b_ends_without_overriding() {
322 let (a_tx, a) = chan(&[]);
323 let (b_tx, b) = chan(&[]);
324 drop(b_tx); let mut s = a.until_overridden_by(b);
326
327 a_tx.unbounded_send(1).unwrap();
328 a_tx.unbounded_send(2).unwrap();
329 tokio_test::assert_ready_eq!(tokio_test::task::spawn(s.next()).poll(), Some(1));
330 tokio_test::assert_ready_eq!(tokio_test::task::spawn(s.next()).poll(), Some(2));
331 assert!(a_tx.unbounded_send(99).is_ok()); }
333
334 #[test]
336 fn both_pending() {
337 let (_a_tx, a) = chan(&[]);
338 let (_b_tx, b) = chan(&[]);
339 let mut s = a.until_overridden_by(b);
340 tokio_test::assert_pending!(tokio_test::task::spawn(s.next()).poll());
341 }
342
343 #[tokio::test]
345 async fn sync_relays_a_non_send_source() {
346 fn assert_send_sync<T: Send + Sync>(_: &T) {}
347
348 tokio::task::LocalSet::new()
349 .run_until(async {
350 let shared = std::rc::Rc::new(vec![10, 20, 30]);
352 let mut synced = futures::stream::iter(0..shared.len())
353 .map(move |i| shared[i])
354 .sync(std::num::NonZero::new(4).unwrap());
355 assert_send_sync(&synced); let mut got = Vec::new();
358 while let Some(item) = synced.next().await {
359 got.push(item.expect("the source was not truncated"));
360 }
361 assert_eq!(got, [10, 20, 30]);
362 })
363 .await;
364 }
365
366 #[tokio::test]
368 async fn an_undriven_dropped_pump_truncates() {
369 let source = futures::stream::iter([1, 2, 3]);
370 let (pump, mut synced) = SyncStream::new(source, std::num::NonZero::new(4).unwrap());
371 drop(pump);
372 assert_eq!(synced.next().await, Some(Err(Truncated)));
373 assert_eq!(synced.next().await, None);
374 }
375
376 #[tokio::test]
379 async fn aborting_mid_stream_truncates_after_the_buffered_items() {
380 let source = futures::stream::iter(0..1000); let (pump, mut synced) = SyncStream::new(source, std::num::NonZero::new(4).unwrap());
382 let pump = tokio::spawn(pump);
383
384 assert_eq!(synced.next().await, Some(Ok(0)));
385 assert_eq!(synced.next().await, Some(Ok(1)));
386 pump.abort();
387
388 let mut last = None;
389 let mut errors = 0;
390 while let Some(item) = synced.next().await {
391 if item.is_err() {
392 errors += 1;
393 }
394 last = Some(item);
395 }
396 assert_eq!(last, Some(Err(Truncated)));
397 assert_eq!(errors, 1);
398 }
399
400 #[tokio::test]
403 async fn pump_exits_when_the_consumer_is_dropped() {
404 let source = futures::stream::iter(0..1000);
405 let (pump, mut synced) = SyncStream::new(source, std::num::NonZero::new(4).unwrap());
406 let pump = tokio::spawn(pump);
407
408 assert_eq!(synced.next().await, Some(Ok(0)));
409 assert_eq!(synced.next().await, Some(Ok(1)));
410 drop(synced);
411
412 tokio::time::timeout(std::time::Duration::from_secs(1), pump)
413 .await
414 .expect("the pump should exit after the consumer is dropped")
415 .expect("the pump task should not panic");
416 }
417}