├── .gitignore ├── .travis.yml ├── test-build.sh ├── Cargo.toml ├── sync-readme.py ├── src ├── atomic.rs ├── unlimited.rs ├── lib.rs ├── limited.rs └── resizable.rs ├── LICENSE-MIT ├── CHANGELOG.md ├── tests ├── unlimited.rs ├── limited.rs └── resizable.rs ├── README.md └── LICENSE-APACHE /.gitignore: -------------------------------------------------------------------------------- 1 | .vscode 2 | /target 3 | **/*.rs.bk 4 | Cargo.lock 5 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: rust 2 | rust: 3 | - stable 4 | - beta 5 | - nightly 6 | jobs: 7 | allow_failures: 8 | - rust: nightly 9 | fast_finish: true 10 | -------------------------------------------------------------------------------- /test-build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -xe 4 | 5 | cargo build 6 | 7 | cargo build --no-default-features --features unlimited 8 | cargo build --no-default-features --features resizable 9 | cargo build --no-default-features --features limited 10 | 11 | cargo test --no-default-features --features unlimited 12 | cargo test --no-default-features --features resizable 13 | cargo test --no-default-features --features limited 14 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "deadqueue" 3 | version = "0.2.5" 4 | authors = ["Michael P. Jung "] 5 | description = "Dead simple async queue" 6 | keywords = ["async", "queue"] 7 | license = "MIT/Apache-2.0" 8 | repository = "https://github.com/deadpool-rs/deadqueue" 9 | readme = "README.md" 10 | edition = "2018" 11 | 12 | [package.metadata.docs.rs] 13 | all-features = true 14 | 15 | [dependencies] 16 | tokio = { version = "1.13", features = ["sync", "macros"] } 17 | crossbeam-queue = "0.3" 18 | 19 | [dev-dependencies] 20 | tokio = { version = "1.13", features = ["sync", "macros", "rt-multi-thread", "time"] } 21 | 22 | [features] 23 | default = ["unlimited", "resizable", "limited"] 24 | unlimited = [] 25 | resizable = ["unlimited"] 26 | limited = [] 27 | -------------------------------------------------------------------------------- /sync-readme.py: -------------------------------------------------------------------------------- 1 | #!/bin/env python 2 | 3 | import os 4 | import itertools 5 | 6 | CRATES = [ 7 | ['README.md', 'src/lib.rs'], 8 | ] 9 | 10 | if __name__ == '__main__': 11 | for readme_filename, src_filename in CRATES: 12 | with open(readme_filename) as fh: 13 | readme = fh.readlines() 14 | with open(src_filename) as fh: 15 | it = iter(fh.readlines()) 16 | for line in it: 17 | if not line.startswith("//!"): 18 | break 19 | code = ''.join(itertools.chain([line], it)) 20 | with open(src_filename + '.new', 'w') as fh: 21 | for line in readme: 22 | if line.rstrip(): 23 | fh.write('//! ') 24 | else: 25 | fh.write('//!') 26 | fh.write(line) 27 | fh.write(code) 28 | os.rename(src_filename + '.new', src_filename) 29 | -------------------------------------------------------------------------------- /src/atomic.rs: -------------------------------------------------------------------------------- 1 | use std::sync::atomic::{AtomicIsize, Ordering}; 2 | 3 | #[derive(Debug)] 4 | pub struct Available(AtomicIsize); 5 | 6 | impl Available { 7 | pub fn new(value: isize) -> Self { 8 | Self(AtomicIsize::new(value)) 9 | } 10 | pub fn sub(&self) -> (TransactionSub<'_>, isize) { 11 | let new_len = self.0.fetch_sub(1, Ordering::Relaxed) - 1; 12 | (TransactionSub(&self.0), new_len) 13 | } 14 | pub fn add(&self) -> isize { 15 | self.0.fetch_add(1, Ordering::Relaxed) + 1 16 | } 17 | pub fn get(&self) -> isize { 18 | self.0.load(Ordering::Relaxed) 19 | } 20 | } 21 | 22 | #[must_use] 23 | pub struct TransactionSub<'a>(&'a AtomicIsize); 24 | 25 | impl TransactionSub<'_> { 26 | pub fn commit(self) { 27 | std::mem::forget(self); 28 | } 29 | } 30 | 31 | impl Drop for TransactionSub<'_> { 32 | fn drop(&mut self) { 33 | self.0.fetch_add(1, Ordering::Relaxed); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2019 Michael P. Jung 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | 22 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 6 | 7 | ## [Unreleased] 8 | 9 | ## [0.2.5] - 2025-07-16 10 | 11 | ### Added 12 | 13 | - Added `Queue::wait_full` and `Queue::wait_empty` async methods 14 | - Update `tokio` dependency to version 1.13 15 | 16 | ## [0.2.4] - 2022-11-4 17 | 18 | ### Fixed 19 | 20 | - Updated `tokio` required features to include `macros` 21 | 22 | ## [0.2.3] - 2022-06-05 23 | 24 | ### Added 25 | 26 | - Added `is_full` to limited and resizable queues 27 | 28 | ## [0.2.2] - 2022-05-01 29 | 30 | ### Added 31 | 32 | - Fix `resize` implementation of resizable queue 33 | 34 | ## [0.2.1] - 2022-03-11 35 | 36 | ### Added 37 | 38 | - Implement `Debug` for all queues 39 | - Add `Queue::is_empty` method 40 | 41 | ## [0.2.0] - 2020-12-26 42 | 43 | ### Changed 44 | 45 | - Update `tokio` dependency to version 1 46 | - Update `crossbeam-queue` dependency to version 0.3 47 | 48 | ## [0.1.0] - 2020-01-21 49 | 50 | ### Added 51 | 52 | - First release 53 | 54 | [unreleased]: https://github.com/deadpool-rs/deadqueue/compare/v0.2.5...HEAD 55 | [0.2.5]: https://github.com/deadpool-rs/deadqueue/releases/tag/v0.2.5 56 | [0.2.4]: https://github.com/deadpool-rs/deadqueue/releases/tag/v0.2.4 57 | [0.2.3]: https://github.com/deadpool-rs/deadqueue/releases/tag/v0.2.3 58 | [0.2.2]: https://github.com/deadpool-rs/deadqueue/releases/tag/v0.2.2 59 | [0.2.1]: https://github.com/deadpool-rs/deadqueue/releases/tag/v0.2.1 60 | [0.2.0]: https://github.com/deadpool-rs/deadqueue/releases/tag/v0.2.0 61 | [0.1.0]: https://github.com/deadpool-rs/deadqueue/releases/tag/v0.1.0 62 | -------------------------------------------------------------------------------- /src/unlimited.rs: -------------------------------------------------------------------------------- 1 | //! Unlimited queue implementation 2 | 3 | use std::convert::TryInto; 4 | use std::fmt::Debug; 5 | use std::iter::FromIterator; 6 | 7 | use crossbeam_queue::SegQueue; 8 | use tokio::sync::Semaphore; 9 | 10 | use crate::atomic::Available; 11 | use crate::{Notifier, Receiver}; 12 | 13 | /// Queue that is unlimited in size. 14 | /// 15 | /// This queue implementation has the following characteristics: 16 | /// 17 | /// - Based on `crossbeam_queue::SegQueue` 18 | /// - Has unlimitied capacity and no back pressure on push 19 | /// - Enabled via the `unlimited` feature in your `Cargo.toml` 20 | pub struct Queue { 21 | queue: SegQueue, 22 | semaphore: Semaphore, 23 | available: Available, 24 | notifier_empty: Notifier, 25 | } 26 | 27 | impl Queue { 28 | /// Create new empty queue 29 | pub fn new() -> Self { 30 | Self::default() 31 | } 32 | 33 | /// Get an item from the queue. If the queue is currently empty 34 | /// this method blocks until an item is available. 35 | pub async fn pop(&self) -> T { 36 | let (txn, new_len) = self.available.sub(); 37 | let permit = self.semaphore.acquire().await.unwrap(); 38 | let item = self.queue.pop().unwrap(); 39 | txn.commit(); 40 | if new_len <= 0 { 41 | self.notify_empty(); 42 | } 43 | permit.forget(); 44 | item 45 | } 46 | /// Try to get an item from the queue. If the queue is currently 47 | /// empty return None instead. 48 | pub fn try_pop(&self) -> Option { 49 | let (txn, new_len) = self.available.sub(); 50 | let permit = self.semaphore.try_acquire().ok()?; 51 | let item = self.queue.pop().unwrap(); 52 | txn.commit(); 53 | if new_len <= 0 { 54 | self.notify_empty(); 55 | } 56 | permit.forget(); 57 | Some(item) 58 | } 59 | /// Push an item into the queue 60 | pub fn push(&self, item: T) { 61 | self.queue.push(item); 62 | self.semaphore.add_permits(1); 63 | self.available.add(); 64 | } 65 | /// Get current length of queue (number of items currently stored). 66 | pub fn len(&self) -> usize { 67 | self.queue.len() 68 | } 69 | /// Returns `true` if the queue is empty. 70 | pub fn is_empty(&self) -> bool { 71 | self.queue.is_empty() 72 | } 73 | /// Get available count. This is the difference between the current 74 | /// queue length and the number of tasks waiting for an item of the 75 | /// queue. 76 | pub fn available(&self) -> isize { 77 | self.available.get() 78 | } 79 | /// Notify any callers awaiting empty() 80 | fn notify_empty(&self) { 81 | self.notifier_empty.send_replace(()); 82 | } 83 | /// Await until the queue is empty. 84 | pub async fn wait_empty(&self) { 85 | if self.is_empty() { 86 | return; 87 | } 88 | self.subscribe_empty().changed().await.unwrap(); 89 | } 90 | /// Get a `Receiver` object that can repeatedly be awaited for 91 | /// queue-empty notifications. 92 | pub fn subscribe_empty(&self) -> Receiver { 93 | self.notifier_empty.subscribe() 94 | } 95 | } 96 | 97 | impl Debug for Queue { 98 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 99 | f.debug_struct("Queue") 100 | .field("queue", &self.queue) 101 | .field("semaphore", &self.semaphore) 102 | .field("available", &self.available) 103 | .field("empty", &self.notifier_empty) 104 | .finish() 105 | } 106 | } 107 | 108 | impl Default for Queue { 109 | fn default() -> Self { 110 | Self { 111 | queue: SegQueue::new(), 112 | semaphore: Semaphore::new(0), 113 | available: Available::new(0), 114 | notifier_empty: crate::new_notifier(), 115 | } 116 | } 117 | } 118 | 119 | impl FromIterator for Queue { 120 | fn from_iter>(iter: I) -> Self { 121 | let queue = SegQueue::new(); 122 | for item in iter { 123 | queue.push(item); 124 | } 125 | let size = queue.len(); 126 | Self { 127 | queue, 128 | semaphore: Semaphore::new(size), 129 | available: Available::new(size.try_into().unwrap()), 130 | ..Self::default() 131 | } 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /tests/unlimited.rs: -------------------------------------------------------------------------------- 1 | #[cfg(feature = "unlimited")] 2 | mod tests { 3 | 4 | use std::iter::FromIterator; 5 | use std::sync::Arc; 6 | 7 | use deadqueue::unlimited::Queue; 8 | 9 | #[tokio::test] 10 | async fn test_basics() { 11 | let queue: Queue = Queue::from_iter(vec![1, 2, 3, 4, 5]); 12 | for _ in 0..5 { 13 | assert!(queue.try_pop().is_some()); 14 | } 15 | assert!(queue.try_pop().is_none()); 16 | } 17 | 18 | #[tokio::test] 19 | async fn test_available() { 20 | let queue: Queue = Queue::new(); 21 | assert_eq!(queue.len(), 0); 22 | queue.push(1); 23 | assert_eq!(queue.len(), 1); 24 | assert_eq!(queue.available(), 1); 25 | assert!(queue.try_pop().is_some()); 26 | assert_eq!(queue.len(), 0); 27 | assert_eq!(queue.available(), 0); 28 | } 29 | 30 | #[tokio::test] 31 | async fn test_pop() { 32 | let queue: Queue = Queue::from_iter(vec![1, 2, 3, 4, 5]); 33 | for i in 0..5 { 34 | assert_eq!(queue.len(), 5 - i); 35 | queue.pop().await; 36 | } 37 | assert_eq!(queue.len(), 0); 38 | assert_eq!(queue.available(), 0); 39 | } 40 | 41 | #[tokio::test] 42 | async fn test_parallel() { 43 | let queue: Arc> = Arc::new(Queue::new()); 44 | let mut futures = Vec::new(); 45 | for i in 0..10000usize { 46 | queue.push(i); 47 | } 48 | assert_eq!(queue.len(), 10000); 49 | assert_eq!(queue.available(), 10000); 50 | for _ in 0..100usize { 51 | let queue = queue.clone(); 52 | futures.push(tokio::spawn(async move { 53 | for _ in 0..100usize { 54 | queue.pop().await; 55 | } 56 | })); 57 | } 58 | for future in futures { 59 | future.await.unwrap(); 60 | } 61 | assert_eq!(queue.len(), 0); 62 | assert_eq!(queue.available(), 0); 63 | } 64 | 65 | #[tokio::test] 66 | async fn test_parallel_available() { 67 | const N: usize = 2; 68 | let queue: Arc> = Arc::new(Queue::new()); 69 | let barrier = Arc::new(tokio::sync::Barrier::new(N + 1)); 70 | let mut futures = Vec::new(); 71 | for _ in 0..N { 72 | let queue = queue.clone(); 73 | let barrier = barrier.clone(); 74 | futures.push(tokio::spawn(async move { 75 | barrier.wait().await; 76 | queue.pop().await; 77 | })); 78 | } 79 | barrier.wait().await; 80 | assert_eq!(queue.len(), 0); 81 | assert_eq!(queue.available(), -(N as isize)); 82 | for i in 0..N { 83 | queue.push(i); 84 | } 85 | for future in futures { 86 | future.await.unwrap(); 87 | } 88 | assert_eq!(queue.len(), 0); 89 | assert_eq!(queue.available(), 0); 90 | } 91 | 92 | #[tokio::test] 93 | async fn test_empty() { 94 | let queue: Arc> = Arc::new(Queue::new()); 95 | for i in 0..100 { 96 | queue.push(i); 97 | } 98 | let barrier = Arc::new(tokio::sync::Barrier::new(2)); 99 | let future_queue = queue.clone(); 100 | let future_barrier = barrier.clone(); 101 | let future = tokio::spawn(async move { 102 | future_barrier.wait().await; 103 | assert!(!future_queue.is_empty()); 104 | future_queue.wait_empty().await; 105 | }); 106 | // Slightly delay the pop operations 107 | // to ensure that the spawned task has 108 | // time to start 109 | barrier.wait().await; 110 | for _ in 0..100 { 111 | queue.pop().await; 112 | } 113 | future.await.unwrap(); 114 | assert_eq!(queue.len(), 0); 115 | } 116 | 117 | #[tokio::test] 118 | async fn test_empty_deadlock() { 119 | let queue: Arc> = Arc::new(Queue::new()); 120 | for _ in 0..2 { 121 | queue.push(()); 122 | } 123 | let barrier = Arc::new(tokio::sync::Barrier::new(2)); 124 | let future_queue = queue.clone(); 125 | let future_barrier = barrier.clone(); 126 | let future = tokio::spawn(async move { 127 | future_barrier.wait().await; 128 | assert!(!future_queue.is_empty()); 129 | future_queue.wait_empty().await; 130 | }); 131 | barrier.wait().await; 132 | queue.push(()); 133 | for _ in 0..3 { 134 | queue.pop().await; 135 | } 136 | future.await.unwrap(); 137 | assert_eq!(queue.len(), 0); 138 | } 139 | 140 | #[test] 141 | fn test_debug() { 142 | struct NoDebug {} 143 | let queue: Queue = Queue::new(); 144 | let _ = format!("{:?}", queue); 145 | } 146 | } 147 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Deadqueue [![Latest Version](https://img.shields.io/crates/v/deadqueue.svg)](https://crates.io/crates/deadqueue) [![Build Status](https://travis-ci.org/deadpool-rs/deadqueue.svg?branch=master)](https://travis-ci.org/deadpool-rs/deadqueue) 2 | 3 | Deadqueue is a dead simple async queue with back pressure support. 4 | 5 | This crate provides three implementations: 6 | 7 | - Unlimited (`deadqueue::unlimited::Queue`) 8 | - Based on `crossbeam_queue::SegQueue` 9 | - Has unlimitied capacity and no back pressure on push 10 | - Enabled via the `unlimited` feature in your `Cargo.toml` 11 | 12 | - Resizable (`deadqueue::resizable::Queue`) 13 | - Based on `deadqueue::unlimited::Queue` 14 | - Has limited capacity with back pressure on push 15 | - Supports resizing 16 | - Enabled via the `resizable` feature in your `Cargo.toml` 17 | 18 | - Limited (`deadqueue::limited::Queue`) 19 | - Based on `crossbeam_queue::ArrayQueue` 20 | - Has limit capacity with back pressure on push 21 | - Does not support resizing 22 | - Enabled via the `limited` feature in your `Cargo.toml` 23 | 24 | ## Features 25 | 26 | | Feature | Description | Extra dependencies | Default | 27 | | ------- | ----------- | ------------------ | ------- | 28 | | `unlimited` | Enable unlimited queue implementation | – | yes | 29 | | `resizable` | Enable resizable queue implementation | `deadqueue/unlimited` | yes | 30 | | `limited` | Enable limited queue implementation | – | yes | 31 | 32 | ## Example 33 | 34 | ```rust 35 | use std::sync::Arc; 36 | use tokio::time::{sleep, Duration}; 37 | 38 | const TASK_COUNT: usize = 1000; 39 | const WORKER_COUNT: usize = 10; 40 | 41 | type TaskQueue = deadqueue::limited::Queue; 42 | 43 | #[tokio::main] 44 | async fn main() { 45 | let queue = Arc::new(TaskQueue::new(TASK_COUNT)); 46 | for i in 0..TASK_COUNT { 47 | queue.try_push(i).unwrap(); 48 | } 49 | for worker in 0..WORKER_COUNT { 50 | let queue = queue.clone(); 51 | tokio::spawn(async move { 52 | loop { 53 | let task = queue.pop().await; 54 | println!("worker[{}] processing task[{}] ...", worker, task); 55 | } 56 | }); 57 | } 58 | println!("Waiting for workers to finish..."); 59 | queue.wait_empty().await; 60 | println!("All tasks done. :-)"); 61 | } 62 | ``` 63 | 64 | ## Reasons for yet another queue 65 | 66 | Deadqueue is by no means the only queue implementation available. It does things a little different and provides features that other implementations are lacking: 67 | 68 | - **Resizable queue.** Usually you have to pick between `limited` and `unlimited` queues. This crate features a `resizable` Queue which can be resized as needed. This is probably a big **unique selling point** of this crate. 69 | 70 | - **Introspection support.** The methods `.len()`, `.capacity()` and `.available()` provide access the current state of the queue. 71 | 72 | - **Fair scheduling.** Tasks calling `pop` will receive items in a first-come-first-serve fashion. This is mainly due to the use of `tokio::sync::Semaphore` which is fair by nature. 73 | 74 | - **One struct, not two.** The channels of `tokio`, `async_std` and `futures-intrusive` split the queue in two structs (`Sender` and `Receiver`) which makes the usage sligthly more complicated. 75 | 76 | - **Bring your own `Arc`.** Since there is no separation between `Sender` and `Receiver` there is also no need for an internal `Arc`. (All implementations that split the channel into a `Sender` and `Receiver` need some kind of `Arc` internally.) 77 | 78 | - **Fully concurrent access.** No need to wrap the `Receiver` part in a `Mutex`. All methods support concurrent accesswithout the need for an additional synchronization primitive. 79 | 80 | - **Support for `try__` methods.** The methods `try_push` and `try_pop` can be used to access the queue from non-blocking synchroneous code. 81 | 82 | - **Support for detecting when the queue becomes empty or full**, using the `wait_empty`, `subscribe_empty`, `wait_full` and `subscribe_full` methods. 83 | 84 | ## Alternatives 85 | 86 | | Crate | Limitations | Documentation | 87 | | --- | --- | --- | 88 | | [`tokio`](https://crates.io/crates/tokio) | No resizable queue. No introspection support. Synchronization of `Receiver` needed. | [`tokio::sync::mpsc::channel`](https://docs.rs/tokio/latest/tokio/sync/mpsc/fn.channel.html), [`tokio::sync::mpsc::unbounded_channel`](https://docs.rs/tokio/latest/tokio/sync/mpsc/fn.unbounded_channel.html) | 89 | | [`async-std`](https://crates.io/crates/async-std) | No resizable or unlimited queue. No introspection support. No `try_send` or `try_recv` methods. | [`async_std::sync::channel`](https://docs.rs/async-std/latest/async_std/sync/fn.channel.html) | 90 | | [`futures`](https://crates.io/crates/futures) | No resizable queue. No introspection support. | [`futures::channel::mpsc::channel`](https://docs.rs/futures/0.3.1/futures/channel/mpsc/fn.channel.html), [`futures::channel::mpsc::unbounded`](https://docs.rs/futures/0.3.1/futures/channel/mpsc/fn.unbounded.html) | 91 | 92 | ## License 93 | 94 | Licensed under either of 95 | 96 | - Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or 97 | - MIT license ([LICENSE-MIT](LICENSE-MIT) or 98 | 99 | at your option. 100 | -------------------------------------------------------------------------------- /tests/limited.rs: -------------------------------------------------------------------------------- 1 | #[cfg(feature = "limited")] 2 | mod tests { 3 | 4 | use std::sync::Arc; 5 | 6 | use deadqueue::limited::Queue; 7 | 8 | #[tokio::test] 9 | async fn test_basics() { 10 | let queue: Queue = Queue::new(2); 11 | assert_eq!(queue.len(), 0); 12 | assert!(queue.try_push(1).is_ok()); 13 | assert_eq!(queue.len(), 1); 14 | assert!(queue.try_push(2).is_ok()); 15 | assert_eq!(queue.len(), 2); 16 | assert!(queue.try_push(3).is_err()); 17 | assert_eq!(queue.len(), 2); 18 | assert!(queue.try_pop().is_some()); 19 | assert_eq!(queue.len(), 1); 20 | assert!(queue.try_push(3).is_ok()); 21 | assert_eq!(queue.len(), 2); 22 | } 23 | 24 | #[tokio::test] 25 | async fn test_available() { 26 | let queue: Queue = Queue::new(2); 27 | assert_eq!(queue.len(), 0); 28 | assert!(queue.try_push(1).is_ok()); 29 | assert_eq!(queue.len(), 1); 30 | assert_eq!(queue.available(), 1); 31 | assert!(queue.try_pop().is_some()); 32 | assert_eq!(queue.len(), 0); 33 | assert_eq!(queue.available(), 0); 34 | } 35 | 36 | #[tokio::test] 37 | async fn test_parallel() { 38 | let queue: Arc> = Arc::new(Queue::new(100)); 39 | let mut futures = Vec::new(); 40 | for _ in 0..100usize { 41 | let queue = queue.clone(); 42 | futures.push(tokio::spawn(async move { 43 | for _ in 0..100usize { 44 | queue.pop().await; 45 | } 46 | })); 47 | } 48 | for i in 0..10000 { 49 | queue.push(i).await; 50 | } 51 | for future in futures { 52 | future.await.unwrap(); 53 | } 54 | assert_eq!(queue.len(), 0); 55 | } 56 | 57 | #[tokio::test] 58 | async fn test_parallel_available() { 59 | const N: usize = 2; 60 | let queue: Arc> = Arc::new(Queue::new(N)); 61 | let barrier = Arc::new(tokio::sync::Barrier::new(N + 1)); 62 | let mut futures = Vec::new(); 63 | for _ in 0..N { 64 | let queue = queue.clone(); 65 | let barrier = barrier.clone(); 66 | futures.push(tokio::spawn(async move { 67 | barrier.wait().await; 68 | queue.pop().await; 69 | })); 70 | } 71 | barrier.wait().await; 72 | assert_eq!(queue.len(), 0); 73 | assert_eq!(queue.available(), -(N as isize)); 74 | for i in 0..N { 75 | queue.push(i).await; 76 | } 77 | for future in futures { 78 | future.await.unwrap(); 79 | } 80 | assert_eq!(queue.len(), 0); 81 | assert_eq!(queue.available(), 0); 82 | } 83 | 84 | #[tokio::test] 85 | async fn test_full() { 86 | let queue: Arc> = Arc::new(Queue::new(100)); 87 | let barrier = Arc::new(tokio::sync::Barrier::new(2)); 88 | let future_queue = queue.clone(); 89 | let future_barrier = barrier.clone(); 90 | let future = tokio::spawn(async move { 91 | future_barrier.wait().await; 92 | assert_ne!(future_queue.capacity(), future_queue.len()); 93 | future_queue.wait_full().await; 94 | }); 95 | barrier.wait().await; 96 | for i in 0..100 { 97 | queue.push(i).await; 98 | } 99 | future.await.unwrap(); 100 | assert_eq!(queue.len(), 100); 101 | } 102 | 103 | #[tokio::test] 104 | async fn test_empty() { 105 | let queue: Arc> = Arc::new(Queue::new(100)); 106 | for i in 0..100 { 107 | queue.push(i).await; 108 | } 109 | let barrier = Arc::new(tokio::sync::Barrier::new(2)); 110 | let future_queue = queue.clone(); 111 | let future_barrier = barrier.clone(); 112 | let future = tokio::spawn(async move { 113 | future_barrier.wait().await; 114 | assert!(!future_queue.is_empty()); 115 | future_queue.wait_empty().await; 116 | }); 117 | barrier.wait().await; 118 | for _ in 0..100 { 119 | queue.pop().await; 120 | } 121 | future.await.unwrap(); 122 | assert_eq!(queue.len(), 0); 123 | } 124 | 125 | #[tokio::test] 126 | async fn test_full_deadlock() { 127 | let queue: Arc> = Arc::new(Queue::new(2)); 128 | let barrier = Arc::new(tokio::sync::Barrier::new(2)); 129 | let future_queue = queue.clone(); 130 | let future_barrier = barrier.clone(); 131 | let future = tokio::spawn(async move { 132 | future_barrier.wait().await; 133 | assert_ne!(future_queue.capacity(), future_queue.len()); 134 | future_queue.wait_full().await; 135 | }); 136 | barrier.wait().await; 137 | queue.push(()).await; 138 | queue.pop().await; 139 | for _ in 0..2 { 140 | queue.push(()).await; 141 | } 142 | future.await.unwrap(); 143 | assert_eq!(queue.len(), 2); 144 | } 145 | 146 | #[tokio::test] 147 | async fn test_empty_deadlock() { 148 | let queue: Arc> = Arc::new(Queue::new(3)); 149 | for _ in 0..2 { 150 | queue.push(()).await; 151 | } 152 | let barrier = Arc::new(tokio::sync::Barrier::new(2)); 153 | let future_queue = queue.clone(); 154 | let future_barrier = barrier.clone(); 155 | let future = tokio::spawn(async move { 156 | future_barrier.wait().await; 157 | assert!(!future_queue.is_empty()); 158 | future_queue.wait_empty().await; 159 | }); 160 | barrier.wait().await; 161 | queue.push(()).await; 162 | for _ in 0..3 { 163 | queue.pop().await; 164 | } 165 | future.await.unwrap(); 166 | assert_eq!(queue.len(), 0); 167 | } 168 | 169 | #[test] 170 | fn test_debug() { 171 | struct NoDebug {} 172 | let queue: Queue = Queue::new(1); 173 | let _ = format!("{:?}", queue); 174 | } 175 | } 176 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! # Deadqueue [![Latest Version](https://img.shields.io/crates/v/deadqueue.svg)](https://crates.io/crates/deadqueue) [![Build Status](https://travis-ci.org/deadpool-rs/deadqueue.svg?branch=master)](https://travis-ci.org/deadpool-rs/deadqueue) 2 | //! 3 | //! Deadqueue is a dead simple async queue with back pressure support. 4 | //! 5 | //! This crate provides three implementations: 6 | //! 7 | //! - Unlimited (`deadqueue::unlimited::Queue`) 8 | //! - Based on `crossbeam_queue::SegQueue` 9 | //! - Has unlimitied capacity and no back pressure on push 10 | //! - Enabled via the `unlimited` feature in your `Cargo.toml` 11 | //! 12 | //! - Resizable (`deadqueue::resizable::Queue`) 13 | //! - Based on `deadqueue::unlimited::Queue` 14 | //! - Has limited capacity with back pressure on push 15 | //! - Supports resizing 16 | //! - Enabled via the `resizable` feature in your `Cargo.toml` 17 | //! 18 | //! - Limited (`deadqueue::limited::Queue`) 19 | //! - Based on `crossbeam_queue::ArrayQueue` 20 | //! - Has limit capacity with back pressure on push 21 | //! - Does not support resizing 22 | //! - Enabled via the `limited` feature in your `Cargo.toml` 23 | //! 24 | //! ## Features 25 | //! 26 | //! | Feature | Description | Extra dependencies | Default | 27 | //! | ------- | ----------- | ------------------ | ------- | 28 | //! | `unlimited` | Enable unlimited queue implementation | – | yes | 29 | //! | `resizable` | Enable resizable queue implementation | `deadqueue/unlimited` | yes | 30 | //! | `limited` | Enable limited queue implementation | – | yes | 31 | //! 32 | //! ## Example 33 | //! 34 | //! ```rust 35 | //! use std::sync::Arc; 36 | //! use tokio::time::{sleep, Duration}; 37 | //! 38 | //! const TASK_COUNT: usize = 1000; 39 | //! const WORKER_COUNT: usize = 10; 40 | //! 41 | //! type TaskQueue = deadqueue::limited::Queue; 42 | //! 43 | //! #[tokio::main] 44 | //! async fn main() { 45 | //! let queue = Arc::new(TaskQueue::new(TASK_COUNT)); 46 | //! for i in 0..TASK_COUNT { 47 | //! queue.try_push(i).unwrap(); 48 | //! } 49 | //! for worker in 0..WORKER_COUNT { 50 | //! let queue = queue.clone(); 51 | //! tokio::spawn(async move { 52 | //! loop { 53 | //! let task = queue.pop().await; 54 | //! println!("worker[{}] processing task[{}] ...", worker, task); 55 | //! } 56 | //! }); 57 | //! } 58 | //! println!("Waiting for workers to finish..."); 59 | //! queue.wait_empty().await; 60 | //! println!("All tasks done. :-)"); 61 | //! } 62 | //! ``` 63 | //! 64 | //! ## Reasons for yet another queue 65 | //! 66 | //! Deadqueue is by no means the only queue implementation available. It does things a little different and provides features that other implementations are lacking: 67 | //! 68 | //! - **Resizable queue.** Usually you have to pick between `limited` and `unlimited` queues. This crate features a `resizable` Queue which can be resized as needed. This is probably a big **unique selling point** of this crate. 69 | //! 70 | //! - **Introspection support.** The methods `.len()`, `.capacity()` and `.available()` provide access the current state of the queue. 71 | //! 72 | //! - **Fair scheduling.** Tasks calling `pop` will receive items in a first-come-first-serve fashion. This is mainly due to the use of `tokio::sync::Semaphore` which is fair by nature. 73 | //! 74 | //! - **One struct, not two.** The channels of `tokio`, `async_std` and `futures-intrusive` split the queue in two structs (`Sender` and `Receiver`) which makes the usage sligthly more complicated. 75 | //! 76 | //! - **Bring your own `Arc`.** Since there is no separation between `Sender` and `Receiver` there is also no need for an internal `Arc`. (All implementations that split the channel into a `Sender` and `Receiver` need some kind of `Arc` internally.) 77 | //! 78 | //! - **Fully concurrent access.** No need to wrap the `Receiver` part in a `Mutex`. All methods support concurrent accesswithout the need for an additional synchronization primitive. 79 | //! 80 | //! - **Support for `try__` methods.** The methods `try_push` and `try_pop` can be used to access the queue from non-blocking synchroneous code. 81 | //! 82 | //! - **Support for detecting when the queue becomes empty or full**, using the `wait_empty`, `subscribe_empty`, `wait_full` and `subscribe_full` methods. 83 | //! 84 | //! ## Alternatives 85 | //! 86 | //! | Crate | Limitations | Documentation | 87 | //! | --- | --- | --- | 88 | //! | [`tokio`](https://crates.io/crates/tokio) | No resizable queue. No introspection support. Synchronization of `Receiver` needed. | [`tokio::sync::mpsc::channel`](https://docs.rs/tokio/latest/tokio/sync/mpsc/fn.channel.html), [`tokio::sync::mpsc::unbounded_channel`](https://docs.rs/tokio/latest/tokio/sync/mpsc/fn.unbounded_channel.html) | 89 | //! | [`async-std`](https://crates.io/crates/async-std) | No resizable or unlimited queue. No introspection support. No `try_send` or `try_recv` methods. | [`async_std::sync::channel`](https://docs.rs/async-std/latest/async_std/sync/fn.channel.html) | 90 | //! | [`futures`](https://crates.io/crates/futures) | No resizable queue. No introspection support. | [`futures::channel::mpsc::channel`](https://docs.rs/futures/0.3.1/futures/channel/mpsc/fn.channel.html), [`futures::channel::mpsc::unbounded`](https://docs.rs/futures/0.3.1/futures/channel/mpsc/fn.unbounded.html) | 91 | //! 92 | //! ## License 93 | //! 94 | //! Licensed under either of 95 | //! 96 | //! - Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or 97 | //! - MIT license ([LICENSE-MIT](LICENSE-MIT) or 98 | //! 99 | //! at your option. 100 | #![warn(missing_docs)] 101 | 102 | use tokio::sync::watch; 103 | 104 | mod atomic; 105 | 106 | #[cfg(feature = "unlimited")] 107 | pub mod unlimited; 108 | 109 | #[cfg(feature = "resizable")] 110 | pub mod resizable; 111 | 112 | #[cfg(feature = "limited")] 113 | pub mod limited; 114 | 115 | /// Private type alias for notify_full and notify_empty 116 | type Notifier = watch::Sender<()>; 117 | 118 | /// Public type alias for subscribe_full and subscribe_empty 119 | pub type Receiver = watch::Receiver<()>; 120 | 121 | /// Initialize the notify_full sender 122 | fn new_notifier() -> Notifier { 123 | let (sender, _) = watch::channel(()); 124 | sender 125 | } 126 | -------------------------------------------------------------------------------- /src/limited.rs: -------------------------------------------------------------------------------- 1 | //! Limited queue implementation 2 | //! 3 | 4 | use std::{convert::TryInto, fmt::Debug}; 5 | 6 | use crossbeam_queue::ArrayQueue; 7 | use tokio::sync::Semaphore; 8 | 9 | use crate::atomic::Available; 10 | use crate::{Notifier, Receiver}; 11 | 12 | /// Queue that is limited in size and does not support resizing. 13 | /// 14 | /// This queue implementation has the following characteristics: 15 | /// 16 | /// - Based on `crossbeam_queue::ArrayQueue` 17 | /// - Has limit capacity with back pressure on push 18 | /// - Does not support resizing 19 | /// - Enabled via the `limited` feature in your `Cargo.toml` 20 | pub struct Queue { 21 | queue: ArrayQueue, 22 | push_semaphore: Semaphore, 23 | pop_semaphore: Semaphore, 24 | available: Available, 25 | notifier_full: Notifier, 26 | notifier_empty: Notifier, 27 | } 28 | 29 | impl Debug for Queue { 30 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 31 | f.debug_struct("Queue") 32 | .field("queue", &self.queue) 33 | .field("push_semaphore", &self.push_semaphore) 34 | .field("pop_semaphore", &self.pop_semaphore) 35 | .field("available", &self.available) 36 | .finish() 37 | } 38 | } 39 | 40 | impl Queue { 41 | /// Create new empty queue 42 | pub fn new(max_size: usize) -> Self { 43 | Self { 44 | queue: ArrayQueue::new(max_size), 45 | push_semaphore: Semaphore::new(max_size), 46 | pop_semaphore: Semaphore::new(0), 47 | available: Available::new(0), 48 | notifier_full: crate::new_notifier(), 49 | notifier_empty: crate::new_notifier(), 50 | } 51 | } 52 | /// Get an item from the queue. If the queue is currently empty 53 | /// this method blocks until an item is available. 54 | pub async fn pop(&self) -> T { 55 | let (txn, new_len) = self.available.sub(); 56 | let permit = self.pop_semaphore.acquire().await.unwrap(); 57 | let item = self.queue.pop().unwrap(); 58 | txn.commit(); 59 | if new_len <= 0 { 60 | self.notify_empty(); 61 | } 62 | permit.forget(); 63 | self.push_semaphore.add_permits(1); 64 | item 65 | } 66 | /// Try to get an item from the queue. If the queue is currently 67 | /// empty return None instead. 68 | pub fn try_pop(&self) -> Option { 69 | let (txn, new_len) = self.available.sub(); 70 | let permit = self.pop_semaphore.try_acquire().ok()?; 71 | let item = Some(self.queue.pop().unwrap()); 72 | txn.commit(); 73 | if new_len <= 0 { 74 | self.notify_empty(); 75 | } 76 | permit.forget(); 77 | self.push_semaphore.add_permits(1); 78 | item 79 | } 80 | /// Push an item into the queue 81 | pub async fn push(&self, item: T) { 82 | let permit = self.push_semaphore.acquire().await.unwrap(); 83 | let new_len = self.available.add(); 84 | self.queue.push(item).ok().unwrap(); 85 | if new_len >= self.queue.capacity().try_into().unwrap() { 86 | self.notify_full(); 87 | } 88 | permit.forget(); 89 | self.pop_semaphore.add_permits(1); 90 | } 91 | /// Try to push an item into the queue. If the queue is full 92 | /// the item is returned as `Err`. 93 | pub fn try_push(&self, item: T) -> Result<(), T> { 94 | match self.push_semaphore.try_acquire() { 95 | Ok(permit) => { 96 | let new_len = self.available.add(); 97 | self.queue.push(item).ok().unwrap(); 98 | if new_len >= self.queue.capacity().try_into().unwrap() { 99 | self.notify_full(); 100 | } 101 | permit.forget(); 102 | self.pop_semaphore.add_permits(1); 103 | Ok(()) 104 | } 105 | Err(_) => Err(item), 106 | } 107 | } 108 | /// Get capacity of the queue (maximum number of items queue can store) 109 | pub fn capacity(&self) -> usize { 110 | self.queue.capacity() 111 | } 112 | /// Get current length of queue (number of items currently stored) 113 | pub fn len(&self) -> usize { 114 | self.queue.len() 115 | } 116 | /// Returns `true` if the queue is empty. 117 | pub fn is_empty(&self) -> bool { 118 | self.queue.is_empty() 119 | } 120 | /// Returns `true` if the queue is full. 121 | pub fn is_full(&self) -> bool { 122 | self.queue.is_full() 123 | } 124 | /// The number of available items in the queue. If there are no 125 | /// items in the queue this number can become negative and stores the 126 | /// number of futures waiting for an item. 127 | pub fn available(&self) -> isize { 128 | self.available.get() 129 | } 130 | /// Check if the queue is full and notify any waiters 131 | fn notify_full(&self) { 132 | self.notifier_full.send_replace(()); 133 | } 134 | /// Await until the queue is full. 135 | pub async fn wait_full(&self) { 136 | if self.len() == self.capacity() { 137 | return; 138 | } 139 | self.subscribe_full().changed().await.unwrap(); 140 | } 141 | /// Get a `Receiver` object that can repeatedly be awaited for 142 | /// queue-full notifications. 143 | pub fn subscribe_full(&self) -> Receiver { 144 | self.notifier_full.subscribe() 145 | } 146 | /// Check if the queue is empty and notify any waiters 147 | fn notify_empty(&self) { 148 | self.notifier_empty.send_replace(()); 149 | } 150 | /// Await until the queue is empty. 151 | pub async fn wait_empty(&self) { 152 | if self.is_empty() { 153 | return; 154 | } 155 | self.subscribe_empty().changed().await.unwrap(); 156 | } 157 | /// Get a `Receiver` object that can repeatedly be awaited for 158 | /// queue-empty notifications. 159 | pub fn subscribe_empty(&self) -> Receiver { 160 | self.notifier_empty.subscribe() 161 | } 162 | } 163 | 164 | impl From for Queue 165 | where 166 | I: IntoIterator, 167 | ::IntoIter: ExactSizeIterator, 168 | { 169 | /// Create new queue from the given exact size iterator of objects. 170 | fn from(iter: I) -> Self { 171 | let iter = iter.into_iter(); 172 | let size = iter.len(); 173 | let queue = ArrayQueue::new(size); 174 | for obj in iter { 175 | queue.push(obj).ok().unwrap(); 176 | } 177 | Queue { 178 | queue: ArrayQueue::new(size), 179 | push_semaphore: Semaphore::new(0), 180 | pop_semaphore: Semaphore::new(size), 181 | available: Available::new(size.try_into().unwrap()), 182 | notifier_full: crate::new_notifier(), 183 | notifier_empty: crate::new_notifier(), 184 | } 185 | } 186 | } 187 | -------------------------------------------------------------------------------- /tests/resizable.rs: -------------------------------------------------------------------------------- 1 | #[cfg(feature = "resizable")] 2 | mod tests { 3 | 4 | use std::sync::Arc; 5 | 6 | use deadqueue::resizable::Queue; 7 | 8 | #[tokio::test] 9 | async fn test_basics() { 10 | let queue: Queue = Queue::new(2); 11 | assert_eq!(queue.len(), 0); 12 | assert!(queue.try_push(1).is_ok()); 13 | assert_eq!(queue.len(), 1); 14 | assert!(queue.try_push(2).is_ok()); 15 | assert_eq!(queue.len(), 2); 16 | assert!(queue.try_push(3).is_err()); 17 | assert_eq!(queue.len(), 2); 18 | assert!(queue.try_pop().is_some()); 19 | assert_eq!(queue.len(), 1); 20 | assert!(queue.try_push(3).is_ok()); 21 | assert_eq!(queue.len(), 2); 22 | } 23 | 24 | #[tokio::test] 25 | async fn test_available() { 26 | let queue: Queue = Queue::new(2); 27 | assert_eq!(queue.len(), 0); 28 | assert!(queue.try_push(1).is_ok()); 29 | assert_eq!(queue.len(), 1); 30 | assert_eq!(queue.available(), 1); 31 | assert!(queue.try_pop().is_some()); 32 | assert_eq!(queue.len(), 0); 33 | assert_eq!(queue.available(), 0); 34 | } 35 | 36 | #[tokio::test] 37 | async fn test_parallel() { 38 | let queue: Arc> = Arc::new(Queue::new(10)); 39 | let mut futures = Vec::new(); 40 | for _ in 0..100usize { 41 | let queue = queue.clone(); 42 | futures.push(tokio::spawn(async move { 43 | for _ in 0..100usize { 44 | queue.pop().await; 45 | } 46 | })); 47 | } 48 | for _ in 0..100usize { 49 | let queue = queue.clone(); 50 | futures.push(tokio::spawn(async move { 51 | for i in 0..100usize { 52 | queue.push(i).await; 53 | } 54 | })); 55 | } 56 | for future in futures { 57 | future.await.unwrap(); 58 | } 59 | assert_eq!(queue.len(), 0); 60 | } 61 | 62 | #[tokio::test] 63 | async fn test_parallel_available() { 64 | const N: usize = 2; 65 | let queue: Arc> = Arc::new(Queue::new(N)); 66 | let barrier = Arc::new(tokio::sync::Barrier::new(N + 1)); 67 | let mut futures = Vec::new(); 68 | for _ in 0..N { 69 | let queue = queue.clone(); 70 | let barrier = barrier.clone(); 71 | futures.push(tokio::spawn(async move { 72 | barrier.wait().await; 73 | queue.pop().await; 74 | })); 75 | } 76 | barrier.wait().await; 77 | assert_eq!(queue.len(), 0); 78 | assert_eq!(queue.available(), -(N as isize)); 79 | for i in 0..N { 80 | queue.push(i).await; 81 | } 82 | for future in futures { 83 | future.await.unwrap(); 84 | } 85 | assert_eq!(queue.len(), 0); 86 | assert_eq!(queue.available(), 0); 87 | } 88 | 89 | #[tokio::test] 90 | async fn test_full() { 91 | let queue: Arc> = Arc::new(Queue::new(100)); 92 | let barrier = Arc::new(tokio::sync::Barrier::new(2)); 93 | let future_queue = queue.clone(); 94 | let future_barrier = barrier.clone(); 95 | let future = tokio::spawn(async move { 96 | future_barrier.wait().await; 97 | assert_ne!(future_queue.capacity(), future_queue.len()); 98 | future_queue.wait_full().await; 99 | }); 100 | barrier.wait().await; 101 | for i in 0..100 { 102 | queue.push(i).await; 103 | } 104 | future.await.unwrap(); 105 | assert_eq!(queue.len(), 100); 106 | } 107 | 108 | #[tokio::test] 109 | async fn test_empty() { 110 | let queue: Arc> = Arc::new(Queue::new(100)); 111 | for i in 0..100 { 112 | queue.push(i).await; 113 | } 114 | let barrier = Arc::new(tokio::sync::Barrier::new(2)); 115 | let future_queue = queue.clone(); 116 | let future_barrier = barrier.clone(); 117 | let future = tokio::spawn(async move { 118 | future_barrier.wait().await; 119 | assert!(!future_queue.is_empty()); 120 | future_queue.wait_empty().await; 121 | }); 122 | barrier.wait().await; 123 | for _ in 0..100 { 124 | queue.pop().await; 125 | } 126 | future.await.unwrap(); 127 | assert_eq!(queue.len(), 0); 128 | } 129 | 130 | #[tokio::test] 131 | async fn test_full_deadlock() { 132 | let queue: Arc> = Arc::new(Queue::new(2)); 133 | let barrier = Arc::new(tokio::sync::Barrier::new(2)); 134 | let future_queue = queue.clone(); 135 | let future_barrier = barrier.clone(); 136 | let future = tokio::spawn(async move { 137 | future_barrier.wait().await; 138 | assert_ne!(future_queue.capacity(), future_queue.len()); 139 | println!("Awaiting full queue"); 140 | future_queue.wait_full().await; 141 | }); 142 | barrier.wait().await; 143 | println!("Begin queue operations"); 144 | queue.push(()).await; 145 | queue.pop().await; 146 | for _ in 0..2 { 147 | queue.push(()).await; 148 | } 149 | println!("End queue operations"); 150 | future.await.unwrap(); 151 | assert_eq!(queue.len(), 2); 152 | } 153 | 154 | #[tokio::test] 155 | async fn test_empty_deadlock() { 156 | let queue: Arc> = Arc::new(Queue::new(3)); 157 | for _ in 0..2 { 158 | queue.push(()).await; 159 | } 160 | let barrier = Arc::new(tokio::sync::Barrier::new(2)); 161 | let future_queue = queue.clone(); 162 | let future_barrier = barrier.clone(); 163 | let future = tokio::spawn(async move { 164 | future_barrier.wait().await; 165 | assert!(!future_queue.is_empty()); 166 | println!("Awaiting full queue"); 167 | future_queue.wait_empty().await; 168 | }); 169 | barrier.wait().await; 170 | println!("Begin queue operations"); 171 | queue.push(()).await; 172 | for _ in 0..3 { 173 | queue.pop().await; 174 | } 175 | println!("End queue operations"); 176 | future.await.unwrap(); 177 | assert_eq!(queue.len(), 0); 178 | } 179 | 180 | #[test] 181 | fn test_debug() { 182 | struct NoDebug {} 183 | let queue: Queue = Queue::new(1); 184 | let _ = format!("{:?}", queue); 185 | } 186 | 187 | #[tokio::test] 188 | async fn test_resize_enlarge() { 189 | let queue: Queue = Queue::new(0); 190 | queue.resize(1).await; 191 | assert_eq!(queue.capacity(), 1); 192 | } 193 | 194 | #[tokio::test] 195 | async fn test_resize_shrink() { 196 | let queue: Queue = Queue::new(2); 197 | queue.try_push(0).unwrap(); 198 | queue.resize(1).await; 199 | assert_eq!(queue.capacity(), 1); 200 | assert_eq!(queue.len(), 1); 201 | assert_eq!(queue.try_push(42), Err(42)); 202 | queue.resize(0).await; 203 | assert_eq!(queue.capacity(), 0); 204 | assert_eq!(queue.len(), 0); 205 | assert_eq!(queue.try_push(42), Err(42)); 206 | } 207 | 208 | #[tokio::test] 209 | async fn test_is_full_basic() { 210 | let queue: Queue = Queue::new(2); 211 | assert!(!queue.is_full(), "Should be empty at construction"); 212 | queue.push(1).await; 213 | assert!( 214 | !queue.is_full(), 215 | "Should not be full a one less than capacity" 216 | ); 217 | queue.push(2).await; 218 | assert!(queue.is_full(), "Should now be full"); 219 | let _ = queue.pop().await; 220 | assert!(!queue.is_full(), "Should no longer be full after pop"); 221 | } 222 | } 223 | -------------------------------------------------------------------------------- /src/resizable.rs: -------------------------------------------------------------------------------- 1 | //! Resizable queue implementation 2 | 3 | use std::convert::TryInto; 4 | use std::fmt::Debug; 5 | use std::iter::FromIterator; 6 | use std::sync::atomic::{AtomicUsize, Ordering}; 7 | 8 | use tokio::sync::{Mutex, Semaphore}; 9 | 10 | use crate::atomic::Available; 11 | use crate::unlimited::Queue as UnlimitedQueue; 12 | use crate::{Notifier, Receiver}; 13 | 14 | /// Queue that is limited in size and supports resizing. 15 | /// 16 | /// This queue implementation has the following characteristics: 17 | /// 18 | /// - Resizable (`deadqueue::resizable::Queue`) 19 | /// - Based on `deadqueue::unlimited::Queue` 20 | /// - Has limited capacity with back pressure on push 21 | /// - Supports resizing 22 | /// - Enabled via the `resizable` feature in your `Cargo.toml` 23 | pub struct Queue { 24 | queue: UnlimitedQueue, 25 | capacity: AtomicUsize, 26 | push_semaphore: Semaphore, 27 | available: Available, 28 | resize_mutex: Mutex<()>, 29 | notifier_full: Notifier, 30 | notifier_empty: Notifier, 31 | } 32 | 33 | impl Queue { 34 | /// Create new empty queue 35 | pub fn new(max_size: usize) -> Self { 36 | Self { 37 | queue: UnlimitedQueue::new(), 38 | capacity: AtomicUsize::new(max_size), 39 | push_semaphore: Semaphore::new(max_size), 40 | available: Available::new(0), 41 | resize_mutex: Mutex::default(), 42 | notifier_full: crate::new_notifier(), 43 | notifier_empty: crate::new_notifier(), 44 | } 45 | } 46 | /// Get an item from the queue. If the queue is currently empty 47 | /// this method blocks until an item is available. 48 | pub async fn pop(&self) -> T { 49 | let (txn, new_len) = self.available.sub(); 50 | let item = self.queue.pop().await; 51 | txn.commit(); 52 | if new_len <= 0 { 53 | self.notify_empty(); 54 | } 55 | self.push_semaphore.add_permits(1); 56 | item 57 | } 58 | /// Try to get an item from the queue. If the queue is currently 59 | /// empty return None instead. 60 | pub fn try_pop(&self) -> Option { 61 | let (txn, new_len) = self.available.sub(); 62 | let item = self.queue.try_pop(); 63 | if item.is_some() { 64 | txn.commit(); 65 | if new_len <= 0 { 66 | self.notify_empty(); 67 | } 68 | self.push_semaphore.add_permits(1); 69 | } 70 | item 71 | } 72 | /// Push an item into the queue 73 | pub async fn push(&self, item: T) { 74 | let permit = self.push_semaphore.acquire().await.unwrap(); 75 | let new_len = self.available.add(); 76 | self.queue.push(item); 77 | if new_len >= self.capacity().try_into().unwrap() { 78 | self.notify_full(); 79 | } 80 | permit.forget(); 81 | } 82 | /// Try to push an item to the queue. If the queue is currently 83 | /// full return the object as `Err`. 84 | pub fn try_push(&self, item: T) -> Result<(), T> { 85 | match self.push_semaphore.try_acquire() { 86 | Ok(permit) => { 87 | let new_len = self.available.add(); 88 | self.queue.push(item); 89 | if new_len >= self.capacity().try_into().unwrap() { 90 | self.notify_full(); 91 | } 92 | permit.forget(); 93 | Ok(()) 94 | } 95 | Err(_) => Err(item), 96 | } 97 | } 98 | /// Get capacity of the queue (maximum number of items queue can store). 99 | pub fn capacity(&self) -> usize { 100 | self.capacity.load(Ordering::Relaxed) 101 | } 102 | /// Get current length of queue (number of items currently stored). 103 | pub fn len(&self) -> usize { 104 | self.queue.len() 105 | } 106 | /// Returns `true` if the queue is empty. 107 | pub fn is_empty(&self) -> bool { 108 | self.queue.is_empty() 109 | } 110 | /// Returns `true` if the queue is full. 111 | /// **Note:** this can give an incorrect result if a simultaneous push/pop 112 | /// and resize ocurr while this function is executing. try_push() is the 113 | /// reccomended and safer mechanism in most circumstances. This method 114 | /// is provided as a convenience API. 115 | pub fn is_full(&self) -> bool { 116 | self.len() >= self.capacity() 117 | } 118 | /// The number of available items in the queue. If there are no 119 | /// items in the queue this number can become negative and stores the 120 | /// number of futures waiting for an item. 121 | pub fn available(&self) -> isize { 122 | self.queue.available() 123 | } 124 | /// Check if the queue is full and notify any waiters 125 | fn notify_full(&self) { 126 | self.notifier_full.send_replace(()); 127 | } 128 | /// Await until the queue is full. 129 | pub async fn wait_full(&self) { 130 | if self.len() == self.capacity() { 131 | return; 132 | } 133 | self.subscribe_full().changed().await.unwrap(); 134 | } 135 | /// Get a `Receiver` object that can repeatedly be awaited for 136 | /// queue-full notifications. 137 | pub fn subscribe_full(&self) -> Receiver { 138 | self.notifier_full.subscribe() 139 | } 140 | /// Check if the queue is empty and notify any waiters 141 | fn notify_empty(&self) { 142 | self.notifier_empty.send_replace(()); 143 | } 144 | /// Await until the queue is empty. 145 | pub async fn wait_empty(&self) { 146 | if self.is_empty() { 147 | return; 148 | } 149 | self.subscribe_empty().changed().await.unwrap(); 150 | } 151 | /// Get a `Receiver` object that can repeatedly be awaited for 152 | /// queue-empty notifications. 153 | pub fn subscribe_empty(&self) -> Receiver { 154 | self.notifier_empty.subscribe() 155 | } 156 | /// Resize queue. This increases or decreases the queue 157 | /// capacity accordingly. 158 | /// 159 | /// **Note:** Increasing the capacity of a queue happens without 160 | /// blocking unless a resize operation is already in progress. 161 | /// Decreasing the capacity can block if there are futures waiting to 162 | /// push items to the queue. 163 | pub async fn resize(&self, target_capacity: usize) { 164 | let _guard = self.resize_mutex.lock().await; 165 | match target_capacity.cmp(&self.capacity()) { 166 | std::cmp::Ordering::Greater => { 167 | let diff = target_capacity - self.capacity(); 168 | self.capacity.fetch_add(diff, Ordering::Relaxed); 169 | self.push_semaphore.add_permits(diff); 170 | } 171 | std::cmp::Ordering::Less => { 172 | // Shrinking the queue is a bit more involved 173 | // as there are two cases that need to be covered. 174 | for _ in target_capacity..self.capacity() { 175 | tokio::select! { 176 | biased; 177 | // If there are push permits available consume 178 | // them first making sure no new items are pushed 179 | // to the queue. 180 | push_permit = self.push_semaphore.acquire() => { 181 | push_permit.unwrap().forget(); 182 | } 183 | // If the queue contains more elements than the 184 | // target capacity those need to be removed from 185 | // the queue. 186 | // Important: Call `self.queue.pop` and not 187 | // `self.pop` as the former would add permits to 188 | // the `push_semaphore` which we don't want to 189 | // happen since the queue is being shrunk. 190 | _ = self.queue.pop() => {} 191 | }; 192 | self.capacity.fetch_sub(1, Ordering::Relaxed); 193 | } 194 | 195 | if self.is_full() { 196 | self.notify_full(); 197 | } 198 | } 199 | _ => {} 200 | } 201 | } 202 | } 203 | 204 | impl Debug for Queue { 205 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 206 | f.debug_struct("Queue") 207 | .field("queue", &self.queue) 208 | .field("capacity", &self.capacity) 209 | .field("push_semaphore", &self.push_semaphore) 210 | .field("resize_mutex", &self.resize_mutex) 211 | .finish() 212 | } 213 | } 214 | 215 | impl FromIterator for Queue { 216 | fn from_iter>(iter: I) -> Self { 217 | let queue = UnlimitedQueue::from_iter(iter); 218 | let len = queue.len(); 219 | Self { 220 | queue, 221 | capacity: len.into(), 222 | push_semaphore: Semaphore::new(0), 223 | available: Available::new(0), 224 | resize_mutex: Mutex::default(), 225 | notifier_full: crate::new_notifier(), 226 | notifier_empty: crate::new_notifier(), 227 | } 228 | } 229 | } 230 | -------------------------------------------------------------------------------- /LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright 2019 Michael P. Jung 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | 203 | --------------------------------------------------------------------------------