1use std::collections::VecDeque;
49use std::fmt;
50use std::future::Future;
51use std::pin::Pin;
52use std::sync::{Arc, Condvar, Mutex, MutexGuard};
53use std::task::{Context, Poll, Waker};
54
55struct State<T> {
58 buffer: VecDeque<T>,
59 waker: Option<Waker>,
60 closed: bool,
63 consumer_gone: bool,
66 sender_count: usize,
67 #[cfg(test)]
68 blocked_producers: usize,
69}
70
71struct Shared<T> {
72 state: Mutex<State<T>>,
73 capacity_available: Condvar,
74 capacity: usize,
75}
76
77impl<T> Shared<T> {
78 fn lock_state(&self) -> MutexGuard<'_, State<T>> {
79 self.state
80 .lock()
81 .unwrap_or_else(|_| panic!("BoundedAsyncStream state mutex poisoned"))
82 }
83
84 fn lock_state_for_drop(&self) -> MutexGuard<'_, State<T>> {
85 self.state
86 .lock()
87 .unwrap_or_else(std::sync::PoisonError::into_inner)
88 }
89
90 #[cfg(test)]
91 fn wait_for_blocked_producers(&self, expected: usize) {
92 let (state, timeout) = self
93 .capacity_available
94 .wait_timeout_while(
95 self.lock_state(),
96 std::time::Duration::from_secs(5),
97 |state| state.blocked_producers < expected,
98 )
99 .unwrap_or_else(|_| panic!("BoundedAsyncStream state mutex poisoned"));
100 assert!(
101 !timeout.timed_out(),
102 "timed out waiting for {expected} blocked producer(s)"
103 );
104 drop(state);
105 }
106}
107
108pub struct BoundedAsyncStream<T> {
115 shared: Arc<Shared<T>>,
116}
117
118pub struct AsyncStreamSender<T> {
124 shared: Arc<Shared<T>>,
125}
126
127impl<T> Clone for AsyncStreamSender<T> {
128 fn clone(&self) -> Self {
129 let mut state = self.shared.lock_state();
130 state.sender_count = state
131 .sender_count
132 .checked_add(1)
133 .expect("AsyncStreamSender count overflow");
134 drop(state);
135
136 Self {
137 shared: Arc::clone(&self.shared),
138 }
139 }
140}
141
142impl<T> fmt::Debug for BoundedAsyncStream<T> {
143 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
144 f.debug_struct("BoundedAsyncStream")
145 .field("buffered", &self.buffered_count())
146 .field("capacity", &self.capacity())
147 .field("is_closed", &self.is_closed())
148 .finish_non_exhaustive()
149 }
150}
151
152impl<T> fmt::Debug for AsyncStreamSender<T> {
153 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
154 f.debug_struct("AsyncStreamSender").finish_non_exhaustive()
155 }
156}
157
158impl<T> BoundedAsyncStream<T> {
159 #[must_use]
170 pub fn new(capacity: usize) -> (Self, AsyncStreamSender<T>) {
171 assert!(capacity > 0, "BoundedAsyncStream capacity must be > 0");
172
173 let shared = Arc::new(Shared {
174 capacity,
175 capacity_available: Condvar::new(),
176 state: Mutex::new(State {
177 buffer: VecDeque::with_capacity(capacity),
178 waker: None,
179 closed: false,
180 consumer_gone: false,
181 sender_count: 1,
182 #[cfg(test)]
183 blocked_producers: 0,
184 }),
185 });
186
187 let stream = Self {
188 shared: Arc::clone(&shared),
189 };
190 let sender = AsyncStreamSender { shared };
191 (stream, sender)
192 }
193
194 #[must_use]
197 pub const fn next(&self) -> NextItem<'_, T> {
198 NextItem { stream: self }
199 }
200
201 #[must_use]
208 pub fn try_next(&self) -> Option<T> {
209 let item = {
210 let mut state = self.shared.lock_state();
211 state.buffer.pop_front()
212 };
213 if item.is_some() {
214 self.shared.capacity_available.notify_one();
215 }
216 item
217 }
218
219 #[must_use]
226 pub fn is_closed(&self) -> bool {
227 self.shared.lock_state().closed
228 }
229
230 #[must_use]
236 pub fn buffered_count(&self) -> usize {
237 self.shared.lock_state().buffer.len()
238 }
239
240 #[must_use]
242 pub fn capacity(&self) -> usize {
243 self.shared.capacity
244 }
245
246 pub fn clear_buffer(&self) {
252 let mut cleared = VecDeque::with_capacity(self.shared.capacity);
253 {
254 let mut state = self.shared.lock_state();
255 std::mem::swap(&mut state.buffer, &mut cleared);
256 }
257 self.shared.capacity_available.notify_all();
258 drop(cleared);
259 }
260
261 fn poll_next_item(&self, cx: &Context<'_>) -> Poll<Option<T>> {
262 let mut next_waker = Some(cx.waker().clone());
263 let (result, replaced_waker, made_room) = {
264 let mut state = self.shared.lock_state();
265
266 let outcome = match state.buffer.pop_front() {
267 Some(item) => (Poll::Ready(Some(item)), None, true),
268 None if state.closed => (Poll::Ready(None), None, false),
269 None => {
270 let replaced_waker = match state.waker.as_ref() {
271 Some(existing) if existing.will_wake(cx.waker()) => None,
272 _ => state
273 .waker
274 .replace(next_waker.take().expect("next waker present")),
275 };
276 (Poll::Pending, replaced_waker, false)
277 }
278 };
279 drop(state);
280 outcome
281 };
282
283 drop(next_waker);
284 drop(replaced_waker);
285 if made_room {
286 self.shared.capacity_available.notify_one();
287 }
288 result
289 }
290}
291
292impl<T> Drop for BoundedAsyncStream<T> {
293 fn drop(&mut self) {
294 let stale_waker = {
295 let mut state = self.shared.lock_state_for_drop();
296 state.consumer_gone = true;
297 state.waker.take()
298 };
299 self.shared.capacity_available.notify_all();
300 drop(stale_waker);
301 }
302}
303
304impl<T> AsyncStreamSender<T> {
305 pub fn push(&self, item: T) {
312 let (overwritten, waker) = {
313 let mut state = self.shared.lock_state();
314 let overwritten = if state.buffer.len() >= self.shared.capacity {
315 state.buffer.pop_front()
316 } else {
317 None
318 };
319 state.buffer.push_back(item);
320 (overwritten, state.waker.take())
321 };
322
323 if let Some(waker) = waker {
324 waker.wake();
325 }
326 drop(overwritten);
327 }
328
329 pub fn push_or_block(&self, item: T) -> Result<(), T> {
343 let mut state = self.shared.lock_state();
344 if !state.consumer_gone && state.buffer.len() >= self.shared.capacity {
345 #[cfg(test)]
346 {
347 state.blocked_producers += 1;
348 self.shared.capacity_available.notify_all();
349 }
350
351 state = self
352 .shared
353 .capacity_available
354 .wait_while(state, |state| {
355 !state.consumer_gone && state.buffer.len() >= self.shared.capacity
356 })
357 .unwrap_or_else(|_| panic!("BoundedAsyncStream state mutex poisoned"));
358
359 #[cfg(test)]
360 {
361 state.blocked_producers -= 1;
362 self.shared.capacity_available.notify_all();
363 }
364 }
365
366 if state.consumer_gone {
367 drop(state);
368 return Err(item);
369 }
370
371 state.buffer.push_back(item);
372 let waker = state.waker.take();
373 drop(state);
374
375 if let Some(waker) = waker {
376 waker.wake();
377 }
378 Ok(())
379 }
380
381 #[must_use]
387 pub fn buffered_count(&self) -> usize {
388 self.shared.lock_state().buffer.len()
389 }
390
391 #[must_use]
397 pub fn is_consumer_gone(&self) -> bool {
398 self.shared.lock_state().consumer_gone
399 }
400}
401
402impl<T> Drop for AsyncStreamSender<T> {
403 fn drop(&mut self) {
404 let waker = {
405 let mut state = self.shared.lock_state_for_drop();
406 state.sender_count -= 1;
407 if state.sender_count == 0 {
408 state.closed = true;
409 state.waker.take()
410 } else {
411 None
412 }
413 };
414
415 if let Some(waker) = waker {
416 waker.wake();
417 }
418 }
419}
420
421pub struct NextItem<'a, T> {
423 stream: &'a BoundedAsyncStream<T>,
424}
425
426impl<T> fmt::Debug for NextItem<'_, T> {
427 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
428 f.debug_struct("NextItem").finish_non_exhaustive()
429 }
430}
431
432impl<T> Future for NextItem<'_, T> {
433 type Output = Option<T>;
434
435 fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
436 self.stream.poll_next_item(cx)
437 }
438}
439
440#[cfg(feature = "futures-stream")]
441impl<T: 'static> futures_core::Stream for BoundedAsyncStream<T> {
442 type Item = T;
443
444 fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<T>> {
445 self.poll_next_item(cx)
446 }
447}
448
449#[cfg(test)]
450mod tests {
451 #[cfg(feature = "futures-stream")]
452 use std::future::poll_fn;
453 use std::future::Future;
454 use std::panic::{catch_unwind, AssertUnwindSafe};
455 use std::pin::Pin;
456 use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
457 use std::sync::{mpsc, Arc, Barrier, TryLockError, Weak};
458 use std::task::{Context, Poll, Wake, Waker};
459 use std::thread::{self, JoinHandle};
460 use std::time::Duration;
461
462 #[cfg(feature = "futures-stream")]
463 use futures_core::Stream;
464
465 use super::{AsyncStreamSender, BoundedAsyncStream, Shared};
466
467 const TEST_TIMEOUT: Duration = Duration::from_secs(5);
468
469 fn spawn_blocking_push(
470 sender: AsyncStreamSender<u32>,
471 item: u32,
472 ) -> (mpsc::Receiver<Result<(), u32>>, JoinHandle<()>) {
473 let (result_tx, result_rx) = mpsc::channel();
474 let handle = thread::spawn(move || {
475 let result = sender.push_or_block(item);
476 result_tx.send(result).unwrap();
477 });
478 (result_rx, handle)
479 }
480
481 #[test]
482 fn try_next_notifies_blocked_producer() {
483 let (stream, sender) = BoundedAsyncStream::new(1);
484 sender.push(1);
485 let (result_rx, handle) = spawn_blocking_push(sender, 2);
486 stream.shared.wait_for_blocked_producers(1);
487
488 assert_eq!(stream.try_next(), Some(1));
489 assert_eq!(result_rx.recv_timeout(TEST_TIMEOUT).unwrap(), Ok(()));
490 handle.join().unwrap();
491 assert_eq!(stream.try_next(), Some(2));
492 }
493
494 #[test]
495 fn next_future_notifies_blocked_producer() {
496 let (stream, sender) = BoundedAsyncStream::new(1);
497 sender.push(1);
498 let (result_rx, handle) = spawn_blocking_push(sender, 2);
499 stream.shared.wait_for_blocked_producers(1);
500
501 assert_eq!(pollster::block_on(stream.next()), Some(1));
502 assert_eq!(result_rx.recv_timeout(TEST_TIMEOUT).unwrap(), Ok(()));
503 handle.join().unwrap();
504 assert_eq!(pollster::block_on(stream.next()), Some(2));
505 }
506
507 #[cfg(feature = "futures-stream")]
508 #[test]
509 fn stream_poll_notifies_blocked_producer() {
510 let (mut stream, sender) = BoundedAsyncStream::new(1);
511 sender.push(1);
512 let (result_rx, handle) = spawn_blocking_push(sender, 2);
513 stream.shared.wait_for_blocked_producers(1);
514
515 let first = pollster::block_on(poll_fn(|cx| Pin::new(&mut stream).poll_next(cx)));
516 assert_eq!(first, Some(1));
517 assert_eq!(result_rx.recv_timeout(TEST_TIMEOUT).unwrap(), Ok(()));
518 handle.join().unwrap();
519
520 let second = pollster::block_on(poll_fn(|cx| Pin::new(&mut stream).poll_next(cx)));
521 assert_eq!(second, Some(2));
522 }
523
524 #[test]
525 fn clear_buffer_notifies_blocked_producer() {
526 let (stream, sender) = BoundedAsyncStream::new(1);
527 sender.push(1);
528 let (result_rx, handle) = spawn_blocking_push(sender, 2);
529 stream.shared.wait_for_blocked_producers(1);
530
531 stream.clear_buffer();
532 assert_eq!(result_rx.recv_timeout(TEST_TIMEOUT).unwrap(), Ok(()));
533 handle.join().unwrap();
534 assert_eq!(stream.try_next(), Some(2));
535 }
536
537 #[test]
538 fn consumer_drop_returns_blocked_item() {
539 let (stream, sender) = BoundedAsyncStream::new(1);
540 sender.push(1);
541 let (result_rx, handle) = spawn_blocking_push(sender, 2);
542 stream.shared.wait_for_blocked_producers(1);
543
544 drop(stream);
545 assert_eq!(result_rx.recv_timeout(TEST_TIMEOUT).unwrap(), Err(2));
546 handle.join().unwrap();
547 }
548
549 #[test]
550 fn concurrent_sender_clone_drops_close_stream() {
551 let (stream, sender) = BoundedAsyncStream::<u32>::new(1);
552 let sender_clone = sender.clone();
553 let barrier = Arc::new(Barrier::new(3));
554
555 let first_barrier = Arc::clone(&barrier);
556 let first = thread::spawn(move || {
557 first_barrier.wait();
558 drop(sender);
559 });
560 let second_barrier = Arc::clone(&barrier);
561 let second = thread::spawn(move || {
562 second_barrier.wait();
563 drop(sender_clone);
564 });
565
566 barrier.wait();
567 first.join().unwrap();
568 second.join().unwrap();
569
570 assert!(stream.is_closed());
571 assert_eq!(pollster::block_on(stream.next()), None);
572 }
573
574 fn shared_state_is_unlocked<T>(shared: &Weak<Shared<T>>) -> bool {
575 let Some(shared) = shared.upgrade() else {
576 return true;
577 };
578 let unlocked = match shared.state.try_lock() {
579 Ok(_state) => true,
580 Err(TryLockError::WouldBlock | TryLockError::Poisoned(_)) => false,
581 };
582 unlocked
583 }
584
585 struct ReentrantWaker {
586 shared: Weak<Shared<u32>>,
587 wake_was_unlocked: Arc<AtomicBool>,
588 drop_was_unlocked: Arc<AtomicBool>,
589 }
590
591 impl Wake for ReentrantWaker {
592 fn wake(self: Arc<Self>) {
593 self.wake_was_unlocked
594 .store(shared_state_is_unlocked(&self.shared), Ordering::SeqCst);
595 }
596 }
597
598 impl Drop for ReentrantWaker {
599 fn drop(&mut self) {
600 self.drop_was_unlocked
601 .store(shared_state_is_unlocked(&self.shared), Ordering::SeqCst);
602 }
603 }
604
605 #[test]
606 fn wakes_and_drops_waker_outside_state_mutex() {
607 let (stream, sender) = BoundedAsyncStream::new(1);
608 let wake_was_unlocked = Arc::new(AtomicBool::new(false));
609 let drop_was_unlocked = Arc::new(AtomicBool::new(false));
610 let probe = Arc::new(ReentrantWaker {
611 shared: Arc::downgrade(&stream.shared),
612 wake_was_unlocked: Arc::clone(&wake_was_unlocked),
613 drop_was_unlocked: Arc::clone(&drop_was_unlocked),
614 });
615 let waker = Waker::from(Arc::clone(&probe));
616 let mut next = stream.next();
617
618 {
619 let mut cx = Context::from_waker(&waker);
620 assert_eq!(Pin::new(&mut next).poll(&mut cx), Poll::Pending);
621 }
622 drop(waker);
623 drop(probe);
624
625 sender.push(1);
626
627 assert!(wake_was_unlocked.load(Ordering::SeqCst));
628 assert!(drop_was_unlocked.load(Ordering::SeqCst));
629 }
630
631 struct ReentrantItem {
632 shared: Weak<Shared<Self>>,
633 unlocked_drops: Arc<AtomicUsize>,
634 locked_drops: Arc<AtomicUsize>,
635 }
636
637 impl Drop for ReentrantItem {
638 fn drop(&mut self) {
639 if shared_state_is_unlocked(&self.shared) {
640 self.unlocked_drops.fetch_add(1, Ordering::SeqCst);
641 } else {
642 self.locked_drops.fetch_add(1, Ordering::SeqCst);
643 }
644 }
645 }
646
647 #[test]
648 fn overwritten_and_cleared_items_drop_outside_state_mutex() {
649 let (stream, sender) = BoundedAsyncStream::new(1);
650 let unlocked_drops = Arc::new(AtomicUsize::new(0));
651 let locked_drops = Arc::new(AtomicUsize::new(0));
652
653 let make_item = || ReentrantItem {
654 shared: Arc::downgrade(&stream.shared),
655 unlocked_drops: Arc::clone(&unlocked_drops),
656 locked_drops: Arc::clone(&locked_drops),
657 };
658
659 sender.push(make_item());
660 sender.push(make_item());
661 assert_eq!(unlocked_drops.load(Ordering::SeqCst), 1);
662
663 stream.clear_buffer();
664 assert_eq!(unlocked_drops.load(Ordering::SeqCst), 2);
665 assert_eq!(locked_drops.load(Ordering::SeqCst), 0);
666 }
667
668 #[test]
669 fn poisoned_state_does_not_masquerade_as_close_or_delivery() {
670 let (stream, sender) = BoundedAsyncStream::new(1);
671 let shared = Arc::clone(&stream.shared);
672 assert!(thread::spawn(move || {
673 let _state = shared.state.lock().unwrap();
674 panic!("poison stream state");
675 })
676 .join()
677 .is_err());
678
679 assert!(catch_unwind(AssertUnwindSafe(|| sender.push(1))).is_err());
680 assert!(catch_unwind(AssertUnwindSafe(|| stream.try_next())).is_err());
681 assert!(catch_unwind(AssertUnwindSafe(|| pollster::block_on(stream.next()))).is_err());
682 }
683}