Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion HISTORY.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

> Apache Asyncband (Incubating) is an effort undergoing incubation at the Apache Software Foundation (ASF), sponsored by the Apache Incubator PMC. Please read the [DISCLAIMER](DISCLAIMER).

Asyncband collects runtime-agnostic synchronization and coordination tools informed by several existing implementations. Only components that draw on external designs or code are listed here.
Asyncband collects composable, runtime-agnostic concurrency building blocks informed by several existing implementations. Only components that draw on external designs or code are listed here.

- `barrier::Barrier` is inspired by [`std::sync::Barrier`](https://doc.rust-lang.org/std/sync/struct.Barrier.html) and [`tokio::sync::Barrier`](https://docs.rs/tokio/latest/tokio/sync/struct.Barrier.html), with a different implementation based on the internal `WaitSet` primitive.
- The single-future polling loop in `blocking` is adapted from [`pollster`](https://github.com/zesterer/pollster), its parker caching strategy follows [`futures-lite`](https://github.com/smol-rs/futures-lite), and its private parker state machine is adapted from [`parking`](https://github.com/smol-rs/parking) 2.2.1.
Expand Down
73 changes: 42 additions & 31 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,18 +26,44 @@

## Overview

Asyncband is a runtime-agnostic library providing synchronization and coordination tools for asynchronous Rust programming. Its APIs work with any async runtime.
Asyncband is a focused collection of composable, runtime-agnostic concurrency building blocks for async Rust. It provides synchronization, initialization, task coordination, channels, resource reuse, and workload control without choosing an executor for the application.

## Available APIs
Asyncband's async APIs are built on standard futures and wakers. The library does not spawn tasks, own worker threads, install timers, or require a reactor or I/O driver. Applications can poll its futures with Tokio, async-std, smol, a custom executor, or any other standards-based runtime, and compose runtime services such as deadlines around them.

The crate enables no APIs by default. Categories describe each API's primary purpose and do not add another module level, so public paths remain concise, such as `asyncband::mutex`, `asyncband::pool`, and `asyncband::once::OnceCell`.
### Project scope

| Category | Primitive | Feature | Purpose |
The project is not limited to small or stateless synchronization primitives. Stateful utilities such as a singleflight group or an object pool fit when they provide a generally reusable coordination mechanism and remain independent of executor policy.

The boundary is mechanism versus policy. Task placement, timers, deadlines, retries, periodic maintenance, and application lifecycle orchestration stay with the caller and its runtime. Potential future-concurrency or scheduling APIs are evaluated against the same boundary: they must remain executor-independent and compose with caller-owned execution and timing.

## Getting started

The crate enables no APIs by default. Enable only the features your application uses:

```shell
cargo add asyncband --features mutex,oneshot
```

```rust
use asyncband::mutex::Mutex;

async fn increment() {
let counter = Mutex::new(0);
*counter.lock().await += 1;
assert_eq!(*counter.lock().await, 1);
}
```

Public paths stay direct—such as `asyncband::mutex`, `asyncband::pool`, and `asyncband::once::OnceCell`—while Cargo features keep unused implementations out of the build.

## API map

| Area | API | Feature | Use |
| ----------------------- | ------------------------------------------------------------------------------------ | -------------- | ----------------------------------------------------------------------- |
| Shared state | [`Mutex`](https://docs.rs/asyncband/*/asyncband/mutex/struct.Mutex.html) | `mutex` | Protect shared data with asynchronous mutual exclusion. |
| | [`RwLock`](https://docs.rs/asyncband/*/asyncband/rwlock/struct.RwLock.html) | `rwlock` | Allow multiple readers or one writer. |
| | [`Condvar`](https://docs.rs/asyncband/*/asyncband/condvar/struct.Condvar.html) | `condvar` | Wait for notifications while releasing a mutex. |
| One-time initialization | [`Once`](https://docs.rs/asyncband/*/asyncband/once/struct.Once.html) | `once` | Run asynchronous initialization exactly once. |
| Initialization | [`Once`](https://docs.rs/asyncband/*/asyncband/once/struct.Once.html) | `once` | Run asynchronous initialization exactly once. |
| | [`OnceCell`](https://docs.rs/asyncband/*/asyncband/once/struct.OnceCell.html) | `once-cell` | Initialize and store one asynchronous value. |
| | [`OnceMap`](https://docs.rs/asyncband/*/asyncband/once/struct.OnceMap.html) | `once-map` | Initialize and store one value per key. |
| Task coordination | [`Barrier`](https://docs.rs/asyncband/*/asyncband/barrier/struct.Barrier.html) | `barrier` | Wait until all participants reach a synchronization point. |
Expand All @@ -47,24 +73,15 @@ The crate enables no APIs by default. Categories describe each API's primary pur
| Channels | [`oneshot::channel`](https://docs.rs/asyncband/*/asyncband/oneshot/fn.channel.html) | `oneshot` | Send one value between two tasks. |
| | [`mpsc::bounded`](https://docs.rs/asyncband/*/asyncband/mpsc/fn.bounded.html) | `mpsc` | Send values from multiple producers through a bounded channel. |
| | [`mpsc::unbounded`](https://docs.rs/asyncband/*/asyncband/mpsc/fn.unbounded.html) | `mpsc` | Send values from multiple producers through an unbounded channel. |
| Resource reuse | [`pool::bounded`](https://docs.rs/asyncband/*/asyncband/pool/bounded/) | `pool` | Reuse managed objects up to a configured capacity. |
| | [`pool::unbounded`](https://docs.rs/asyncband/*/asyncband/pool/unbounded/) | `pool` | Reuse manually supplied or manager-created objects. |
| Workload control | [`Semaphore`](https://docs.rs/asyncband/*/asyncband/semaphore/struct.Semaphore.html) | `semaphore` | Control concurrent access with permits. |
| Resource reuse | [`pool::bounded`](https://docs.rs/asyncband/*/asyncband/pool/bounded/) | `pool` | Reuse managed objects up to a configured capacity. |
| | [`pool::unbounded`](https://docs.rs/asyncband/*/asyncband/pool/unbounded/) | `pool` | Reuse manually supplied or manager-created objects. |
| Workload coordination | [`Semaphore`](https://docs.rs/asyncband/*/asyncband/semaphore/struct.Semaphore.html) | `semaphore` | Control concurrent access with permits. |
| | [`Group`](https://docs.rs/asyncband/*/asyncband/singleflight/struct.Group.html) | `singleflight` | Coalesce concurrent calls for the same key. |

## Installation

Add the dependency to your `Cargo.toml` via:

```shell
cargo add asyncband --features mutex,oneshot
```

List every API your application uses in `features`; a bare `cargo add asyncband` intentionally exposes no optional modules.
| Synchronous interop | [`FutureExt`](https://docs.rs/asyncband/*/asyncband/blocking/trait.FutureExt.html) | `blocking` | Drive one runtime-agnostic future from a blocking thread. |

## Synchronous interoperability

The optional `blocking` module bridges synchronous Rust code to runtime-agnostic futures. It is an interoperability utility rather than another async primitive, so it is documented separately from the table above.
The optional `blocking` module is a boundary adapter for synchronous callers. It parks the calling thread while driving one future; it is not a general-purpose executor.

```shell
cargo add asyncband --features blocking
Expand All @@ -82,25 +99,19 @@ let value = async { 42 }.wait_timeout(Duration::ZERO);
assert_eq!(value, Some(42));
```

`asyncband::blocking::FutureExt::block_on(future)` is the equivalent UFCS spelling when function syntax is preferred; it calls the same trait method rather than a separate free function.

### Async first, blocking by adaptation

Async and synchronous synchronization primitives have different optimization constraints. Once an async primitive is runtime-agnostic, synchronous code can usually drive its future with a `block_on` adapter. Asyncband's `blocking` feature provides this adapter with a lightweight, thread-parking single-future executor: pending work parks the calling thread and its waker resumes it, providing practical blocking interoperability without busy-waiting or a full async runtime.
Async and synchronous synchronization primitives have different optimization constraints. Once an async operation is exposed as a runtime-agnostic future, synchronous code can usually drive that future through a `block_on` adapter. Asyncband therefore designs its primitives for async use and provides blocking interoperability at the boundary instead of duplicating synchronous methods across every type.

A sync-first implementation can still exploit OS- or platform-specific facilities for better performance. Asyncband therefore optimizes its primitives for async code and keeps blocking as a boundary adapter instead of duplicating sync and async methods across every type. This keeps the public API focused while leaving sync-oriented optimizations to dedicated libraries.
A sync-first implementation can exploit OS- or platform-specific facilities that an async implementation cannot assume. Libraries focused on synchronous code can therefore make different and sometimes better tradeoffs. Asyncband leaves those optimizations to dedicated libraries rather than treating blocking adaptation as a second family of primitives.

### Execution constraints

This is a minimal single-future executor, not a general-purpose async runtime. A timed-out `wait_timeout` drops the future. The implementation uses a private parker, so it does not consume wake-ups belonging to other parking operations on the same thread; recursive calls use a separate parker. Futures depending on a runtime-specific timer or I/O driver may not make progress, and blocking an executor thread can cause starvation or deadlocks. See [`asyncband::blocking`](https://docs.rs/asyncband/*/asyncband/blocking/index.html) for details.

## Runtime Agnostic

All asynchronous APIs in this library are runtime-agnostic, meaning they can be used with any async runtime like Tokio, async-std, or others. This makes the library highly versatile and portable.
The `blocking` module is a lightweight, thread-parking single-future executor, not a general-purpose async runtime. `wait_timeout` drops the future on timeout. Futures that depend on a runtime-specific timer or I/O driver still need that runtime's driver to make progress, and blocking an executor thread can cause starvation or deadlocks. See the [`blocking` module documentation](https://docs.rs/asyncband/*/asyncband/blocking/) for the full contract.

## Thread Safety
## Thread safety

Asyncband primitives and guards implement `Send` and `Sync` only when the protected or transferred value satisfies the necessary bounds. In particular, owned read guards that may move destruction to another thread require the protected value to be `Send` as well as `Sync`. See each type's documentation for its exact bounds.
Asyncband types implement `Send` and `Sync` only when the protected, transferred, or managed value satisfies the necessary bounds. See each API's documentation for its exact contract.

## Minimum Supported Rust Version (MSRV)

Expand All @@ -116,4 +127,4 @@ Apache Asyncband, Asyncband, and Apache are either registered trademarks or trad

## History

See [HISTORY.md](HISTORY.md) for the external implementations that informed Asyncband's primitives.
See [HISTORY.md](HISTORY.md) for the external implementations that informed Asyncband's APIs.
10 changes: 8 additions & 2 deletions asyncband/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,15 @@ name = "asyncband"
version = "0.6.7"

categories = ["asynchronous", "concurrency"]
description = "Runtime-agnostic synchronization and coordination tools for asynchronous Rust."
description = "Composable, runtime-agnostic concurrency building blocks for async Rust."
documentation = "https://docs.rs/asyncband"
keywords = ["async", "concurrency", "synchronization", "waitgroup", "mutex"]
keywords = [
"async",
"concurrency",
"synchronization",
"coordination",
"runtime-agnostic",
]

edition.workspace = true
homepage.workspace = true
Expand Down
40 changes: 26 additions & 14 deletions asyncband/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,10 @@
#![cfg_attr(docsrs, feature(doc_cfg))]
#![deny(missing_docs)]

//! Runtime-agnostic synchronization and coordination tools for asynchronous Rust.
//! Composable, runtime-agnostic concurrency building blocks for async Rust.
//!
//! `asyncband` provides locks, initialization tools, task coordination, channels, object pools, and
//! workload controls without tying an application to a particular async runtime. The APIs use
//! `asyncband` provides synchronization, initialization, task coordination, channels, resource
//! reuse, and workload control without choosing an executor for the application. Its async APIs use
//! standard futures and wakers, so they can run on Tokio, async-std, smol, or a custom executor.
//!
//! # Getting started
Expand All @@ -33,7 +33,7 @@
//! asyncband = { version = "0.7", features = ["mutex", "oneshot"] }
//! ```
//!
//! Then use the selected primitives directly:
//! Then use the selected APIs directly:
//!
//! ```
//! # #[tokio::main]
Expand All @@ -57,25 +57,37 @@
//! | Initialize values once | [`once::Once`], [`once::OnceCell`], [`once::OnceMap`] | `once`, `once-cell`, `once-map` |
//! | Coordinate tasks | [`barrier::Barrier`], [`latch::Latch`], [`waitgroup::WaitGroup`], [`shutdown`] | `barrier`, `latch`, `waitgroup`, `shutdown` |
//! | Send values | [`oneshot::channel`], [`mpsc::bounded`], [`mpsc::unbounded`] | `oneshot`, `mpsc` |
//! | Reuse managed objects | [`pool::bounded`], [`pool::unbounded`] | `pool` |
//! | Control workloads | [`semaphore::Semaphore`], [`singleflight::Group`] | `semaphore`, `singleflight` |
//! | Reuse objects | [`pool::bounded`], [`pool::unbounded`] | `pool` |
//! | Coordinate workloads | [`semaphore::Semaphore`], [`singleflight::Group`] | `semaphore`, `singleflight` |
//! | Wait from synchronous code | [`blocking::FutureExt`] | `blocking` |
//!
//! # Runtime and blocking model
//! # Scope and runtime model
//!
//! The async primitives do not start threads, spawn tasks, or require a runtime-specific reactor.
//! Await them inside any executor that polls standard Rust futures.
//! The project is not limited to small or stateless primitives. Stateful tools such as
//! [`singleflight::Group`] and the [`pool`] module fit when they provide reusable coordination and
//! remain independent of executor policy.
//!
//! Async APIs are the primary interface. The optional [`blocking`] module is a boundary adapter for
//! synchronous callers: its single-future executor parks the calling thread and resumes it through
//! the future's waker. It is not a general-purpose async runtime, and futures that depend on a
//! The async APIs do not start threads, spawn tasks, install timers, or require a runtime-specific
//! reactor. Task placement, deadlines, retries, periodic maintenance, and lifecycle orchestration
//! remain with the caller. Await Asyncband futures inside any executor that polls standard Rust
//! futures, and compose those runtime services around them.
//!
//! # Async first, blocking by adaptation
//!
//! Async and synchronous primitives have different optimization constraints. Asyncband designs its
//! primitives for async use and provides the optional [`blocking`] module as a boundary adapter
//! instead of duplicating synchronous methods across every type. Sync-first implementations can
//! exploit OS- or platform-specific facilities and remain the domain of dedicated libraries.
//!
//! The adapter's single-future executor parks the calling thread and resumes it through the
//! future's waker. It is not a general-purpose async runtime, and futures that depend on a
//! runtime-specific timer or I/O driver may not make progress. See the module documentation for the
//! full execution constraints.
//!
//! # Thread safety
//!
//! Primitives and guards implement `Send` and `Sync` only when their protected or transferred
//! values satisfy the required bounds. Consult each type's documentation for its exact contract.
//! Asyncband types implement `Send` and `Sync` only when their protected, transferred, or managed
//! values satisfy the required bounds. Consult each API's documentation for its exact contract.
//!
//! # Disclaimer
//!
Expand Down
Loading