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:
- Before the sender’s
fetch_or, the sender owns the cell and the receiver never reads it: the receiver reads it only after it sawVALUE_SENTin a word it loaded withAcquire, or in the word its ownfetch_orreturned. sendwrites the value, then does onefetch_or(VALUE_SENT | TX_DROPPED)and looks at the word that came back.RX_CLOSEDwas clear: the receiver’sfetch_or(RX_CLOSED), if it ever happens, comes later in the order, so the receiver’s own result showsVALUE_SENT. The send succeeded and the cell is the receiver’s.RX_CLOSEDwas set: the receiver’sfetch_orcame first and its result showed noVALUE_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.
- The receiver’s side reads the cell exactly once, in the first of: a
poll or
try_recvthat seesVALUE_SENT(it takes the value out and forgets the channel), orReceiver::drop(it drops the value ifVALUE_SENTwas 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§
Functions§
- channel
- Creates a one-shot channel for sending a single value between tasks.