Skip to main content

Module oneshot

Module oneshot 

Source
Expand description

A channel that carries one value from one sender to one receiver.

channel returns a Sender and a Receiver. The sender sends at most one value and never waits; the receiver is a Future that completes with the value, or with a error::RecvError when the sender is dropped without sending. The API is the established async one-shot channel API; nothing here needs a runtime, and every wait is a plain Waker handshake.

use nuclei::sync::oneshot;

let (tx, rx) = oneshot::channel();
std::thread::spawn(move || {
    tx.send(3).expect("the receiver is still there");
});
assert_eq!(rx.blocking_recv(), Ok(3));

If the sender is dropped without sending, the receiver fails:

use nuclei::sync::oneshot;

let (tx, rx) = oneshot::channel::<u32>();
drop(tx);
assert!(rx.blocking_recv().is_err());

A value that was sent but never received stays in the channel until the receiver is dropped, which drops it on the spot.

§Progress

Every operation of the channel is wait-free: it runs a bounded number of atomic read-modify-writes and loads of its own, and none of them loops because another thread got in first or waits for another thread. That covers Sender::send, Sender::is_closed, Sender::poll_closed, every poll of the Receiver, Receiver::try_recv, Receiver::close, Receiver::is_empty, Receiver::is_terminated and the drop of either half. Three things are not counted. A Waker the channel calls is the executor’s, and so is what it does; the destructor of the value runs in the call that drops it; and Receiver::blocking_recv parks the calling thread until the value arrives, which is what it is for.

§How the halves agree on the value

The value lives in one cell, UnsafeCell<MaybeUninit<T>>, and the state word has three bits: VALUE_SENT, RX_CLOSED and TX_DROPPED. Every bit is set once, with one fetch_or, so the word has one modification order that both halves read their outcome from. The cell has exactly one owner at a time:

  1. Before the sender’s fetch_or, the sender owns the cell and the receiver never reads it: the receiver reads it only after it saw VALUE_SENT in a word it loaded with Acquire, or in the word its own fetch_or returned.
  2. send writes the value, then does one fetch_or(VALUE_SENT | TX_DROPPED) and looks at the word that came back.
    • RX_CLOSED was clear: the receiver’s fetch_or(RX_CLOSED), if it ever happens, comes later in the order, so the receiver’s own result shows VALUE_SENT. The send succeeded and the cell is the receiver’s.
    • RX_CLOSED was set: the receiver’s fetch_or came first and its result showed no VALUE_SENT, so the receiver wrote down that its channel closed empty (RxClose::Empty) and never reads the cell, whatever bits show up later. The sender reads its value back out and returns it.
  3. The receiver’s side reads the cell exactly once, in the first of: a poll or try_recv that sees VALUE_SENT (it takes the value out and forgets the channel), or Receiver::drop (it drops the value if VALUE_SENT was set when it closed). A receiver that closed empty skips all of these.

So a value is read by the sender (a failed send), by the receiver’s take (a received value), or by the receiver’s drop (an unreceived value), never by two of them, and never leaked while both halves honour the protocol: the cell needs no drop glue of its own.

§Wakeups

The receiver’s task and the sender’s task each have an AtomicWaker. A poll registers its waker and then looks at the state word again, and a state change is followed by a wake of the other side’s waker, so a change is either seen by the re-check or woken: see the waker’s module for the handshake.

Modules§

error
Error types of the one-shot channel.

Structs§

Receiver
Receives a value from the associated Sender.
Sender
Sends a value to the associated Receiver.

Functions§

channel
Creates a one-shot channel for sending a single value between tasks.