Skip to main content

lance_core/utils/
futures.rs

1// SPDX-License-Identifier: Apache-2.0
2// SPDX-FileCopyrightText: Copyright The Lance Authors
3
4use std::{
5    collections::VecDeque,
6    sync::{Arc, Mutex},
7    task::Waker,
8};
9
10use futures::{Stream, StreamExt, stream::BoxStream};
11use pin_project::{pin_project, pinned_drop};
12use tokio::sync::Semaphore;
13use tokio_util::sync::PollSemaphore;
14
15#[derive(Clone, Copy, Debug, PartialEq)]
16enum Side {
17    Left,
18    Right,
19}
20
21/// A potentially unbounded capacity
22#[derive(Clone, Copy, Debug, PartialEq)]
23pub enum Capacity {
24    Bounded(u32),
25    Unbounded,
26}
27
28struct InnerState<'a, T> {
29    inner: Option<BoxStream<'a, T>>,
30    buffer: VecDeque<T>,
31    polling: Option<Side>,
32    waker: Option<Waker>,
33    exhausted: bool,
34    left_buffered: u32,
35    right_buffered: u32,
36    left_dropped: bool,
37    right_dropped: bool,
38    available_buffer: Option<PollSemaphore>,
39}
40
41/// A stream that can be shared between two consumers.
42pub struct SharedStream<'a, T: Clone> {
43    state: Arc<Mutex<InnerState<'a, T>>>,
44    side: Side,
45}
46
47impl<'a, T: Clone> SharedStream<'a, T> {
48    pub fn new(inner: BoxStream<'a, T>, capacity: Capacity) -> (Self, Self) {
49        let available_buffer = match capacity {
50            Capacity::Unbounded => None,
51            Capacity::Bounded(capacity) => Some(PollSemaphore::new(Arc::new(Semaphore::new(
52                capacity as usize,
53            )))),
54        };
55        let state = InnerState {
56            inner: Some(inner),
57            buffer: VecDeque::new(),
58            polling: None,
59            waker: None,
60            exhausted: false,
61            left_buffered: 0,
62            right_buffered: 0,
63            left_dropped: false,
64            right_dropped: false,
65            available_buffer,
66        };
67
68        let state = Arc::new(Mutex::new(state));
69
70        let left = Self {
71            state: state.clone(),
72            side: Side::Left,
73        };
74        let right = Self {
75            state,
76            side: Side::Right,
77        };
78        (left, right)
79    }
80}
81
82impl<T: Clone> Drop for SharedStream<'_, T> {
83    fn drop(&mut self) {
84        let Ok(mut inner_state) = self.state.lock() else {
85            return;
86        };
87        match self.side {
88            Side::Left => inner_state.left_dropped = true,
89            Side::Right => inner_state.right_dropped = true,
90        }
91        // The buffer only ever holds items destined for one side (a side
92        // polls the inner stream only after draining its own share), so any
93        // count still pending for this side sits at the front. Pop those
94        // items and release their capacity permits, or a bounded survivor
95        // deadlocks once the budget is exhausted.
96        let pending_for_self = match self.side {
97            Side::Left => std::mem::take(&mut inner_state.left_buffered),
98            Side::Right => std::mem::take(&mut inner_state.right_buffered),
99        };
100        debug_assert!(
101            pending_for_self == 0 || inner_state.buffer.len() as u32 == pending_for_self,
102            "buffer holds items for both sides"
103        );
104        for _ in 0..pending_for_self {
105            inner_state.buffer.pop_front();
106            if let Some(available_buffer) = inner_state.available_buffer.as_mut() {
107                available_buffer.add_permits(1);
108            }
109        }
110        // If this side was mid-poll when its task went away, the inner
111        // stream holds a dead waker and the survivor would wait forever on
112        // `polling`. Let the survivor take over.
113        let was_polling = inner_state.polling == Some(self.side);
114        let to_wake = if was_polling {
115            inner_state.polling = None;
116            inner_state.waker.take()
117        } else {
118            None
119        };
120        drop(inner_state);
121        if let Some(waker) = to_wake {
122            waker.wake();
123        }
124    }
125}
126
127impl<T: Clone> Stream for SharedStream<'_, T> {
128    type Item = T;
129
130    fn poll_next(
131        self: std::pin::Pin<&mut Self>,
132        cx: &mut std::task::Context<'_>,
133    ) -> std::task::Poll<Option<Self::Item>> {
134        let mut inner_state = self.state.lock().unwrap();
135        let can_take_buffered = match self.side {
136            Side::Left => inner_state.left_buffered > 0,
137            Side::Right => inner_state.right_buffered > 0,
138        };
139        if can_take_buffered {
140            // Easy case, there is an item in the buffer.  Grab it, decrement the count, and return it.
141            let item = inner_state.buffer.pop_front();
142            match self.side {
143                Side::Left => {
144                    inner_state.left_buffered -= 1;
145                }
146                Side::Right => {
147                    inner_state.right_buffered -= 1;
148                }
149            }
150            if let Some(available_buffer) = inner_state.available_buffer.as_mut() {
151                available_buffer.add_permits(1);
152            }
153            std::task::Poll::Ready(item)
154        } else {
155            if inner_state.exhausted {
156                return std::task::Poll::Ready(None);
157            }
158            // No buffered items, if we have room in the buffer, then try and poll for one
159            let permit = if let Some(available_buffer) = inner_state.available_buffer.as_mut() {
160                match available_buffer.poll_acquire(cx) {
161                    // Can return None if the semaphore is closed but we never close the semaphore
162                    // so its safe to unwrap here
163                    std::task::Poll::Ready(permit) => Some(permit.unwrap()),
164                    std::task::Poll::Pending => {
165                        return std::task::Poll::Pending;
166                    }
167                }
168            } else {
169                None
170            };
171            if let Some(polling_side) = inner_state.polling.as_ref()
172                && *polling_side != self.side
173            {
174                // Another task is already polling the inner stream, so we don't need to do anything
175
176                // Per rust docs:
177                //   Note that on multiple calls to poll, only the Waker from the Context
178                //   passed to the most recent call should be scheduled to receive a wakeup.
179                //
180                // So it is safe to replace a potentially stale waker here.
181                inner_state.waker = Some(cx.waker().clone());
182                return std::task::Poll::Pending;
183            }
184            inner_state.polling = Some(self.side);
185            // Release the mutex here as polling the inner stream is potentially expensive
186            let mut to_poll = inner_state
187                .inner
188                .take()
189                .expect("Other half of shared stream panic'd while polling inner stream");
190            drop(inner_state);
191            let res = to_poll.poll_next_unpin(cx);
192            let mut inner_state = self.state.lock().unwrap();
193
194            let mut should_wake = true;
195            match &res {
196                std::task::Poll::Ready(None) => {
197                    inner_state.exhausted = true;
198                    inner_state.polling = None;
199                }
200                std::task::Poll::Ready(Some(item)) => {
201                    inner_state.polling = None;
202                    // Let the other side know an item is available. A dropped
203                    // side never will be: skip the clone so neither a buffer
204                    // entry nor its capacity permit is stranded.
205                    let other_alive = match self.side {
206                        Side::Left => !inner_state.right_dropped,
207                        Side::Right => !inner_state.left_dropped,
208                    };
209                    if other_alive {
210                        // We got an item, forget the permit to mark that we can take one fewer items
211                        if let Some(permit) = permit {
212                            permit.forget();
213                        }
214                        match self.side {
215                            Side::Left => {
216                                inner_state.right_buffered += 1;
217                            }
218                            Side::Right => {
219                                inner_state.left_buffered += 1;
220                            }
221                        };
222                        inner_state.buffer.push_back(item.clone());
223                    }
224                }
225                std::task::Poll::Pending => {
226                    should_wake = false;
227                }
228            };
229
230            inner_state.inner = Some(to_poll);
231
232            // If the other side was waiting for us to poll, wake them up, but only after we release the mutex
233            let to_wake = if should_wake {
234                inner_state.waker.take()
235            } else {
236                // If the inner stream is pending then the inner stream will wake us up and we will wake the
237                // other side up then.
238                None
239            };
240            drop(inner_state);
241            if let Some(waker) = to_wake {
242                waker.wake();
243            }
244            res
245        }
246    }
247}
248
249pub trait SharedStreamExt<'a>: Stream + Send
250where
251    Self::Item: Clone,
252{
253    /// Split a stream into two shared streams
254    ///
255    /// Each shared stream will return the full set of items from the underlying stream.
256    /// This works by buffering the items from the underlying stream and then replaying
257    /// them to the other side.
258    ///
259    /// The capacity parameter controls how many items can be buffered at once.  Be careful
260    /// with the capacity parameter as it can lead to deadlock if the two streams are not
261    /// polled evenly.
262    ///
263    /// If the capacity is unbounded then the stream could potentially buffer the entire
264    /// input stream in memory.
265    fn share(
266        self,
267        capacity: Capacity,
268    ) -> (SharedStream<'a, Self::Item>, SharedStream<'a, Self::Item>);
269}
270
271impl<'a, T: Clone> SharedStreamExt<'a> for BoxStream<'a, T> {
272    fn share(self, capacity: Capacity) -> (SharedStream<'a, T>, SharedStream<'a, T>) {
273        SharedStream::new(self, capacity)
274    }
275}
276
277#[pin_project]
278pub struct FinallyStream<S: Stream, F: FnOnce()> {
279    #[pin]
280    stream: S,
281    f: Option<F>,
282}
283
284impl<S: Stream, F: FnOnce()> FinallyStream<S, F> {
285    pub fn new(stream: S, f: F) -> Self {
286        Self { stream, f: Some(f) }
287    }
288}
289
290impl<S: Stream, F: FnOnce()> Stream for FinallyStream<S, F> {
291    type Item = S::Item;
292
293    fn poll_next(
294        self: std::pin::Pin<&mut Self>,
295        cx: &mut std::task::Context<'_>,
296    ) -> std::task::Poll<Option<Self::Item>> {
297        let this = self.project();
298        let res = this.stream.poll_next(cx);
299        if matches!(res, std::task::Poll::Ready(None)) {
300            // It's possible that None is polled multiple times, but we only call the function once
301            if let Some(f) = this.f.take() {
302                f();
303            }
304        }
305        res
306    }
307}
308
309pub trait FinallyStreamExt<S: Stream>: Stream + Sized {
310    fn finally<F: FnOnce()>(self, f: F) -> FinallyStream<Self, F> {
311        FinallyStream {
312            stream: self,
313            f: Some(f),
314        }
315    }
316}
317
318impl<S: Stream> FinallyStreamExt<S> for S {
319    fn finally<F: FnOnce()>(self, f: F) -> FinallyStream<Self, F> {
320        FinallyStream::new(self, f)
321    }
322}
323
324/// A stream wrapper that calls a function when dropped.
325///
326/// Unlike [`FinallyStream`], which fires when the inner stream yields `None`,
327/// this fires when the wrapper is dropped — even if the stream was not fully
328/// consumed.
329#[pin_project(PinnedDrop)]
330pub struct OnDropStream<S: Stream, F: FnOnce()> {
331    #[pin]
332    stream: S,
333    f: Option<F>,
334}
335
336impl<S: Stream, F: FnOnce()> OnDropStream<S, F> {
337    pub fn new(stream: S, f: F) -> Self {
338        Self { stream, f: Some(f) }
339    }
340}
341
342impl<S: Stream, F: FnOnce()> Stream for OnDropStream<S, F> {
343    type Item = S::Item;
344
345    fn poll_next(
346        self: std::pin::Pin<&mut Self>,
347        cx: &mut std::task::Context<'_>,
348    ) -> std::task::Poll<Option<Self::Item>> {
349        self.project().stream.poll_next(cx)
350    }
351}
352
353#[pinned_drop]
354impl<S: Stream, F: FnOnce()> PinnedDrop for OnDropStream<S, F> {
355    fn drop(self: std::pin::Pin<&mut Self>) {
356        let this = self.project();
357        if let Some(f) = this.f.take() {
358            f();
359        }
360    }
361}
362
363pub trait StreamOnDropExt: Stream + Sized {
364    /// Wrap this stream so that `f` is called when the stream is dropped.
365    fn on_drop<F: FnOnce()>(self, f: F) -> OnDropStream<Self, F> {
366        OnDropStream::new(self, f)
367    }
368}
369
370impl<S: Stream> StreamOnDropExt for S {}
371
372#[cfg(test)]
373mod tests {
374
375    use std::sync::Arc;
376    use std::sync::atomic::{AtomicBool, Ordering};
377
378    use futures::{FutureExt, StreamExt};
379    use tokio_stream::wrappers::ReceiverStream;
380
381    use crate::utils::futures::{Capacity, SharedStreamExt, StreamOnDropExt};
382
383    fn is_pending(fut: &mut (impl std::future::Future + Unpin)) -> bool {
384        let noop_waker = futures::task::noop_waker();
385        let mut context = std::task::Context::from_waker(&noop_waker);
386        fut.poll_unpin(&mut context).is_pending()
387    }
388
389    #[tokio::test]
390    async fn test_shared_stream() {
391        let (tx, rx) = tokio::sync::mpsc::channel::<u32>(10);
392        let inner_stream = ReceiverStream::new(rx);
393
394        // Feed in a few items
395        for i in 0..3 {
396            tx.send(i).await.unwrap();
397        }
398
399        let (mut left, mut right) = inner_stream.boxed().share(Capacity::Bounded(2));
400
401        // We should be able to immediately poll 2 items
402        assert_eq!(left.next().await.unwrap(), 0);
403        assert_eq!(left.next().await.unwrap(), 1);
404
405        // Polling again should block because the right side has fallen behind
406        let mut left_fut = left.next();
407
408        assert!(is_pending(&mut left_fut));
409
410        // Polling the right side should yield the first cached item and unblock the left
411        assert_eq!(right.next().await.unwrap(), 0);
412        assert_eq!(left_fut.await.unwrap(), 2);
413
414        // Drain the rest of the stream from the right
415        assert_eq!(right.next().await.unwrap(), 1);
416        assert_eq!(right.next().await.unwrap(), 2);
417
418        // The channel isn't closed yet so we should get pending on both sides
419        let mut right_fut = right.next();
420        let mut left_fut = left.next();
421        assert!(is_pending(&mut right_fut));
422        assert!(is_pending(&mut left_fut));
423
424        // Send one more item
425        tx.send(3).await.unwrap();
426
427        // Should be received by both
428        assert_eq!(right_fut.await.unwrap(), 3);
429        assert_eq!(left_fut.await.unwrap(), 3);
430
431        drop(tx);
432
433        // Now we should be able to poll the end from either side
434        assert_eq!(left.next().await, None);
435        assert_eq!(right.next().await, None);
436
437        // We should be self-fused
438        assert_eq!(left.next().await, None);
439        assert_eq!(right.next().await, None);
440    }
441
442    #[test]
443    fn test_shared_stream_replaces_waiting_waker() {
444        let inner_stream = futures::stream::pending::<u32>();
445        let (mut left, mut right) = inner_stream.boxed().share(Capacity::Unbounded);
446
447        let mut left_fut = left.next();
448        assert!(is_pending(&mut left_fut));
449
450        let mut right_fut = right.next();
451        assert!(is_pending(&mut right_fut));
452        assert!(is_pending(&mut right_fut));
453    }
454
455    #[tokio::test]
456    async fn test_unbounded_shared_stream() {
457        let (tx, rx) = tokio::sync::mpsc::channel::<u32>(10);
458        let inner_stream = ReceiverStream::new(rx);
459
460        // Feed in a few items
461        for i in 0..10 {
462            tx.send(i).await.unwrap();
463        }
464        drop(tx);
465
466        let (mut left, mut right) = inner_stream.boxed().share(Capacity::Unbounded);
467
468        // We should be able to completely drain one side
469        for i in 0..10 {
470            assert_eq!(left.next().await.unwrap(), i);
471        }
472        assert_eq!(left.next().await, None);
473
474        // And still drain the other side from the buffer
475        for i in 0..10 {
476            assert_eq!(right.next().await.unwrap(), i);
477        }
478        assert_eq!(right.next().await, None);
479    }
480
481    #[tokio::test(flavor = "multi_thread")]
482    async fn stress_shared_stream() {
483        for _ in 0..100 {
484            let (tx, rx) = tokio::sync::mpsc::channel::<u32>(10);
485            let inner_stream = ReceiverStream::new(rx);
486            let (mut left, mut right) = inner_stream.boxed().share(Capacity::Bounded(2));
487
488            let left_handle = tokio::spawn(async move {
489                let mut counter = 0;
490                while let Some(item) = left.next().await {
491                    assert_eq!(item, counter);
492                    counter += 1;
493                }
494            });
495
496            let right_handle = tokio::spawn(async move {
497                let mut counter = 0;
498                while let Some(item) = right.next().await {
499                    assert_eq!(item, counter);
500                    counter += 1;
501                }
502            });
503
504            for i in 0..1000 {
505                tx.send(i).await.unwrap();
506            }
507            drop(tx);
508            left_handle.await.unwrap();
509            right_handle.await.unwrap();
510        }
511    }
512
513    #[tokio::test]
514    async fn test_on_drop_fires_on_early_drop() {
515        let called = Arc::new(AtomicBool::new(false));
516        let called_clone = called.clone();
517
518        let stream = futures::stream::iter(vec![1, 2, 3]);
519        let mut stream = stream.on_drop(move || {
520            called_clone.store(true, Ordering::SeqCst);
521        });
522
523        // Consume only one item, then drop
524        assert_eq!(stream.next().await, Some(1));
525        assert!(!called.load(Ordering::SeqCst));
526        drop(stream);
527        assert!(called.load(Ordering::SeqCst));
528    }
529
530    #[tokio::test]
531    async fn test_on_drop_fires_after_exhaustion() {
532        let called = Arc::new(AtomicBool::new(false));
533        let called_clone = called.clone();
534
535        let stream = futures::stream::iter(vec![1]);
536        let mut stream = stream.on_drop(move || {
537            called_clone.store(true, Ordering::SeqCst);
538        });
539
540        assert_eq!(stream.next().await, Some(1));
541        assert_eq!(stream.next().await, None);
542        assert!(!called.load(Ordering::SeqCst));
543        drop(stream);
544        assert!(called.load(Ordering::SeqCst));
545    }
546
547    #[tokio::test]
548    async fn test_on_drop_fires_without_polling() {
549        let called = Arc::new(AtomicBool::new(false));
550        let called_clone = called.clone();
551
552        let stream = futures::stream::iter(vec![1, 2, 3]);
553        let stream = stream.on_drop(move || {
554            called_clone.store(true, Ordering::SeqCst);
555        });
556
557        assert!(!called.load(Ordering::SeqCst));
558        drop(stream);
559        assert!(called.load(Ordering::SeqCst));
560    }
561
562    /// If one side is dropped mid-stream, the survivor must still be able to
563    /// make progress. Two mechanisms used to wedge a bounded stream: items
564    /// buffered for the dropped side never released their capacity permits,
565    /// and every later fetch kept spending a permit on a clone nobody would
566    /// take, so the survivor stalled again one capacity later. The third
567    /// mechanism, a dropped side left recorded as the current poller, is
568    /// covered by the two tests below.
569    #[tokio::test]
570    async fn test_shared_stream_survivor_progresses_after_drop() {
571        let (tx, rx) = tokio::sync::mpsc::channel::<u32>(10);
572        for i in 0..6 {
573            tx.send(i).await.unwrap();
574        }
575        let (left, mut right) = ReceiverStream::new(rx).boxed().share(Capacity::Bounded(2));
576
577        // Right races ahead by the full capacity; the two buffered items are
578        // destined for left and hold the only two permits.
579        assert_eq!(right.next().await.unwrap(), 0);
580        assert_eq!(right.next().await.unwrap(), 1);
581        let mut right_fut = right.next();
582        assert!(is_pending(&mut right_fut));
583
584        drop(left);
585
586        // The stranded permits must be released so right can keep reading,
587        // including past the original capacity budget: later fetches must
588        // stop buffering clones for the dropped side at all.
589        let item = tokio::time::timeout(std::time::Duration::from_secs(5), right_fut).await;
590        assert_eq!(item.expect("survivor wedged by dropped side").unwrap(), 2);
591        for expected in 3..6 {
592            let item = tokio::time::timeout(std::time::Duration::from_secs(5), right.next()).await;
593            assert_eq!(
594                item.expect("survivor wedged after capacity re-spent")
595                    .unwrap(),
596                expected
597            );
598        }
599    }
600
601    #[tokio::test]
602    async fn test_shared_stream_dropped_poller_releases_inner_stream() {
603        let (tx, rx) = tokio::sync::mpsc::channel::<u32>(10);
604        let (mut left, mut right) = ReceiverStream::new(rx).boxed().share(Capacity::Unbounded);
605
606        // Left polls while the channel is empty, so it stays recorded as the
607        // poller and the inner stream keeps the waker it was polled with.
608        let mut left_fut = left.next();
609        assert!(is_pending(&mut left_fut));
610        drop(left_fut);
611        drop(left);
612
613        // An item arriving for the survivor must not be lost to the dead
614        // side's stale poll.
615        tx.send(42).await.unwrap();
616        let item = tokio::time::timeout(std::time::Duration::from_secs(5), right.next()).await;
617        assert_eq!(
618            item.expect("survivor wedged by dropped poller").unwrap(),
619            42
620        );
621    }
622    struct FlagWaker(AtomicBool);
623
624    impl futures::task::ArcWake for FlagWaker {
625        fn wake_by_ref(arc_self: &Arc<Self>) {
626            arc_self.0.store(true, Ordering::SeqCst);
627        }
628    }
629
630    /// A survivor already parked behind `polling` has to be woken when the
631    /// recorded poller is dropped: the inner stream only holds the waker the
632    /// dropped side polled with, so nothing else will re-poll the survivor.
633    #[tokio::test]
634    async fn test_shared_stream_dropped_poller_wakes_parked_survivor() {
635        let (tx, rx) = tokio::sync::mpsc::channel::<u32>(10);
636        let (mut left, mut right) = ReceiverStream::new(rx).boxed().share(Capacity::Unbounded);
637
638        // Left becomes the recorded poller and parks on the empty channel.
639        let mut left_fut = left.next();
640        assert!(is_pending(&mut left_fut));
641        drop(left_fut);
642
643        // Right parks behind `polling == Some(Left)`, leaving its waker behind.
644        let flag = Arc::new(FlagWaker(AtomicBool::new(false)));
645        let flag_waker = futures::task::waker(flag.clone());
646        let mut cx = std::task::Context::from_waker(&flag_waker);
647        let mut right_fut = right.next();
648        assert!(right_fut.poll_unpin(&mut cx).is_pending());
649        assert!(!flag.0.load(Ordering::SeqCst));
650
651        drop(left);
652        assert!(
653            flag.0.load(Ordering::SeqCst),
654            "dropping the poller must wake the parked survivor"
655        );
656
657        tx.send(42).await.unwrap();
658        let item = tokio::time::timeout(std::time::Duration::from_secs(5), right_fut).await;
659        assert_eq!(
660            item.expect("survivor wedged by dropped poller").unwrap(),
661            42
662        );
663    }
664
665    /// With an unbounded capacity nothing blocks, so the only observable effect
666    /// of a drop is memory: the dropped side's buffered items must be freed and
667    /// no further clone queued for it.
668    #[tokio::test]
669    async fn test_shared_stream_unbounded_stops_buffering_for_dropped_side() {
670        let (tx, rx) = tokio::sync::mpsc::channel::<u32>(10);
671        for i in 0..4 {
672            tx.send(i).await.unwrap();
673        }
674        let (left, mut right) = ReceiverStream::new(rx).boxed().share(Capacity::Unbounded);
675
676        // Right reads ahead, so every item is also cloned into the buffer for left.
677        assert_eq!(right.next().await.unwrap(), 0);
678        assert_eq!(right.next().await.unwrap(), 1);
679        assert_eq!(right.state.lock().unwrap().buffer.len(), 2);
680
681        drop(left);
682        assert!(
683            right.state.lock().unwrap().buffer.is_empty(),
684            "the dropped side's buffered items must be released"
685        );
686
687        for expected in 2..4 {
688            let item = tokio::time::timeout(std::time::Duration::from_secs(5), right.next()).await;
689            assert_eq!(
690                item.expect("survivor wedged after peer drop").unwrap(),
691                expected
692            );
693        }
694        let state = right.state.lock().unwrap();
695        assert!(
696            state.buffer.is_empty(),
697            "no clone may be queued for a dropped side"
698        );
699        assert_eq!(state.left_buffered, 0);
700    }
701}