#![cfg_attr(test, allow(clippy::unwrap_used, reason = "test scope"))]
use super::*;
use core::mem::size_of;
use std::future::Future;
use std::pin::Pin;
use std::sync::atomic::Ordering;
use std::task::{Context, Poll};
#[test]
fn hybrid_channel_factory_is_zero_sized() {
assert_eq!(size_of::<HybridChannel<u8>>(), 0);
}
#[test]
fn test_recv_future_waker_cleanup_on_drop() {
use std::task::{RawWaker, RawWakerVTable, Waker};
fn dummy_raw_waker() -> RawWaker {
fn clone_raw(_: *const ()) -> RawWaker {
dummy_raw_waker()
}
fn wake_raw(_: *const ()) {}
fn wake_by_ref_raw(_: *const ()) {}
fn drop_raw(_: *const ()) {}
static VTABLE: RawWakerVTable =
RawWakerVTable::new(clone_raw, wake_raw, wake_by_ref_raw, drop_raw);
RawWaker::new(std::ptr::null(), &VTABLE)
}
let (_tx, rx) = HybridChannel::<i32>::new(4);
let waker = unsafe { Waker::from_raw(dummy_raw_waker()) };
let mut cx = Context::from_waker(&waker);
{
let mut fut = rx.recv_async();
let mut pinned = Pin::new(&mut fut);
assert_eq!(pinned.as_mut().poll(&mut cx), Poll::Pending);
let wakers = rx.async_wakers.lock().unwrap();
assert_eq!(wakers.len(), 1);
}
let wakers = rx.async_wakers.lock().unwrap();
assert_eq!(wakers.len(), 0);
}
#[test]
fn test_hybrid_channel_lost_wakeup() {
use std::task::{RawWaker, RawWakerVTable, Waker};
fn dummy_raw_waker() -> RawWaker {
fn clone_raw(_: *const ()) -> RawWaker {
dummy_raw_waker()
}
fn wake_raw(_: *const ()) {}
fn wake_by_ref_raw(_: *const ()) {}
fn drop_raw(_: *const ()) {}
static VTABLE: RawWakerVTable =
RawWakerVTable::new(clone_raw, wake_raw, wake_by_ref_raw, drop_raw);
RawWaker::new(std::ptr::null(), &VTABLE)
}
let (tx, rx) = HybridChannel::<i32>::new(4);
let waker = unsafe { Waker::from_raw(dummy_raw_waker()) };
let mut cx = Context::from_waker(&waker);
let mut fut = rx.recv_async();
let mut pinned = Pin::new(&mut fut);
assert_eq!(pinned.as_mut().poll(&mut cx), Poll::Pending);
assert_eq!(rx.waker_count.load(Ordering::Relaxed), 1);
tx.send(42).unwrap();
assert_eq!(rx.waker_count.load(Ordering::Relaxed), 0);
assert_eq!(rx.try_recv().unwrap(), 42);
assert_eq!(pinned.as_mut().poll(&mut cx), Poll::Pending);
assert_eq!(rx.waker_count.load(Ordering::Relaxed), 1);
}
#[test]
fn send_wakes_outside_the_waker_registry_lock() {
use std::sync::atomic::AtomicBool;
use std::sync::{Arc, Mutex};
use std::task::{Wake, Waker};
struct RegistryProbe {
registry: Arc<Mutex<Vec<(u64, Waker)>>>,
woken: AtomicBool,
held_during_wake: AtomicBool,
}
impl Wake for RegistryProbe {
fn wake(self: Arc<Self>) {
self.woken.store(true, Ordering::SeqCst);
if self.registry.try_lock().is_err() {
self.held_during_wake.store(true, Ordering::SeqCst);
}
}
}
let (tx, rx) = HybridChannel::<i32>::new(4);
let probe = Arc::new(RegistryProbe {
registry: Arc::clone(&rx.async_wakers),
woken: AtomicBool::new(false),
held_during_wake: AtomicBool::new(false),
});
let waker = Waker::from(Arc::clone(&probe));
let mut cx = Context::from_waker(&waker);
let mut fut = rx.recv_async();
assert_eq!(Pin::new(&mut fut).poll(&mut cx), Poll::Pending);
tx.send(9).unwrap();
assert!(
probe.woken.load(Ordering::SeqCst),
"send must wake the future"
);
assert!(
!probe.held_during_wake.load(Ordering::SeqCst),
"wake ran while notify_consumers held the waker registry lock"
);
assert_eq!(Pin::new(&mut fut).poll(&mut cx), Poll::Ready(Ok(9)));
}
#[test]
fn recv_delivers_last_message_under_park_unpark_churn() {
use std::sync::{Arc, Barrier};
const ROUNDS: usize = 4096;
let (tx, rx) = HybridChannel::<usize>::new(2);
let barrier = Arc::new(Barrier::new(2));
let receiver_barrier = Arc::clone(&barrier);
let receiver = std::thread::spawn(move || {
for round in 0..ROUNDS {
receiver_barrier.wait();
assert_eq!(rx.recv().unwrap(), round, "round {round} lost its message");
}
});
for round in 0..ROUNDS {
barrier.wait();
tx.send(round).unwrap();
}
receiver.join().unwrap();
}
#[test]
fn recv_timeout_observes_message_racing_registration() {
use std::sync::{Arc, Barrier};
use std::time::Duration;
const ROUNDS: usize = 1024;
let (tx, rx) = HybridChannel::<usize>::new(2);
let barrier = Arc::new(Barrier::new(2));
let receiver_barrier = Arc::clone(&barrier);
let receiver = std::thread::spawn(move || {
for round in 0..ROUNDS {
receiver_barrier.wait();
assert_eq!(
rx.recv_timeout(Duration::from_secs(30)).unwrap(),
round,
"round {round} lost its message"
);
}
});
for round in 0..ROUNDS {
barrier.wait();
tx.send(round).unwrap();
}
receiver.join().unwrap();
}
#[test]
fn recv_future_delivers_last_message_under_wake_churn() {
use std::sync::atomic::AtomicUsize;
use std::sync::{Arc, Barrier};
use std::task::{Wake, Waker};
const ROUNDS: usize = 4096;
struct CountingWaker {
wakes: AtomicUsize,
}
impl Wake for CountingWaker {
fn wake(self: Arc<Self>) {
self.wakes.fetch_add(1, Ordering::SeqCst);
}
}
let (tx, rx) = HybridChannel::<usize>::new(2);
let barrier = Arc::new(Barrier::new(2));
let sender_barrier = Arc::clone(&barrier);
let sender = std::thread::spawn(move || {
for round in 0..ROUNDS {
sender_barrier.wait();
tx.send(round).unwrap();
}
});
let probe = Arc::new(CountingWaker {
wakes: AtomicUsize::new(0),
});
let waker = Waker::from(Arc::clone(&probe));
let mut cx = Context::from_waker(&waker);
for round in 0..ROUNDS {
let mut fut = rx.recv_async();
let mut pinned = Pin::new(&mut fut);
barrier.wait();
loop {
let wakes_before = probe.wakes.load(Ordering::SeqCst);
match pinned.as_mut().poll(&mut cx) {
Poll::Ready(value) => {
assert_eq!(value.unwrap(), round, "round {round} lost its message");
break;
}
Poll::Pending => {
while probe.wakes.load(Ordering::SeqCst) == wakes_before {
std::hint::spin_loop();
}
}
}
}
}
sender.join().unwrap();
}