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
20 changes: 20 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# Agent Instructions

## Cargo XTask

Use `cargo x` as the source of truth for repository workflows.

- Run `cargo x --help` before choosing build, test, lint, or formatting commands.
- Run `cargo x <command> --help` for command-specific behavior.

## Code Style

- Express visibility at the module boundary. Inside a restricted module, use `pub` for its module API instead of repeating `pub(crate)`.

## Commits and Pull Requests

Follow the semantic definition at `.github/semantic.yml`.

- Keep title descriptions short.
- Simple PR descriptions should only include a `Summary` section.
- Complex PR descriptions may also include a `Design Notes` section.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ All notable changes to this project will be documented in this file.

* Add an opt-in `asyncband::blocking::FutureExt` bridge with `block_on` and `wait_timeout` methods for waiting on runtime-agnostic futures from synchronous code.
* Add opt-in bounded and unbounded runtime-agnostic object pools under `asyncband::pool`.
* Add opt-in `asyncband::once::LazyCell` for values that own one asynchronous initializer and preserve its in-flight future across caller cancellation.

### Breaking changes

Expand Down
8 changes: 8 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
# under the License.

[workspace]
members = ["asyncband", "benchmarks", "tests-integration", "xtask"]
members = ["asyncband", "benchmarks", "examples", "tests-integration", "xtask"]
resolver = "3"

[workspace.package]
Expand Down
9 changes: 9 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,14 @@ async fn increment() {

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

## Examples

Runnable examples live in the [`examples`](examples) workspace crate. For example, the [`OnceCell` versus `LazyCell` initialization guide](examples/src/once_cell_vs_lazy_cell.rs) demonstrates when a restartable fixed free function is sufficient, when initialization needs access-time context and retries, and when a lazy value needs to own and preserve a one-shot initializer.

```shell
cargo run -p examples --example once_cell_vs_lazy_cell
```

## API map

| Area | API | Feature | Use |
Expand All @@ -65,6 +73,7 @@ Public paths stay direct—such as `asyncband::mutex`, `asyncband::pool`, and `a
| | [`Condvar`](https://docs.rs/asyncband/*/asyncband/condvar/struct.Condvar.html) | `condvar` | Wait for notifications while releasing a mutex. |
| 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. |
| | [`LazyCell`](https://docs.rs/asyncband/*/asyncband/once/struct.LazyCell.html) | `lazy-cell` | Lazily initialize a value with a stored asynchronous function. |
| | [`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. |
| | [`Latch`](https://docs.rs/asyncband/*/asyncband/latch/struct.Latch.html) | `latch` | Wait until a one-way countdown completes. |
Expand Down
1 change: 1 addition & 0 deletions asyncband/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ barrier = []
blocking = []
condvar = ["mutex"]
latch = []
lazy-cell = ["mutex"]
mpsc = []
mutex = []
once = ["semaphore"]
Expand Down
10 changes: 9 additions & 1 deletion asyncband/src/internal/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,17 @@ mod arena;
pub(crate) mod countdown;

#[cfg(any(feature = "once-map", feature = "singleflight"))]
#[cfg_attr(not(feature = "once-map"), allow(dead_code))]
// `OnceMap` and `singleflight` use different subsets of `OnceTable`, so single-feature builds
// leave some operations in the shared implementation unused.
#[allow(dead_code)]
pub(crate) mod once_table;

#[cfg(any(feature = "lazy-cell", feature = "once-cell"))]
// `LazyCell` and `OnceCell` use different subsets of `ValueCell`, so single-feature builds leave
// some operations in the shared implementation unused.
#[allow(dead_code)]
pub(crate) mod value_cell;

#[cfg(any(
feature = "barrier",
feature = "latch",
Expand Down
142 changes: 142 additions & 0 deletions asyncband/src/internal/value_cell.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.

use std::cell::UnsafeCell;
use std::mem::MaybeUninit;
use std::sync::atomic::AtomicBool;
use std::sync::atomic::Ordering;

/// Storage for a value published exactly once.
///
/// Synchronizing initialization is deliberately left to the containing primitive.
pub struct ValueCell<T> {
initialized: AtomicBool,
value: UnsafeCell<MaybeUninit<T>>,
}

// SAFETY: Shared access only exposes `&T`, and publication is synchronized by the initialized
// flag. The containing primitive is responsible for serializing writes.
unsafe impl<T: Sync + Send> Sync for ValueCell<T> {}

// SAFETY: Ownership of the cell and its value may be transferred when `T` is `Send`.
unsafe impl<T: Send> Send for ValueCell<T> {}

impl<T> ValueCell<T> {
pub const fn new() -> Self {
Self {
initialized: AtomicBool::new(false),
value: UnsafeCell::new(MaybeUninit::uninit()),
}
}

pub const fn from_value(value: T) -> Self {
Self {
initialized: AtomicBool::new(true),
value: UnsafeCell::new(MaybeUninit::new(value)),
}
}

pub fn is_initialized(&self) -> bool {
self.initialized.load(Ordering::Acquire)
}

pub fn is_initialized_mut(&mut self) -> bool {
*self.initialized.get_mut()
}

pub fn get(&self) -> Option<&T> {
if self.is_initialized() {
// SAFETY: The acquire load above observed publication of the value.
Some(unsafe { self.get_unchecked() })
} else {
None
}
}

pub fn get_mut(&mut self) -> Option<&mut T> {
if self.is_initialized_mut() {
// SAFETY: Exclusive access rules out concurrent reads or writes.
Some(unsafe { self.get_unchecked_mut() })
} else {
None
}
}

pub fn into_inner(mut self) -> Option<T> {
self.take()
}

pub fn take(&mut self) -> Option<T> {
if self.is_initialized_mut() {
*self.initialized.get_mut() = false;
// SAFETY: The value was initialized and the flag was cleared so it will not be
// dropped a second time.
Some(unsafe { self.value.get_mut().assume_init_read() })
} else {
None
}
}

/// Publishes a value after the containing primitive has won initialization.
///
/// # Safety
///
/// The cell must be uninitialized, and no other initialization may read or write it.
pub unsafe fn set(&self, value: T) -> &T {
debug_assert!(!self.is_initialized());
let value_ptr = self.value.get();
unsafe { value_ptr.write(MaybeUninit::new(value)) };

// Publish the initialized value to readers performing an acquire load.
self.initialized.store(true, Ordering::Release);

// SAFETY: The value was initialized and published above.
unsafe { self.get_unchecked() }
}

pub fn set_mut(&mut self, value: T) -> &mut T {
debug_assert!(!self.is_initialized_mut());
let value = self.value.get_mut().write(value);
*self.initialized.get_mut() = true;
value
}

/// # Safety
///
/// The cell must be initialized.
unsafe fn get_unchecked(&self) -> &T {
debug_assert!(self.is_initialized());
unsafe { (&*self.value.get()).assume_init_ref() }
}

/// # Safety
///
/// The cell must be initialized and exclusively borrowed.
unsafe fn get_unchecked_mut(&mut self) -> &mut T {
debug_assert!(self.is_initialized_mut());
unsafe { (&mut *self.value.get()).assume_init_mut() }
}
}

impl<T> Drop for ValueCell<T> {
fn drop(&mut self) {
if self.is_initialized_mut() {
// SAFETY: The value is initialized and exclusive access rules out other users.
unsafe { self.value.get_mut().assume_init_drop() };
}
}
}
9 changes: 7 additions & 2 deletions asyncband/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@
//! | Use case | APIs | Cargo features |
//! | -------------------------- | ------------------------------------------------------------------------------------- | ------------------------------------------- |
//! | Protect shared state | [`mutex::Mutex`], [`rwlock::RwLock`], [`condvar::Condvar`] | `mutex`, `rwlock`, `condvar` |
//! | Initialize values once | [`once::Once`], [`once::OnceCell`], [`once::OnceMap`] | `once`, `once-cell`, `once-map` |
//! | Initialize values once | [`once::Once`], [`once::OnceCell`], [`once::LazyCell`], [`once::OnceMap`] | `once`, `once-cell`, `lazy-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 objects | [`pool::bounded`], [`pool::unbounded`] | `pool` |
Expand Down Expand Up @@ -114,7 +114,12 @@ pub mod latch;
pub mod mpsc;
#[cfg(feature = "mutex")]
pub mod mutex;
#[cfg(any(feature = "once", feature = "once-cell", feature = "once-map"))]
#[cfg(any(
feature = "lazy-cell",
feature = "once",
feature = "once-cell",
feature = "once-map"
))]
pub mod once;
#[cfg(feature = "oneshot")]
pub mod oneshot;
Expand Down
Loading