Skip to main content

gpui/
queue.rs

1//! Provides a thread-safe queue with weighted random selection between priorities.
2//!
3//! Items within each priority are received in FIFO order. Receivers can be cloned
4//! to share work, and queued items remain available after the sender is dropped.
5
6use std::{
7    collections::VecDeque,
8    fmt,
9    iter::FusedIterator,
10    sync::{Arc, Condvar, Mutex, MutexGuard, PoisonError, TryLockError, atomic::AtomicUsize},
11};
12
13use rand::{Rng, SeedableRng, rngs::SmallRng};
14
15use crate::Priority;
16
17struct PriorityQueues<T> {
18    high_priority: VecDeque<T>,
19    medium_priority: VecDeque<T>,
20    low_priority: VecDeque<T>,
21}
22
23impl<T> PriorityQueues<T> {
24    fn is_empty(&self) -> bool {
25        self.high_priority.is_empty()
26            && self.medium_priority.is_empty()
27            && self.low_priority.is_empty()
28    }
29}
30
31struct PriorityQueueState<T> {
32    // std's Wasm atomic backend unlocks and notifies without acquiring another
33    // lock. parking_lot can park during those operations, even after try_lock.
34    queues: Mutex<PriorityQueues<T>>,
35    condvar: Condvar,
36    receiver_count: AtomicUsize,
37    sender_count: AtomicUsize,
38}
39
40impl<T> PriorityQueueState<T> {
41    fn send(&self, priority: Priority, item: T) -> Result<(), SendError<T>> {
42        if self
43            .receiver_count
44            .load(std::sync::atomic::Ordering::Relaxed)
45            == 0
46        {
47            return Err(SendError(item));
48        }
49
50        let mut queues = self.queues.lock().unwrap_or_else(PoisonError::into_inner);
51        Self::push(&mut queues, priority, item);
52        self.condvar.notify_one();
53        Ok(())
54    }
55
56    fn spin_send(&self, priority: Priority, item: T) -> Result<(), SendError<T>> {
57        if self
58            .receiver_count
59            .load(std::sync::atomic::Ordering::Relaxed)
60            == 0
61        {
62            return Err(SendError(item));
63        }
64
65        let mut queues = loop {
66            match self.queues.try_lock() {
67                Ok(guard) => break guard,
68                Err(TryLockError::Poisoned(error)) => break error.into_inner(),
69                Err(TryLockError::WouldBlock) => std::hint::spin_loop(),
70            }
71        };
72        Self::push(&mut queues, priority, item);
73        self.condvar.notify_one();
74        Ok(())
75    }
76
77    fn push(queues: &mut PriorityQueues<T>, priority: Priority, item: T) {
78        match priority {
79            Priority::RealtimeAudio => unreachable!(
80                "Realtime audio priority runs on a dedicated thread and is never queued"
81            ),
82            Priority::High => queues.high_priority.push_back(item),
83            Priority::Medium => queues.medium_priority.push_back(item),
84            Priority::Low => queues.low_priority.push_back(item),
85        };
86    }
87
88    fn recv<'a>(&'a self) -> Result<MutexGuard<'a, PriorityQueues<T>>, RecvError> {
89        let mut queues = self.queues.lock().unwrap_or_else(PoisonError::into_inner);
90
91        // Re-checked after every wake: dropping the last sender wakes waiting receivers
92        // so they can end instead of blocking forever on a queue nothing can fill.
93        while queues.is_empty() {
94            if self.sender_count.load(std::sync::atomic::Ordering::Acquire) == 0 {
95                return Err(crate::queue::RecvError);
96            }
97            queues = self
98                .condvar
99                .wait(queues)
100                .unwrap_or_else(PoisonError::into_inner);
101        }
102
103        Ok(queues)
104    }
105
106    fn try_recv<'a>(&'a self) -> Result<Option<MutexGuard<'a, PriorityQueues<T>>>, RecvError> {
107        let queues = self.queues.lock().unwrap_or_else(PoisonError::into_inner);
108
109        let sender_count = self.sender_count.load(std::sync::atomic::Ordering::Relaxed);
110        if queues.is_empty() && sender_count == 0 {
111            return Err(crate::queue::RecvError);
112        }
113
114        if queues.is_empty() {
115            Ok(None)
116        } else {
117            Ok(Some(queues))
118        }
119    }
120
121    fn spin_try_recv<'a>(&'a self) -> Result<Option<MutexGuard<'a, PriorityQueues<T>>>, RecvError> {
122        let queues = loop {
123            match self.queues.try_lock() {
124                Ok(guard) => break guard,
125                Err(TryLockError::Poisoned(error)) => break error.into_inner(),
126                Err(TryLockError::WouldBlock) => std::hint::spin_loop(),
127            }
128        };
129
130        let sender_count = self.sender_count.load(std::sync::atomic::Ordering::Relaxed);
131        if queues.is_empty() && sender_count == 0 {
132            return Err(crate::queue::RecvError);
133        }
134
135        if queues.is_empty() {
136            Ok(None)
137        } else {
138            Ok(Some(queues))
139        }
140    }
141}
142
143#[doc(hidden)]
144pub struct PriorityQueueSender<T> {
145    state: Arc<PriorityQueueState<T>>,
146}
147
148impl<T> PriorityQueueSender<T> {
149    fn new(state: Arc<PriorityQueueState<T>>) -> Self {
150        Self { state }
151    }
152
153    /// Enqueues an item at the given priority, waiting for the queue lock if necessary.
154    ///
155    /// # Errors
156    ///
157    /// Returns the item in a [`SendError<T>`] if all receivers have been dropped.
158    ///
159    /// # Panics
160    ///
161    /// Panics if `priority` is [`Priority::RealtimeAudio`] and a receiver still exists.
162    pub fn send(&self, priority: Priority, item: T) -> Result<(), SendError<T>> {
163        self.state.send(priority, item)?;
164        Ok(())
165    }
166
167    /// Enqueues an item at the given priority, spinning until the queue lock is acquired.
168    ///
169    /// # Errors
170    ///
171    /// Returns the item in a [`SendError<T>`] if all receivers have been dropped.
172    ///
173    /// # Panics
174    ///
175    /// Panics if `priority` is [`Priority::RealtimeAudio`] and a receiver still exists.
176    pub fn spin_send(&self, priority: Priority, item: T) -> Result<(), SendError<T>> {
177        self.state.spin_send(priority, item)?;
178        Ok(())
179    }
180}
181
182impl<T> Drop for PriorityQueueSender<T> {
183    fn drop(&mut self) {
184        let previous = self
185            .state
186            .sender_count
187            .fetch_sub(1, std::sync::atomic::Ordering::AcqRel);
188        if previous == 1 {
189            // A receiver checks the sender count and starts waiting under the queue lock,
190            // atomically. Acquiring that lock once, even with nothing to do under it,
191            // means any receiver that saw a live sender is already waiting, so the
192            // notification below reaches it; without this, a receiver between its check
193            // and its wait would miss the wake-up and block forever.
194            drop(
195                self.state
196                    .queues
197                    .lock()
198                    .unwrap_or_else(PoisonError::into_inner),
199            );
200            self.state.condvar.notify_all();
201        }
202    }
203}
204
205#[doc(hidden)]
206pub struct PriorityQueueReceiver<T> {
207    state: Arc<PriorityQueueState<T>>,
208    rand: SmallRng,
209    disconnected: bool,
210}
211
212impl<T> Clone for PriorityQueueReceiver<T> {
213    fn clone(&self) -> Self {
214        self.state
215            .receiver_count
216            .fetch_add(1, std::sync::atomic::Ordering::AcqRel);
217        Self {
218            state: Arc::clone(&self.state),
219            rand: SmallRng::seed_from_u64(0),
220            disconnected: self.disconnected,
221        }
222    }
223}
224
225#[doc(hidden)]
226pub struct SendError<T>(pub T);
227
228impl<T: fmt::Debug> fmt::Debug for SendError<T> {
229    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
230        f.debug_tuple("SendError").field(&self.0).finish()
231    }
232}
233
234#[derive(Debug)]
235#[doc(hidden)]
236pub struct RecvError;
237
238#[allow(dead_code)]
239impl<T> PriorityQueueReceiver<T> {
240    /// Creates an empty priority queue and returns its sender and receiver.
241    ///
242    /// # Examples
243    ///
244    /// ```
245    /// use gpui::{Priority, queue::PriorityQueueReceiver};
246    ///
247    /// let (sender, mut receiver) = PriorityQueueReceiver::new();
248    /// sender.send(Priority::Medium, 42).unwrap();
249    /// assert_eq!(receiver.pop().unwrap(), 42);
250    /// ```
251    pub fn new() -> (PriorityQueueSender<T>, Self) {
252        let state = PriorityQueueState {
253            queues: Mutex::new(PriorityQueues {
254                high_priority: VecDeque::new(),
255                medium_priority: VecDeque::new(),
256                low_priority: VecDeque::new(),
257            }),
258            condvar: Condvar::new(),
259            receiver_count: AtomicUsize::new(1),
260            sender_count: AtomicUsize::new(1),
261        };
262        let state = Arc::new(state);
263
264        let sender = PriorityQueueSender::new(Arc::clone(&state));
265
266        let receiver = PriorityQueueReceiver {
267            state,
268            rand: SmallRng::seed_from_u64(0),
269            disconnected: false,
270        };
271
272        (sender, receiver)
273    }
274
275    /// Returns whether the queue currently contains no elements.
276    pub fn is_empty(&self) -> bool {
277        self.state
278            .queues
279            .lock()
280            .unwrap_or_else(PoisonError::into_inner)
281            .is_empty()
282    }
283
284    /// Returns the number of queued elements across all priorities.
285    pub(crate) fn len(&self) -> usize {
286        let queues = self
287            .state
288            .queues
289            .lock()
290            .unwrap_or_else(PoisonError::into_inner);
291        queues.high_priority.len() + queues.medium_priority.len() + queues.low_priority.len()
292    }
293
294    /// Tries to pop one element from the priority queue without blocking.
295    ///
296    /// This will early return if there are no elements in the queue.
297    ///
298    /// This method is best suited if you only intend to pop one element, for better performance
299    /// on large queues see [`Self::try_iter`]
300    ///
301    /// # Errors
302    ///
303    /// If the sender was dropped
304    pub fn try_pop(&mut self) -> Result<Option<T>, RecvError> {
305        self.pop_inner(false)
306    }
307
308    /// Pops an item if available, spinning until the queue lock is acquired.
309    ///
310    /// Returns `Ok(None)` if the queue is empty and the sender still exists.
311    /// Does not wait for new items to arrive.
312    ///
313    /// # Errors
314    ///
315    /// Returns [`RecvError`] if the queue is empty and the sender has been dropped.
316    pub fn spin_try_pop(&mut self) -> Result<Option<T>, RecvError> {
317        use Priority as P;
318
319        let Some(mut queues) = self.state.spin_try_recv()? else {
320            return Ok(None);
321        };
322
323        let high = P::High.weight() * !queues.high_priority.is_empty() as u32;
324        let medium = P::Medium.weight() * !queues.medium_priority.is_empty() as u32;
325        let low = P::Low.weight() * !queues.low_priority.is_empty() as u32;
326        let mut mass = high + medium + low;
327
328        if !queues.high_priority.is_empty() {
329            let flip = self.rand.random_ratio(P::High.weight(), mass);
330            if flip {
331                return Ok(queues.high_priority.pop_front());
332            }
333            mass -= P::High.weight();
334        }
335
336        if !queues.medium_priority.is_empty() {
337            let flip = self.rand.random_ratio(P::Medium.weight(), mass);
338            if flip {
339                return Ok(queues.medium_priority.pop_front());
340            }
341            mass -= P::Medium.weight();
342        }
343
344        if !queues.low_priority.is_empty() {
345            let flip = self.rand.random_ratio(P::Low.weight(), mass);
346            if flip {
347                return Ok(queues.low_priority.pop_front());
348            }
349        }
350
351        Ok(None)
352    }
353
354    /// Pops an element from the priority queue blocking if necessary.
355    ///
356    /// This method is best suited if you only intend to pop one element, for better performance
357    /// on large queues see [`Self::iter`].
358    ///
359    /// # Errors
360    ///
361    /// If the sender was dropped
362    pub fn pop(&mut self) -> Result<T, RecvError> {
363        self.pop_inner(true).map(|e| e.unwrap())
364    }
365
366    /// Returns an iterator over the elements of the queue
367    /// this iterator will end when all elements have been consumed and will not wait for new ones.
368    pub fn try_iter(self) -> TryIter<T> {
369        TryIter {
370            receiver: self,
371            ended: false,
372        }
373    }
374
375    /// Returns an iterator over the elements of the queue
376    /// this iterator will wait for new elements if the queue is empty.
377    pub fn iter(self) -> Iter<T> {
378        Iter(self)
379    }
380
381    #[inline(always)]
382    // algorithm is the loaded die from biased coin from
383    // https://www.keithschwarz.com/darts-dice-coins/
384    fn pop_inner(&mut self, block: bool) -> Result<Option<T>, RecvError> {
385        use Priority as P;
386
387        let mut queues = if !block {
388            let Some(queues) = self.state.try_recv()? else {
389                return Ok(None);
390            };
391            queues
392        } else {
393            self.state.recv()?
394        };
395
396        let high = P::High.weight() * !queues.high_priority.is_empty() as u32;
397        let medium = P::Medium.weight() * !queues.medium_priority.is_empty() as u32;
398        let low = P::Low.weight() * !queues.low_priority.is_empty() as u32;
399        let mut mass = high + medium + low; //%
400
401        if !queues.high_priority.is_empty() {
402            let flip = self.rand.random_ratio(P::High.weight(), mass);
403            if flip {
404                return Ok(queues.high_priority.pop_front());
405            }
406            mass -= P::High.weight();
407        }
408
409        if !queues.medium_priority.is_empty() {
410            let flip = self.rand.random_ratio(P::Medium.weight(), mass);
411            if flip {
412                return Ok(queues.medium_priority.pop_front());
413            }
414            mass -= P::Medium.weight();
415        }
416
417        if !queues.low_priority.is_empty() {
418            let flip = self.rand.random_ratio(P::Low.weight(), mass);
419            if flip {
420                return Ok(queues.low_priority.pop_front());
421            }
422        }
423
424        Ok(None)
425    }
426}
427
428impl<T> Drop for PriorityQueueReceiver<T> {
429    fn drop(&mut self) {
430        self.state
431            .receiver_count
432            .fetch_sub(1, std::sync::atomic::Ordering::AcqRel);
433    }
434}
435
436#[doc(hidden)]
437pub struct Iter<T>(PriorityQueueReceiver<T>);
438impl<T> Iterator for Iter<T> {
439    type Item = T;
440
441    fn next(&mut self) -> Option<Self::Item> {
442        self.0.pop().ok()
443    }
444}
445impl<T> FusedIterator for Iter<T> {}
446
447#[doc(hidden)]
448pub struct TryIter<T> {
449    receiver: PriorityQueueReceiver<T>,
450    ended: bool,
451}
452impl<T> Iterator for TryIter<T> {
453    type Item = Result<T, RecvError>;
454
455    fn next(&mut self) -> Option<Self::Item> {
456        if self.ended {
457            return None;
458        }
459
460        let res = self.receiver.try_pop();
461        self.ended = res.is_err();
462
463        res.transpose()
464    }
465}
466impl<T> FusedIterator for TryIter<T> {}
467
468#[cfg(test)]
469mod tests {
470    use collections::HashSet;
471
472    use super::*;
473
474    #[test]
475    fn blocked_receivers_end_when_the_sender_drops() {
476        let (sender, receiver) = PriorityQueueReceiver::<u32>::new();
477        let (done_sender, done_receiver) = std::sync::mpsc::channel();
478        let workers = (0..2)
479            .map(|_| {
480                let receiver = receiver.clone();
481                let done_sender = done_sender.clone();
482                std::thread::spawn(move || {
483                    let received: Vec<u32> = receiver.iter().collect();
484                    done_sender.send(received).ok();
485                })
486            })
487            .collect::<Vec<_>>();
488        drop(receiver);
489
490        sender.send(Priority::Medium, 7).unwrap();
491        std::thread::sleep(std::time::Duration::from_millis(50));
492        assert!(
493            done_receiver.try_recv().is_err(),
494            "receivers keep waiting while the sender is alive"
495        );
496
497        drop(sender);
498        let mut received = Vec::new();
499        for _ in 0..2 {
500            received.extend(
501                done_receiver
502                    .recv_timeout(std::time::Duration::from_secs(5))
503                    .expect("a waiting receiver should end once the sender is dropped"),
504            );
505        }
506        assert_eq!(received, [7]);
507        for worker in workers {
508            worker.join().expect("worker should exit cleanly");
509        }
510    }
511
512    #[test]
513    fn all_tasks_get_yielded() {
514        let (tx, rx) = PriorityQueueReceiver::new();
515        tx.send(Priority::Medium, 20).unwrap();
516        tx.send(Priority::High, 30).unwrap();
517        tx.send(Priority::Low, 10).unwrap();
518        tx.send(Priority::Medium, 21).unwrap();
519        tx.send(Priority::High, 31).unwrap();
520
521        drop(tx);
522
523        assert_eq!(
524            rx.iter().collect::<HashSet<_>>(),
525            [30, 31, 20, 21, 10].into_iter().collect::<HashSet<_>>()
526        )
527    }
528
529    #[test]
530    fn new_high_prio_task_get_scheduled_quickly() {
531        let (tx, mut rx) = PriorityQueueReceiver::new();
532        for _ in 0..100 {
533            tx.send(Priority::Low, 1).unwrap();
534        }
535
536        assert_eq!(rx.pop().unwrap(), 1);
537        tx.send(Priority::High, 3).unwrap();
538        assert_eq!(rx.pop().unwrap(), 3);
539        assert_eq!(rx.pop().unwrap(), 1);
540    }
541}