Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
e8bdb64
refactor(latch): hide internal wait futures
tisonkun Aug 30, 2026
4cfd4c7
test: stop asserting debug text
tisonkun Aug 30, 2026
7417ded
test(latch): replace timing checks with state transitions
tisonkun Aug 30, 2026
9c92b44
test(waitgroup): make lifecycle tests deterministic
tisonkun Aug 30, 2026
a5e3294
test(once): coordinate retries without timers
tisonkun Aug 30, 2026
b6e16a7
test(once-cell): drive initialization state explicitly
tisonkun Aug 30, 2026
d8814c0
test(rwlock): remove redundant unwind scenarios
tisonkun Aug 30, 2026
1109f25
test(rwlock): poll fairness transitions directly
tisonkun Aug 30, 2026
0a7df8a
test(rwlock): consolidate owned guard mapping coverage
tisonkun Aug 30, 2026
4bd82bb
test(rwlock): focus concurrency tests on observable guarantees
tisonkun Aug 30, 2026
384f2df
fix(pool): reject zero-sized bounded pools
tisonkun Aug 30, 2026
c6dd38c
docs(waitgroup): explain handle-based completion
tisonkun Aug 30, 2026
8726330
test: cover documentation without default features
tisonkun Aug 30, 2026
758411d
docs(barrier): describe generations and leader results
tisonkun Aug 30, 2026
57cfb8e
test(semaphore): remove timer-based pseudo stress
tisonkun Aug 30, 2026
6242da2
test(broadcast): remove scheduler timing assumptions
tisonkun Aug 30, 2026
f9eaedf
refactor(pool): distinguish state invariants from unsafe code
tisonkun Aug 30, 2026
16b8747
docs(latch): state countdown and cancellation semantics
tisonkun Aug 30, 2026
bd60572
refactor(mpsc): label endpoint state invariants accurately
tisonkun Aug 30, 2026
37dabd5
test(mpsc): replace pressure loops with boundary cases
tisonkun Aug 30, 2026
0431604
docs(mpsc): document capacity and waiting behavior
tisonkun Aug 30, 2026
db9f067
docs(semaphore): remove blocking I/O from async example
tisonkun Aug 30, 2026
f1f19f8
docs(semaphore): correct owned permit examples
tisonkun Aug 30, 2026
d27ff61
docs(once-cell): assert initialization behavior directly
tisonkun Aug 30, 2026
8a9bdad
docs(condvar): demonstrate predicate-based task waiting
tisonkun Aug 30, 2026
b051463
docs(shutdown): focus on observable guard semantics
tisonkun Aug 30, 2026
f3f0870
docs(shutdown): clarify owned request future semantics
tisonkun Aug 30, 2026
990f66f
docs(shutdown): keep owned wait documentation concise
tisonkun Aug 30, 2026
7ee64ff
docs(shutdown): describe owned wait by its use
tisonkun Aug 30, 2026
cae2e9f
docs(shutdown): summarize owned wait in one sentence
tisonkun Aug 30, 2026
7cc1949
test: remove special-case no-feature doctest pass
tisonkun Aug 30, 2026
9948794
Apply suggestion from @tisonkun
tisonkun Aug 30, 2026
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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ All notable changes to this project will be documented in this file.
* Remove the `asyncband::atomicbox` module and its `AtomicBox` and `AtomicOptionBox` types from the public API.
* Remove the lossy `broadcast::overflow` channel; use the new lossless `broadcast::mpmc::unbounded` channel instead.
* Remove `OnceMap::with_capacity` and `OnceMap::with_capacity_and_hasher`; use `OnceMap::new` or `OnceMap::with_hasher`, which allocate the backing table lazily.
* Remove the unconstructible `LatchWait` and `OwnedLatchWait` types from the public API; `Latch::wait` and `Latch::wait_owned` continue to return anonymous futures through their `async fn` signatures.
* Rename `oneshot::Sender::is_closed` and `oneshot::Receiver::is_closed` to `is_disconnected`.
* Remove `Semaphore::try_acquire_and_forget`, `Semaphore::acquire_and_forget`, `Semaphore::try_acquire_owned_and_forget`, and `Semaphore::acquire_owned_and_forget`; acquire a permit and call its `forget` method instead.
* Replace `Semaphore::forget` with `Semaphore::drain_permits` and `Semaphore::forget_exact` with `Semaphore::reduce_permits`; permit-level `forget` methods are unchanged.
Expand Down
96 changes: 22 additions & 74 deletions asyncband/src/barrier/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,17 +15,11 @@
// specific language governing permissions and limitations
// under the License.

//! A synchronization primitive that enables multiple tasks to wait for each other.
//! Synchronize a fixed number of tasks at a reusable rendezvous point.
//!
//! The barrier ensures that no task proceeds past a certain point until all tasks have reached it.
//! This is useful for scenarios where multiple tasks need to proceed together after reaching a
//! certain point in their execution.
//!
//! A barrier enables multiple tasks to synchronize the beginning of some computation.
//! When a barrier is created, it is initialized with a count of the number of tasks
//! that will synchronize on the barrier. Each task can then call [`wait()`] on the
//! barrier to indicate it is ready to proceed. The barrier ensures that no task
//! proceeds past the barrier point until all tasks have made the call.
//! A [`Barrier`] releases one generation after the configured number of participants have awaited
//! [`Barrier::wait`]. It can then be reused for the next generation. Exactly one participant in
//! each generation receives a [`BarrierWaitResult`] marked as the leader.
//!
//! # Examples
//!
Expand All @@ -37,38 +31,21 @@
//! use asyncband::barrier::Barrier;
//!
//! let barrier = Arc::new(Barrier::new(3));
//! let mut handles = Vec::new();
//! let mut tasks = Vec::new();
//!
//! for i in 0..3 {
//! for _ in 0..3 {
//! let barrier = barrier.clone();
//! handles.push(tokio::spawn(async move {
//! println!("Task {} before barrier", i);
//! let result = barrier.wait().await;
//! println!("Task {} after barrier (leader: {})", i, result.is_leader());
//! }));
//! }
//!
//! for handle in std::mem::take(&mut handles) {
//! handle.await.unwrap();
//! let task = tokio::spawn(async move { barrier.wait().await.is_leader() });
//! tasks.push(task);
//! }
//!
//! // The barrier can be reused (generation is increased).
//! for i in 0..3 {
//! let barrier = barrier.clone();
//! handles.push(tokio::spawn(async move {
//! println!("Task {} before barrier", i);
//! let result = barrier.wait().await;
//! println!("Task {} after barrier (leader: {})", i, result.is_leader());
//! }));
//! }
//!
//! for handle in handles {
//! handle.await.unwrap();
//! let mut leaders = 0;
//! for task in tasks {
//! leaders += usize::from(task.await.unwrap());
//! }
//! assert_eq!(leaders, 1);
//! # }
//! ```
//!
//! [`wait()`]: Barrier::wait

use std::fmt;
use std::future::Future;
Expand Down Expand Up @@ -105,20 +82,9 @@ impl fmt::Debug for BarrierState {
}
}

/// A `BarrierWaitResult` is returned by [`Barrier::wait()`] when all threads
/// in the [`Barrier`] have rendezvoused.
/// The result of participating in one [`Barrier`] generation.
///
/// # Examples
///
/// ```
/// # #[tokio::main]
/// # async fn main() {
/// use asyncband::barrier::Barrier;
///
/// let barrier = Barrier::new(1);
/// let barrier_wait_result = barrier.wait().await;
/// # }
/// ```
/// Exactly one participant in each completed generation is designated as the leader.
pub struct BarrierWaitResult(bool);

impl fmt::Debug for BarrierWaitResult {
Expand All @@ -130,10 +96,9 @@ impl fmt::Debug for BarrierWaitResult {
}

impl BarrierWaitResult {
/// Returns `true` if this worker is the "leader" for the call to [`Barrier::wait()`].
/// Returns `true` if this participant is the leader for its barrier generation.
///
/// Only one worker will have `true` returned from their result, all other
/// workers will have `false` returned.
/// Exactly one participant per generation returns `true`.
///
/// # Examples
///
Expand All @@ -143,8 +108,7 @@ impl BarrierWaitResult {
/// use asyncband::barrier::Barrier;
///
/// let barrier = Barrier::new(1);
/// let barrier_wait_result = barrier.wait().await;
/// println!("{:?}", barrier_wait_result.is_leader());
/// assert!(barrier.wait().await.is_leader());
/// # }
/// ```
#[must_use]
Expand Down Expand Up @@ -188,9 +152,8 @@ impl Barrier {

/// Waits for all tasks to reach this point.
///
/// The barrier will block the current task until all `n` tasks have called `wait()`.
/// The last task to call `wait()` will be designated as the leader and receive `true`
/// as the return value. All other tasks will receive `false`.
/// The barrier holds the current task until all `n` participants have arrived. The final
/// participant is designated as the leader for this generation.
///
/// # Cancel safety
///
Expand All @@ -206,32 +169,17 @@ impl Barrier {
/// wait futures. Callers that need to keep waiting after another operation completes first
/// should retain and continue polling the same `wait` future instead of creating a new one.
///
/// # Returns
///
/// Returns a `Future` that resolves to:
/// * `true` if this task is the last (leader) task to arrive at the barrier
/// * `false` for all other tasks
/// Returns a [`BarrierWaitResult`] that identifies whether this participant is the leader.
///
/// # Examples
///
/// ```
/// # #[tokio::main]
/// # async fn main() {
/// use std::sync::Arc;
///
/// use asyncband::barrier::Barrier;
///
/// let barrier = Arc::new(Barrier::new(2));
/// let barrier2 = barrier.clone();
///
/// let handle = tokio::spawn(async move {
/// let result = barrier2.wait().await;
/// println!("Task 1: leader = {}", result.is_leader());
/// });
///
/// let result = barrier.wait().await;
/// println!("Task 2: leader = {}", result.is_leader());
/// handle.await.unwrap();
/// let barrier = Barrier::new(1);
/// assert!(barrier.wait().await.is_leader());
/// # }
/// ```
pub async fn wait(&self) -> BarrierWaitResult {
Expand Down
35 changes: 15 additions & 20 deletions asyncband/src/condvar/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,23 +40,20 @@
//! use asyncband::mutex::Mutex;
//!
//! let pair = Arc::new((Mutex::new(false), Condvar::new()));
//! let pair_clone = pair.clone();
//! let notifier_pair = pair.clone();
//!
//! // Inside our lock, spawn a new thread, and then wait for it to start.
//! tokio::spawn(async move {
//! let (lock, cvar) = &*pair_clone;
//! let mut started = lock.lock().await;
//! *started = true;
//! // We notify the condvar that the value has changed.
//! let notifier = tokio::spawn(async move {
//! let (lock, cvar) = &*notifier_pair;
//! let mut ready = lock.lock().await;
//! *ready = true;
//! cvar.notify_one();
//! });
//!
//! // Wait for the thread to start up.
//! let (lock, cvar) = &*pair;
//! let mut started = lock.lock().await;
//! while !*started {
//! started = cvar.wait(started).await;
//! }
//! let ready = cvar.wait_while(lock.lock().await, |ready| !*ready).await;
//! assert!(*ready);
//! drop(ready);
//! notifier.await.unwrap();
//! # }
//! ```

Expand Down Expand Up @@ -247,21 +244,20 @@ impl Condvar {
/// let pair = Arc::new((Mutex::new(false), Condvar::new()));
/// let pair_clone = pair.clone();
///
/// tokio::spawn(async move {
/// let notifier = tokio::spawn(async move {
/// let (lock, cvar) = &*pair_clone;
/// let mut started = lock.lock().await;
/// *started = true;
/// // We notify the condvar that the value has changed.
/// cvar.notify_one();
/// });
///
/// // Wait for the thread to start up.
/// let (lock, cvar) = &*pair;
/// // As long as the value inside the `Mutex<bool>` is `false`, we wait.
/// let guard = cvar
/// .wait_while(lock.lock().await, |started| !*started)
/// .await;
/// assert!(*guard);
/// drop(guard);
/// notifier.await.unwrap();
/// # }
/// ```
pub async fn wait_while<'a, T, F>(
Expand Down Expand Up @@ -293,21 +289,20 @@ impl Condvar {
/// let pair = (Arc::new(Mutex::new(false)), Arc::new(Condvar::new()));
/// let pair_clone = pair.clone();
///
/// tokio::spawn(async move {
/// let notifier = tokio::spawn(async move {
/// let (lock, cvar) = pair_clone;
/// let mut started = lock.lock_owned().await;
/// *started = true;
/// // We notify the condvar that the value has changed.
/// cvar.notify_one();
/// });
///
/// // Wait for the thread to start up.
/// let (lock, cvar) = pair;
/// // As long as the value inside the `Mutex<bool>` is `false`, we wait.
/// let guard = cvar
/// .wait_while_owned(lock.lock_owned().await, |started| !*started)
/// .await;
/// assert!(*guard);
/// drop(guard);
/// notifier.await.unwrap();
/// # }
/// ```
pub async fn wait_while_owned<T, F>(
Expand Down
Loading