apache / apache/asyncband

Provide an auto-reset event

Closed
#292 2 comments 0 reactions 0 assignees View on GitHub
enhancement good first issue help wanted
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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.