Skip to main content

CoreMailbox

Struct CoreMailbox 

Source
pub struct CoreMailbox { /* private fields */ }
Expand description

Send + Sync mailbox bridging autonomous async producers to an owned, relocatable crate::node::Core. Held behind an Arc; the Core owns one clone, each timer task another. See the module docs.

Implementations§

Source§

impl CoreMailbox

Source

pub fn new() -> Self

A fresh, open, empty mailbox.

Source

pub fn post_emit(&self, node_id: NodeId, handle: HandleId) -> bool

Post a timer-fired emit request. Returns false iff the owning Core has already dropped (Self::close was called) — the caller MUST then release handle and stop (mirrors the old WeakCore::upgrade() == None teardown branch in timer.rs). Returns true when queued (and sets the runnable wake bit).

Source

pub fn post_complete(&self, node_id: NodeId) -> bool

Post a producer-sink Complete (D232-AMEND/A′). Returns false iff the owning Core is gone (caller stops; nothing to release).

Source

pub fn post_error(&self, node_id: NodeId, handle: HandleId) -> bool

Post a producer-sink Error (D232-AMEND/A′). Returns false iff the owning Core is gone — the caller MUST then release handle (it owned a retain for the would-be error payload).

Source

pub fn post_defer(&self, f: SendDeferFn) -> bool

Post a Send cross-thread Defer (D249/S2c). For an autonomous timer task whose closure captures only Send state (temporal.rs window_time/etc.). Returns false iff the owning Core is gone — the closure is dropped unrun. The !Send owner-side sink defers use DeferQueue::post instead.

Source

pub fn post_op(&self, op: MailboxOp) -> bool

Post a MailboxOp. Returns false iff the owning Core has already dropped (Self::close) — see the per-kind wrappers for the caller’s handle-release obligation.

QA F-A (2026-05-18): the closed check and the push_back are performed in one ops-lock critical section so a concurrent owner-thread close() (which also takes ops) cannot interleave between “observed not-closed” and “enqueued” — that TOCTOU would strand the op (with its retained HandleId) in a queue Drop for Core already walked → leak. close() takes the same lock, so the two are mutually exclusive.

Source

pub fn drain_into(&self, max_ops: u32, apply: impl FnMut(MailboxOp))

Owner-side drain. Pops every queued MailboxOp in FIFO order and hands each to apply (the caller passes a closure over the sync Core::{emit,complete,error}). Re-entrancy: apply may itself cascade and a concurrent timer task / re-entrant sink may post again — a fresh post re-sets runnable, so the enclosing drain-to-quiescence loop (or a later drain) picks it up.

QA F-#4 (2026-05-18): the empty observation and the runnable = false store happen **in the same ops-lock critical section. Previously the empty pop_front released the lock before storing false, so a concurrent post_op (which takes ops) could push and set runnable=true in between, then our false store would clobber it — a lost wakeup invisible to the is_runnable-gated in-wave drain and the S4/M6 scheduler. Clearing runnable while still holding the lock every post_op must take orders them: a post is either popped here or runs strictly after the false store and re-sets true. max_ops bounds a single drain (QA P3, 2026-05-18): apply may re-post (a Defer that re-defers, a producer re-subscribing), which this loop correctly drains in the same call — but a closure that re-posts itself on every application is an unbounded mailbox livelock (the producer-authoring analogue of a fn that emits to itself). That livelock lives HERE (the inner drain loop), not in drain_and_flush’s fire-cascade cap, so it is bounded + panics here — decoupled from the fire counter so a legitimately large finite producer drain never false-trips the fire cap.

/qa M3 (2026-05-19): a panic from apply(f) mid-drain previously unwound out of this loop with runnable still true — under the parking_lot::Mutex shape that was self-correcting (the next drain pop would re-observe + clear), but downstream is_runnable() gates would spuriously return true until then. Wraps the empty-queue clear in a RunnableClearGuard so an apply panic still clears runnable on unwind: if the queue is empty at unwind time the cell is reset; if the queue is non-empty the next post would re-set it anyway. Tightens the scheduler-wakeup contract under panic.

§Panics

Panics if more than max_ops ops are applied in one drain — that indicates a producer / Defer op re-posting itself every application (livelock guard). The default cap is sized for realistic cascades; bump via the corresponding setter if your workload has evidence it needs more.

Source

pub fn is_runnable(&self) -> bool

Whether the mailbox currently holds queued work (the wake bit). Advisory pre-M6 (the embedder pump drains unconditionally); S4/M6 consumers gate scheduling on it. #[must_use] (/qa m4) — a discarded result silently loses the scheduling signal.

Source

pub fn close(&self)

Mark the owning Core gone. Idempotent. Takes the ops lock so it is mutually exclusive with Self::post_op’s under-lock closed check (QA F-A): after this returns, no further post_op can enqueue. Callers MUST then Self::take_all and release any Emit/Error payload handles still queued (a TOCTOU-enqueued op posted just before close won the lock).

Source

pub fn take_all(&self) -> VecDeque<MailboxOp>

Drain and return every still-queued MailboxOp without applying it — for Drop for Core teardown (QA F-A / Blind #2). Emit/Error ops carry a retained HandleId the caller must release; Defer closures are dropped unrun (running CoreFull on a half-dropped Core is unsound — user-locked QA decision A, 2026-05-18). Clears runnable under the lock (same race discipline as drain_into).

Source

pub fn is_closed(&self) -> bool

Whether Self::close has been called.

Trait Implementations§

Source§

impl Default for CoreMailbox

Source§

fn default() -> Self

Returns the “default value” for a type. Read more

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.