aqueue — Fast, Thread-Safe Async Execution Queue
aqueue provides three concurrency models for protecting shared state in async Rust,
each backed by a different locking primitive — pick the one that best fits your workload:
| Type | Primitive | Best for |
|---|---|---|
Actor<I> |
async_lock::Mutex |
Serial / write-heavy / stateful workloads |
RwModel<I> |
async_lock::RwLock |
Read-heavy workloads |
PCModel<I> |
async_lock::Semaphore |
Bounded parallelism / rate-limiting |
The low-level queue primitives (AQueue, RwQueue, SemaphoreQueue) are also exported for custom use cases. |
Table of Contents
Installation
[]
= "1.4"
# Optional: enable timeout macro support (tokio runtime)
# aqueue = { version = "1.4", features = ["tokio_time"] }
Feature Flags
| Flag | Description |
|---|---|
tokio_time |
Enables inner_wait!, call_wait!, call_mut_wait! backed by tokio timeouts |
async_std_time |
Same three macros backed by async-std timeouts |
Neither flag is enabled by default. The core library only depends on async-lock. |
Core Types
Actor<I>
Best for: write-heavy, exclusive-access, or stateful workloads.
Actor<I>wraps any valueIbehind an async Mutex queue (AQueue). All operations execute serially — only one closure runs at a time — without anyMutex<I>in user code. The closure parameter is&'a InnerStore<I>whose lifetime'ais tied to&'a self, guaranteeing the reference stays valid across every.awaitpoint inside the closure. Public API: | Method | Description | |--------|-------------| |Actor::new(x)| Wrapxin a newActor| |async fn inner_call(f)| Runfexclusively under the queue lock (FIFO) | |unsafe fn deref_inner()| Borrow the inner value directly, bypassing the lock |
RwModel<I>
Best for: read-heavy workloads with occasional exclusive mutations.
RwModel<I>is backed by an async RwLock queue (RwQueue):
callacquires a shared read lock — multiple readers run concurrently.call_mutacquires an exclusive write lock — no other reads or writes run concurrently. Synchronous variantssync_call/sync_mut_callare available for non-async call sites (they yield the thread viathread::yield_nowuntil the lock is free). Public API: | Method | Description | |--------|-------------| |RwModel::new(x)| Wrapxin a newRwModel| |async fn call(f)| Shared read lock — closure receivesRefInner<'a, I>| |async fn call_mut(f)| Exclusive write lock — closure receivesRefMutInner<'a, I>| |fn sync_call(f)| Synchronous read (yields thread until lock is free) | |fn sync_mut_call(f)| Synchronous write (yields thread until lock is free) |
PCModel<I>
Best for: capping the number of simultaneously executing tasks (e.g. HTTP connections, DB query concurrency).
PCModel<I>uses an async Semaphore (SemaphoreQueue) to enforce a maximum concurrency ofn. Callers that exceed the limit suspend asynchronously until a permit becomes available. Warning:PCModel::inner()returns a direct reference to the inner value without acquiring any semaphore permit. Calls made through that reference are not subject to the configured concurrency limit, silently defeating the purpose ofPCModel. Only useinner()for metadata that is safe to read without counting against the parallelism budget (e.g. configuration flags, addresses). Usecall()for everything else. Public API: | Method | Description | |--------|-------------| |PCModel::new(inner, n)| Create a model allowing at mostnconcurrent executions | |fn inner()| Direct reference — does not acquire a semaphore permit | |async fn call(f)| Acquire one permit, runf, release permit on completion |
Low-Level Queues
The following primitives are publicly exported for advanced use cases:
| Type | Primitive | Use case |
|---|---|---|
AQueue |
async_lock::Mutex |
Serial async execution |
RwQueue |
async_lock::RwLock |
Concurrent reads / exclusive writes |
SemaphoreQueue |
async_lock::Semaphore |
Bounded concurrency |
Timeout Macros
Enable tokio_time (or async_std_time) to get three timeout macros that wrap a call with a millisecond deadline, returning Err on expiry:
= { = "1.4", = ["tokio_time"] }
| Macro | Wraps |
|---|---|
inner_wait!(actor, ms, f) |
actor.inner_call(f) |
call_mut_wait!(model, ms, f) |
model.call_mut(f) |
call_wait!(model, ms, f) |
model.call(f) |
use ;
// Returns Err if the operation takes longer than 5 000 ms
let result = inner_wait!.await?;
Examples
Actor — Basic Counter
Four concurrent tasks accumulate into the same counter with zero data races:
use Actor;
use Arc;
use try_join;
async
Actor — SQLite Database
Wrapping a SQLite connection pool in an Actor serialises all writes so the auto-incrementing ID is always unique, even across 100 concurrent tasks:
use Result;
use Actor;
use ;
unsafe
unsafe
lazy_static!
async
RwModel — Read-Heavy Counter
call permits multiple simultaneous readers; call_mut takes an exclusive write lock:
use RwModel;
use Arc;
use try_join;
async
test rw a count:100000000 value:4999999950000000 time:5.18s qps:19,308,000
test rw b count:100000000 value:4999999950000000 time:5.29s qps:18,892,000
PCModel — Concurrency Limiter
At most 4 requests are in-flight at any time; the rest queue up automatically:
use PCModel;
use Arc;
;
async
Benchmarks
Run the built-in Criterion benchmarks:
cargo bench
Four scenarios are measured (100 000 iterations each):
| Benchmark ID | Description |
|---|---|
single_task_actor_call |
Serial Actor::inner_call on a single-thread runtime |
multi_task_actor_call |
Concurrent Actor::inner_call from 2 tasks (multi-thread runtime) |
single_task_model_call |
Serial RwModel::call_mut on a single-thread runtime |
multi_task_model_call |
Concurrent RwModel::call_mut from 2 tasks (multi-thread runtime) |
Architecture
aqueue
├── Actor<I> ──► AQueue (async_lock::Mutex) exclusive, serial
├── RwModel<I> ──► RwQueue (async_lock::RwLock) concurrent reads / exclusive writes
├── PCModel<I> ──► SemaphoreQueue (async_lock::Semaphore) bounded parallelism
│
├── InnerStore<T> raw value store (UnsafeCell<T>; safety enforced by the queue above)
├── RefInner<'a,T> shared-reference smart pointer (impl Deref)
└── RefMutInner<'a,T> mutable-reference smart pointer (impl Deref + DerefMut)
All models store their data in InnerStore<T> — an UnsafeCell<T> with manual Send + Sync impls.
Thread safety is enforced entirely by the surrounding async lock primitive, keeping the lock and the
value completely separate with zero additional wrapper overhead.
Changelog
See CHANGELOG.md for the full release history.
License
Licensed under either of
- Apache License, Version 2.0 (LICENSE or http://www.apache.org/licenses/LICENSE-2.0)
- MIT License (LICENSE or http://opensource.org/licenses/MIT) at your option.