Skip to main content

Cx

Struct Cx 

Source
pub struct Cx<'a, M, I, N, Me, En> { /* private fields */ }
Expand description

A protocol’s window onto the world.

Supplies the current time and a seeded randomness source, and receives every effect the protocol emits. Nothing else reaches a protocol: given the same state, event, now, and RNG stream, it behaves identically every time.

Implementations§

Source§

impl<'a, M, I, N, Me, En> Cx<'a, M, I, N, Me, En>

Source

pub fn new( sink: &'a mut dyn EffectSink<M, I>, now: Time, rng: &'a mut dyn RngCore, store: &'a mut dyn Store<Me, En>, next_timer: &'a mut u64, notes: &'a mut dyn NoteSink<N>, ) -> Self

Build a context over any sink, any store, and any audience for what it narrates.

Pass NoNotes when nothing is listening, which is the ordinary case. It is a parameter rather than an option so that the protocol’s own code is the same either way — which is what makes narrating unable to change the run.

Source

pub fn send(&mut self, to: NodeId, msg: M)

Transmit msg to to.

Source

pub fn indicate(&mut self, ind: I)

Raise an indication to the layer above.

Source

pub fn set_timer(&mut self, after: Duration) -> TimerId

Register a timer for after, and take a handle naming it.

The handle is what a later expiry is compared against, so a protocol can tell the timer it is waiting on from one it has superseded. It says nothing about which protocol registered it or where that protocol sits in a composition.

Source

pub fn note(&mut self, note: N)

Record a decision this protocol has taken.

Recorded at the point of the call, not deferred like an effect: a note says what has already happened, and its place in the run is where the handler put it.

Worth narrating only where the record of effects cannot say it. A note beside indicate restating the same thing adds nothing a reader could not already see and can drift from it. What no trace can hold is a decision that produced no effect — a message refused, a timestamp already passed, an announcement not made — and that is the case whose absence has cost this project the most.

Uncallable for a protocol whose Note is uninhabited: there is no value to pass. The absence is checked rather than trusted, exactly as it is for a scope event.

ⓘ
struct Quiet;
impl Protocol for Quiet {
    type Cmd = ();
    type Ind = ();
    type Msg = ();
    type Scope = core::convert::Infallible;
    // Narrates nothing, and so cannot.
    type Note = core::convert::Infallible;
    type Meta = core::convert::Infallible;
    type Entry = core::convert::Infallible;
    fn on_cmd(&mut self, (): (), cx: &mut ProtoCx<'_, Self>) {
        cx.note(());
    }
    fn on_msg(&mut self, _: NodeId, (): (), _: &mut ProtoCx<'_, Self>) {}
    fn on_timer(&mut self, _: TimerId, _: &mut ProtoCx<'_, Self>) {}
}
Source

pub fn storage(&mut self) -> &mut dyn Store<Me, En>

This protocol’s durable state. A write does not return until it would survive a crash, so a protocol may record a promise and then make it. See crate::store.

Source

pub fn now(&self) -> Time

The current time — virtual under simulation, real under a live driver.

Source

pub fn rng(&mut self) -> &mut dyn RngCore

The seeded randomness source. Reproducible for a given seed.

Source

pub fn with_child<CM, CI>( &mut self, msg: impl Fn(CM) -> M, ind: impl Fn(CI) -> I, f: impl FnOnce(&mut Cx<'_, CM, CI, N, Infallible, Infallible>), )

Run f against a child’s context, translating everything it emits into this protocol’s terms on the way out.

The mappers are normally enum variant constructors, so a wrong one is a type error.

The child is handed a store it cannot write to, so only a child keeping nothing durably can be composed. See crate::store::NoStore. Nothing is buffered: a child’s effect is re-wrapped as it is emitted and passed straight on to this context’s sink.

Source

pub fn with_child_consuming<CM, CI>( &mut self, msg: impl Fn(CM) -> M, collected: &mut Vec<CI>, f: impl FnOnce(&mut Cx<'_, CM, CI, N, Infallible, Infallible>), )

Run f against a child’s context, forwarding what it sends and schedules but collecting its indications into collected for this protocol to handle itself.

This is the usual shape. A child’s indication is the parent’s input — the stubborn link reporting a delivery is what the perfect link deduplicates — so it must be consumed, not passed through. collected belongs to the caller and is reused across events.

Source

pub fn with_durable_child_consuming<CM, CI, CMe>( &mut self, msg: impl Fn(CM) -> M, collected: &mut Vec<CI>, slot: Slot<Me, CMe>, f: impl FnOnce(&mut Cx<'_, CM, CI, N, CMe, Infallible>), )

Cx::with_child_consuming, for a child that keeps durable state of its own.

The child is handed a view of slot — the part of this protocol’s record that belongs to it. Its get projects, and its set reads this record back, replaces the child’s part, and writes the whole thing down again. That is one write, not two, which is what stops a crash landing between a parent’s record and its child’s.

Prefer Cx::with_child_consuming wherever the child keeps nothing: it hands a NoStore, and a child that cannot write is one fewer thing to reason about. This exists because Algorithm 5.10 needs it — a protocol that keeps (ets, ℓ, decision) of its own and composes two children that each keep a record too, and whose recovery reads its children’s records by name.

The child’s Entry is uninhabited: a child composed this way cannot append. That is the right default and most durable children want nothing else; one that does append is composed with Cx::with_durable_child and a SeqSlot as well.

Source

pub fn with_keyed_durable_child_consuming<CM, CI, CMe, K>( &mut self, msg: impl Fn(CM) -> M, collected: &mut Vec<CI>, slot: KeyedSlot<Me, CMe, K>, key: K, f: impl FnOnce(&mut Cx<'_, CM, CI, N, CMe, Infallible>), )

Cx::with_durable_child_consuming, for one member of a family of durable children.

A Slot names a fixed place, which is right when a parent has one child of a kind. A parent holding one instance per round needs a place per round, and key supplies it — as data rather than as something the slot captured, so the slot is still one fixed function.

The parent owns the keyspace: two members handed the same key share a record.

Source

pub fn with_durable_child<CM, CI, CMe, CEn>( &mut self, msg: impl Fn(CM) -> M, collected: &mut Vec<CI>, slot: Slot<Me, CMe>, entries: SeqSlot<En, CEn>, f: impl FnOnce(&mut Cx<'_, CM, CI, N, CMe, CEn>), )

Cx::with_durable_child_consuming, for a child that keeps metadata and appends.

The child’s record lives in slot of this protocol’s, exactly as before, and its entries go into this protocol’s sequence through entries — one sequence, not two. Two sequences would have no order between them, and a recovery replaying both would be inventing one.

The positions the child reads back are this protocol’s, and therefore sparse: its third entry may sit at position seven. SeqSlot says why that is all a cursor needs.

Cx::with_durable_child_consuming remains the one to reach for. A child that cannot append is one fewer thing to reason about, and its signature says it cannot; this exists because the fail-recovery total-order broadcast keeps a durable record of its own and composes a child that appends, which nothing did before.

Auto Trait Implementations§

§

impl<'a, M, I, N, Me, En> !RefUnwindSafe for Cx<'a, M, I, N, Me, En>

§

impl<'a, M, I, N, Me, En> !Send for Cx<'a, M, I, N, Me, En>

§

impl<'a, M, I, N, Me, En> !Sync for Cx<'a, M, I, N, Me, En>

§

impl<'a, M, I, N, Me, En> !UnwindSafe for Cx<'a, M, I, N, Me, En>

§

impl<'a, M, I, N, Me, En> Freeze for Cx<'a, M, I, N, Me, En>

§

impl<'a, M, I, N, Me, En> Unpin for Cx<'a, M, I, N, Me, En>

§

impl<'a, M, I, N, Me, En> UnsafeUnpin for Cx<'a, M, I, N, Me, En>

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
§

impl<ST, DT> CastableFrom<ST, Initialized, Initialized> for DT
where ST: ?Sized, DT: ?Sized,

§

impl<ST, DT> CastableFrom<ST, Uninit, Uninit> for DT
where ST: ?Sized, DT: ?Sized,

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.

§

impl<T> Read<Exclusive, BecauseExclusive> for T
where T: ?Sized,

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.
§

impl<V, T> VZip<V> for T
where V: MultiLane<T>,

§

fn vzip(self) -> V