Provide an auto-reset event
- Dominant language
- Rust
- Stars
- 269
- Forks
- 38
- Avg merge
- 16h 44m
- Merged PRs (30d)
- 102
Description
## Summary
Add a runtime-agnostic `asyncband::event::AutoResetEvent` under the existing `event` feature. It should retain at most one unassigned signal and release one waiter per delivered signal, consuming the signal automatically when a wait succeeds.
This fills the single-permit notification gap identified in the primitive survey. Related: #218. The manual-reset event issue #221 explicitly excluded auto-reset and Tokio-style single-permit notification semantics.
## Motivation
A background worker or a serialized drain operation may need a payload-free "recheck the predicate" signal. Notifications must survive the interval between checking the external state and registering a wait, while repeated notifications without a consumer can coalesce.
The existing primitives represent different contracts:
- `Condvar` does not retain notifications and coordinates waiting with a mutex-protected predicate.
- `ManualResetEvent` releases all registered waits and remains set for future waits until explicitly reset.
- `Semaphore` counts permits and normally returns an acquired permit when its guard is dropped.
- `watch` retains a value and tracks changes independently for each receiver.
An auto-reset event offers one consumable signal without a payload, version tracking, or a permit-return lifecycle. It is useful for simplifying notification plumbing; any performance benefit should be measured separately.
## Proposed initial API
```rust
AutoResetEvent::new()
AutoResetEvent::with_state(bool)
event.set()
event.try_wait() -> bool
event.wait().await
event.wait_owned().await // through Arc
```
Keep `AutoResetEvent` and `ManualResetEvent` as separate public types. The initial API need not include `reset()` or `is_set()`: `try_wait()` provides an atomic consuming operation, while manual reset would require an additional contract for signals already assigned to waiters.
## Proposed contract and design questions
- `new()` starts unset. `with_state(true)` starts with one stored signal.
- If eligible waiters are queued, `set()` assigns a signal to one waiter. Prefer FIFO selection by registration order and define whether `try_wait()` can bypass queued waits.
- Otherwise, `set()` stores one signal. Further sets while that unassigned signal is stored coalesce rather than accumulating a count.
- A successful wait or `try_wait()` consumes one signal; completion or dropping the completed future does not return it.
- Distinguish a stored signal from a signal already assigned to a waiter. Specify the outcome of repeated sets when selected waiters have been woken but not polled again.
- Define the waiter-registration point, including futures created but never polled. Coordinate state checks and registration so a concurrent set cannot leave a registered waiter asleep with an available signal.
- Cancelling an unselected pending wait removes only that registration. If a selected waiter is cancelled before returning `Ready`, transfer its signal to another eligible waiter or restore the stored signal, coalescing with any signal already stored.
- Define the memory-publication guarantee between signaling and successful consumption, including repeated/coalesced sets.
A mutex-protected state and `WaitList` are a reasonable baseline. The existing `Condvar::notify_one` cancellation handoff is relevant design experience. Waker clone, drop, and wake callbacks must remain outside internal state locks, including on cancellation paths.
## Usage boundaries
For a single async observer, a predicate loop can use:
```rust
while !predicate() {
changed.wait().await;
}
// On a path that may make the predicate true:
publish_state();
changed.set();
```
The predicate remains the source of truth, and every relevant state transition must signal. A stale signal may cause an extra predicate check.
This is not automatically a replacement for multi-observer `watch` or manual-reset events: one signal only releases one waiter. Examples involving drain generations must make the single-observer invariant explicit. Multiple consumers of external state may require an explicit registration protocol; document the supported pattern rather than promising that this loop works for every consumer topology.
## Acceptance criteria
- Document stored-signal coalescing, signal assignment and consumption, registration, fairness, memory publication, and cancellation.
- Cover set-before-wait, repeated sets, competing waiters, registration races, and cancellation before and after signal assignment with deterministic regression tests.
- Verify that cancellation neither loses an assigned signal nor retains stale wakers indefinitely.
- Support borrowed and owned waits consistently with `ManualResetEvent`, without executor or timer dependencies.
- Add public examples and update the API map and changelog when the feature is implemented; validate through the repository's `cargo x` workflows.
## Prior art
- [.NET AutoResetEvent](https://learn.microsoft.com/en-us/dotnet/api/system.threading.autoresetevent?view=net-10.0): a reusable signal consumed by one waiter.
- [Tokio Notify](https://docs.rs/tokio/1.53.1/tokio/sync/struct.Notify.html): single-permit notification, with useful documentation on coalescing and multi-consumer registration races. This proposal does not require its broadcast operations or the full `Notify` API.
Contributor guide
No contributing guide indexed for this repository
Research direction
Start by reading the existing ManualResetEvent implementation and the Condvar::notify_one cancellation handoff, then inspect the WaitList and the repository's cargo x workflows. Define the auto-reset contract for coalescing, registration, fairness, memory publication, and cancellation, and cover those cases with deterministic tests plus public examples and API-map/changelog updates.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- rust
- Domain
- backend
- Issue type
- Feature
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 35/100