pub struct Mailbox { /* private fields */ }Expand description
A flow’s inbox, plus (when the owning flow is blocked on
Receive / ReceiveMatch / Ask with nothing to read) the parked flow itself.
§Why the flow lives inside its own mailbox while waiting
Internally, a flow is reachable through its FlowId, which
resolves (via the runtime’s flow directory) to this Mailbox. Bytecode
does not address by Pid anymore (FlowCap): Send / Ask resolve a
Cap to a FlowId first, then look up here. Host crate::Runtime::send
still uses FlowId directly (trusted).
Storing the blocked Box<Flow> directly in MailboxInner::parked, behind
the same mutex that guards the message queue, turns “deliver a message and
wake the receiver if it was waiting” into a single critical section — which
is what actually prevents the classic lost-wakeup race:
racing without a shared lock:
receiver: queue.pop() -> None
sender: queue.push(msg); wake(receiver) // receiver isn't parked yet!
receiver: park() // ...and now sleeps forever
with both steps under one mutex (what this type does):
receiver: lock; queue.pop() -> None; store self in `parked`; unlock
sender: lock; parked.take() -> Some(receiver); unlock; wake(receiver)Because “check the queue” and “become parked” happen atomically with
respect to “push and check for a parked receiver”, there is no window
where a message can be pushed without either landing in the queue for a
later Receive or immediately waking an already-parked one.
Wake only happens when a hop is accepted and matches the waiter.
A flow that is already runnable (message queued, nobody parked) does
not generate extra scheduler work — no wake storm on every Send.
§Selective wait (ReceiveMatch / Ask)
When parked with a selective (non-Any) filter, only a hop that
satisfies the filter wakes the flow. Other hops are appended to the
queue (subject to the bound) and the waiter stays parked (FIFO skip,
never drop matching semantics).
§Bound
push may return MailboxFull when the policy is Reject and the
logical capacity is already occupied and nobody matching is parked.
A parked waiter that matches the hop still takes a handoff — that
hop never occupies a queue slot.
Implementations§
Source§impl Mailbox
impl Mailbox
pub fn new() -> Self
pub fn with_config(config: MailboxConfig) -> Self
pub fn config(&self) -> MailboxConfig
Sourcepub fn stats(&self) -> Result<MailboxStats, RuntimeError>
pub fn stats(&self) -> Result<MailboxStats, RuntimeError>
Snapshot of enqueue/dequeue/drop counters plus current occupancy (taken under the mailbox lock, so the depth and the counters describe the same instant).
Sourcepub fn push(
&self,
value: Value,
) -> Result<Result<Delivery, MailboxFull>, RuntimeError>
pub fn push( &self, value: Value, ) -> Result<Result<Delivery, MailboxFull>, RuntimeError>
Push value under the mailbox config.
- If a matching waiter is parked →
Delivery::Handoff(does not consume a queue slot). - Else enqueue / overflow according to
OverflowPolicy. MailboxFullonly for Reject when the queue is already at one of its logical bounds (seeMailboxFullReason) — plus the one case no policy can absorb: a hop larger than the whole byte budget.
Mutex poison → RuntimeError (fail-closed).
Sourcepub fn try_pop(&self) -> Result<Option<Value>, RuntimeError>
pub fn try_pop(&self) -> Result<Option<Value>, RuntimeError>
Non-blocking pop of the front hop (classic Receive).
Sourcepub fn try_pop_match(&self, tag: u16) -> Result<Option<Value>, RuntimeError>
pub fn try_pop_match(&self, tag: u16) -> Result<Option<Value>, RuntimeError>
Non-blocking selective pop by application tag (ReceiveMatch).
Sourcepub fn park(
&self,
flow: Box<Flow>,
) -> Result<Result<WaitEpoch, Box<Flow>>, RuntimeError>
pub fn park( &self, flow: Box<Flow>, ) -> Result<Result<WaitEpoch, Box<Flow>>, RuntimeError>
Atomically re-check the queue and, if still empty, store flow
as parked (classic Receive — any hop wakes).
Outer Result is infrastructure (mutex poison). Inner Result is
the lost-wakeup race: Err(flow) means a message arrived between
the worker’s try_pop and this call — resume immediately with the
stashed pending message rather than parking forever.
Ok(Ok(epoch)) identifies the park that was installed. A caller
arming a ReceiveTimeout deadline must carry that WaitEpoch to
Mailbox::take_parked_at, or a late deadline will wake whatever
wait happens to be current instead.
Sourcepub fn park_match(
&self,
flow: Box<Flow>,
tag: u16,
) -> Result<Result<WaitEpoch, Box<Flow>>, RuntimeError>
pub fn park_match( &self, flow: Box<Flow>, tag: u16, ) -> Result<Result<WaitEpoch, Box<Flow>>, RuntimeError>
Like park, but only a hop with Message.tag == tag
ends the wait. Non-matching hops already in the queue are left
untouched (FIFO skip).
Sourcepub fn take_parked_at(
&self,
epoch: WaitEpoch,
) -> Result<Option<Box<Flow>>, RuntimeError>
pub fn take_parked_at( &self, epoch: WaitEpoch, ) -> Result<Option<Box<Flow>>, RuntimeError>
Take the parked flow back out only if epoch is still the
current wait, used by the timer when a ReceiveTimeout deadline
fires.
None means the deadline lost the race and must do nothing: either
a hop already woke that wait, or the flow has since parked on a
different Receive that this deadline does not own (see
WaitEpoch).
The selective filter is reset only when the flow is actually taken.
Clearing it on a stale call would downgrade a live ReceiveMatch /
Ask waiter to “any hop wakes me”.