use std::{ collections::VecDeque, fmt, iter::FusedIterator, sync::{Arc, Condvar, Mutex, MutexGuard, PoisonError, TryLockError, atomic::AtomicUsize}, }; use rand::{Rng, SeedableRng, rngs::SmallRng}; use crate::Priority; struct PriorityQueues { high_priority: VecDeque, medium_priority: VecDeque, low_priority: VecDeque, } impl PriorityQueues { fn is_empty(&self) -> bool { self.high_priority.is_empty() && self.medium_priority.is_empty() && self.low_priority.is_empty() } } struct PriorityQueueState { // std's Wasm atomic backend unlocks and notifies without acquiring another // lock. parking_lot can park during those operations, even after try_lock. queues: Mutex>, condvar: Condvar, receiver_count: AtomicUsize, sender_count: AtomicUsize, } impl PriorityQueueState { fn send(&self, priority: Priority, item: T) -> Result<(), SendError> { if self .receiver_count .load(std::sync::atomic::Ordering::Relaxed) == 0 { return Err(SendError(item)); } let mut queues = self.queues.lock().unwrap_or_else(PoisonError::into_inner); Self::push(&mut queues, priority, item); self.condvar.notify_one(); Ok(()) } fn spin_send(&self, priority: Priority, item: T) -> Result<(), SendError> { if self .receiver_count .load(std::sync::atomic::Ordering::Relaxed) == 0 { return Err(SendError(item)); } let mut queues = loop { match self.queues.try_lock() { Ok(guard) => break guard, Err(TryLockError::Poisoned(error)) => break error.into_inner(), Err(TryLockError::WouldBlock) => std::hint::spin_loop(), } }; Self::push(&mut queues, priority, item); self.condvar.notify_one(); Ok(()) } fn push(queues: &mut PriorityQueues, priority: Priority, item: T) { match priority { Priority::RealtimeAudio => unreachable!( "Realtime audio priority runs on a dedicated thread and is never queued" ), Priority::High => queues.high_priority.push_back(item), Priority::Medium => queues.medium_priority.push_back(item), Priority::Low => queues.low_priority.push_back(item), }; } fn recv<'a>(&'a self) -> Result>, RecvError> { let mut queues = self.queues.lock().unwrap_or_else(PoisonError::into_inner); // Re-checked after every wake: dropping the last sender wakes waiting receivers // so they can end instead of blocking forever on a queue nothing can fill. while queues.is_empty() { if self.sender_count.load(std::sync::atomic::Ordering::Acquire) == 0 { return Err(crate::queue::RecvError); } queues = self .condvar .wait(queues) .unwrap_or_else(PoisonError::into_inner); } Ok(queues) } fn try_recv<'a>(&'a self) -> Result>>, RecvError> { let queues = self.queues.lock().unwrap_or_else(PoisonError::into_inner); let sender_count = self.sender_count.load(std::sync::atomic::Ordering::Relaxed); if queues.is_empty() && sender_count == 0 { return Err(crate::queue::RecvError); } if queues.is_empty() { Ok(None) } else { Ok(Some(queues)) } } fn spin_try_recv<'a>(&'a self) -> Result>>, RecvError> { let queues = loop { match self.queues.try_lock() { Ok(guard) => break guard, Err(TryLockError::Poisoned(error)) => break error.into_inner(), Err(TryLockError::WouldBlock) => std::hint::spin_loop(), } }; let sender_count = self.sender_count.load(std::sync::atomic::Ordering::Relaxed); if queues.is_empty() && sender_count == 0 { return Err(crate::queue::RecvError); } if queues.is_empty() { Ok(None) } else { Ok(Some(queues)) } } } #[doc(hidden)] pub struct PriorityQueueSender { state: Arc>, } impl PriorityQueueSender { fn new(state: Arc>) -> Self { Self { state } } pub fn send(&self, priority: Priority, item: T) -> Result<(), SendError> { self.state.send(priority, item)?; Ok(()) } pub fn spin_send(&self, priority: Priority, item: T) -> Result<(), SendError> { self.state.spin_send(priority, item)?; Ok(()) } } impl Drop for PriorityQueueSender { fn drop(&mut self) { let previous = self .state .sender_count .fetch_sub(1, std::sync::atomic::Ordering::AcqRel); if previous == 1 { // A receiver checks the sender count and starts waiting under the queue lock, // atomically. Acquiring that lock once, even with nothing to do under it, // means any receiver that saw a live sender is already waiting, so the // notification below reaches it; without this, a receiver between its check // and its wait would miss the wake-up and block forever. drop( self.state .queues .lock() .unwrap_or_else(PoisonError::into_inner), ); self.state.condvar.notify_all(); } } } #[doc(hidden)] pub struct PriorityQueueReceiver { state: Arc>, rand: SmallRng, disconnected: bool, } impl Clone for PriorityQueueReceiver { fn clone(&self) -> Self { self.state .receiver_count .fetch_add(1, std::sync::atomic::Ordering::AcqRel); Self { state: Arc::clone(&self.state), rand: SmallRng::seed_from_u64(0), disconnected: self.disconnected, } } } #[doc(hidden)] pub struct SendError(pub T); impl fmt::Debug for SendError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_tuple("SendError").field(&self.0).finish() } } #[derive(Debug)] #[doc(hidden)] pub struct RecvError; #[allow(dead_code)] impl PriorityQueueReceiver { pub fn new() -> (PriorityQueueSender, Self) { let state = PriorityQueueState { queues: Mutex::new(PriorityQueues { high_priority: VecDeque::new(), medium_priority: VecDeque::new(), low_priority: VecDeque::new(), }), condvar: Condvar::new(), receiver_count: AtomicUsize::new(1), sender_count: AtomicUsize::new(1), }; let state = Arc::new(state); let sender = PriorityQueueSender::new(Arc::clone(&state)); let receiver = PriorityQueueReceiver { state, rand: SmallRng::seed_from_u64(0), disconnected: false, }; (sender, receiver) } /// Returns whether the queue currently contains no elements. pub fn is_empty(&self) -> bool { self.state .queues .lock() .unwrap_or_else(PoisonError::into_inner) .is_empty() } /// Returns the number of queued elements across all priorities. pub(crate) fn len(&self) -> usize { let queues = self .state .queues .lock() .unwrap_or_else(PoisonError::into_inner); queues.high_priority.len() + queues.medium_priority.len() + queues.low_priority.len() } /// Tries to pop one element from the priority queue without blocking. /// /// This will early return if there are no elements in the queue. /// /// This method is best suited if you only intend to pop one element, for better performance /// on large queues see [`Self::try_iter`] /// /// # Errors /// /// If the sender was dropped pub fn try_pop(&mut self) -> Result, RecvError> { self.pop_inner(false) } pub fn spin_try_pop(&mut self) -> Result, RecvError> { use Priority as P; let Some(mut queues) = self.state.spin_try_recv()? else { return Ok(None); }; let high = P::High.weight() * !queues.high_priority.is_empty() as u32; let medium = P::Medium.weight() * !queues.medium_priority.is_empty() as u32; let low = P::Low.weight() * !queues.low_priority.is_empty() as u32; let mut mass = high + medium + low; if !queues.high_priority.is_empty() { let flip = self.rand.random_ratio(P::High.weight(), mass); if flip { return Ok(queues.high_priority.pop_front()); } mass -= P::High.weight(); } if !queues.medium_priority.is_empty() { let flip = self.rand.random_ratio(P::Medium.weight(), mass); if flip { return Ok(queues.medium_priority.pop_front()); } mass -= P::Medium.weight(); } if !queues.low_priority.is_empty() { let flip = self.rand.random_ratio(P::Low.weight(), mass); if flip { return Ok(queues.low_priority.pop_front()); } } Ok(None) } /// Pops an element from the priority queue blocking if necessary. /// /// This method is best suited if you only intend to pop one element, for better performance /// on large queues see [`Self::iter``] /// /// # Errors /// /// If the sender was dropped pub fn pop(&mut self) -> Result { self.pop_inner(true).map(|e| e.unwrap()) } /// Returns an iterator over the elements of the queue /// this iterator will end when all elements have been consumed and will not wait for new ones. pub fn try_iter(self) -> TryIter { TryIter { receiver: self, ended: false, } } /// Returns an iterator over the elements of the queue /// this iterator will wait for new elements if the queue is empty. pub fn iter(self) -> Iter { Iter(self) } #[inline(always)] // algorithm is the loaded die from biased coin from // https://www.keithschwarz.com/darts-dice-coins/ fn pop_inner(&mut self, block: bool) -> Result, RecvError> { use Priority as P; let mut queues = if !block { let Some(queues) = self.state.try_recv()? else { return Ok(None); }; queues } else { self.state.recv()? }; let high = P::High.weight() * !queues.high_priority.is_empty() as u32; let medium = P::Medium.weight() * !queues.medium_priority.is_empty() as u32; let low = P::Low.weight() * !queues.low_priority.is_empty() as u32; let mut mass = high + medium + low; //% if !queues.high_priority.is_empty() { let flip = self.rand.random_ratio(P::High.weight(), mass); if flip { return Ok(queues.high_priority.pop_front()); } mass -= P::High.weight(); } if !queues.medium_priority.is_empty() { let flip = self.rand.random_ratio(P::Medium.weight(), mass); if flip { return Ok(queues.medium_priority.pop_front()); } mass -= P::Medium.weight(); } if !queues.low_priority.is_empty() { let flip = self.rand.random_ratio(P::Low.weight(), mass); if flip { return Ok(queues.low_priority.pop_front()); } } Ok(None) } } impl Drop for PriorityQueueReceiver { fn drop(&mut self) { self.state .receiver_count .fetch_sub(1, std::sync::atomic::Ordering::AcqRel); } } #[doc(hidden)] pub struct Iter(PriorityQueueReceiver); impl Iterator for Iter { type Item = T; fn next(&mut self) -> Option { self.0.pop().ok() } } impl FusedIterator for Iter {} #[doc(hidden)] pub struct TryIter { receiver: PriorityQueueReceiver, ended: bool, } impl Iterator for TryIter { type Item = Result; fn next(&mut self) -> Option { if self.ended { return None; } let res = self.receiver.try_pop(); self.ended = res.is_err(); res.transpose() } } impl FusedIterator for TryIter {} #[cfg(test)] mod tests { use collections::HashSet; use super::*; #[test] fn blocked_receivers_end_when_the_sender_drops() { let (sender, receiver) = PriorityQueueReceiver::::new(); let (done_sender, done_receiver) = std::sync::mpsc::channel(); let workers = (0..2) .map(|_| { let receiver = receiver.clone(); let done_sender = done_sender.clone(); std::thread::spawn(move || { let received: Vec = receiver.iter().collect(); done_sender.send(received).ok(); }) }) .collect::>(); drop(receiver); sender.send(Priority::Medium, 7).unwrap(); std::thread::sleep(std::time::Duration::from_millis(50)); assert!( done_receiver.try_recv().is_err(), "receivers keep waiting while the sender is alive" ); drop(sender); let mut received = Vec::new(); for _ in 0..2 { received.extend( done_receiver .recv_timeout(std::time::Duration::from_secs(5)) .expect("a waiting receiver should end once the sender is dropped"), ); } assert_eq!(received, [7]); for worker in workers { worker.join().expect("worker should exit cleanly"); } } #[test] fn all_tasks_get_yielded() { let (tx, mut rx) = PriorityQueueReceiver::new(); tx.send(Priority::Medium, 20).unwrap(); tx.send(Priority::High, 30).unwrap(); tx.send(Priority::Low, 10).unwrap(); tx.send(Priority::Medium, 21).unwrap(); tx.send(Priority::High, 31).unwrap(); drop(tx); assert_eq!( rx.iter().collect::>(), [30, 31, 20, 21, 10].into_iter().collect::>() ) } #[test] fn new_high_prio_task_get_scheduled_quickly() { let (tx, mut rx) = PriorityQueueReceiver::new(); for _ in 0..100 { tx.send(Priority::Low, 1).unwrap(); } assert_eq!(rx.pop().unwrap(), 1); tx.send(Priority::High, 3).unwrap(); assert_eq!(rx.pop().unwrap(), 3); assert_eq!(rx.pop().unwrap(), 1); } }