Skip to main content

Protocol

Trait Protocol 

Source
pub trait Protocol {
    type Cmd;
    type Ind;
    type Msg;
    type Meta;
    type Entry;
    type Scope;
    type Note;

    // Required methods
    fn on_cmd(&mut self, cmd: Self::Cmd, cx: &mut ProtoCx<'_, Self>);
    fn on_msg(
        &mut self,
        from: NodeId,
        msg: Self::Msg,
        cx: &mut ProtoCx<'_, Self>,
    );
    fn on_timer(&mut self, id: TimerId, cx: &mut ProtoCx<'_, Self>);

    // Provided methods
    fn on_init(&mut self, _cx: &mut ProtoCx<'_, Self>) { ... }
    fn on_recovery(&mut self, _cx: &mut ProtoCx<'_, Self>) { ... }
    fn on_scope_event(
        &mut self,
        _scope: Self::Scope,
        _cx: &mut ProtoCx<'_, Self>,
    ) { ... }
}
Expand description

A synchronous protocol state machine.

Implementors handle three kinds of event — a command from the layer above, a message from a peer, and a timer fire — and emit effects through the context. Handling an event runs to completion: it cannot await, cannot be suspended, and cannot be cancelled part-way, so a state transition is atomic with respect to whatever drives it.

The four associated types are the protocol’s ports, in Kompics terms: what it accepts from above, what it promises upward, what it puts on the wire, and what it schedules.

Required Associated Types§

Source

type Cmd

Requests from the layer above.

Source

type Ind

Indications to the layer above — this protocol delivering on its guarantee.

Source

type Msg

What crosses the wire to a peer running the same protocol.

Source

type Meta

The durable value this protocol rewrites: a position, a count, an epoch. Small enough that rewriting it costs nothing.

A protocol keeping nothing durably declares this and Protocol::Entry as core::convert::Infallible, and then a write cannot be constructed — the same check-rather-than-trust that Protocol::Scope uses.

Source

type Entry

The durable entries this protocol appends: what accumulates.

Source

type Scope

Scopes whose boundaries this protocol’s guarantees depend on, and which it can observe.

A guarantee is rarely absolute. It holds while some condition does — a transport session, a retention window — and the end of that condition is an event the protocol must be told about, not an implementation detail beneath it. See docs/scope-annotated-modules.md.

Both boundaries travel here, not only the ending. A scope beginning is what makes a bridge possible at all: an ending says a suffix may be gone, and only the beginning of the successor says where to send it again. A port carrying just the ending would name a problem with no event on which to act.

A protocol with no such condition declares core::convert::Infallible. That is not a convention: an uninhabited type has no values, so a scope event cannot be constructed for it and Protocol::on_scope_event can never be called. The absence is checked rather than trusted, and such a protocol writes no handler at all.

A scope may only be named by a protocol that can observe its end. Naming one it cannot detect creates an obligation no implementation can discharge and no test can exercise.

Source

type Note

The vocabulary in which this protocol narrates its decisions.

A record of effects says what a protocol did. It cannot say what the protocol decided, and in particular it is silent about a decision whose outcome was to do nothing — a message refused, a candidate already passed, an announcement not made. Those are the cases that have cost this project the most, because a silence leaves nothing behind to read.

A protocol narrating nothing declares core::convert::Infallible, as with Protocol::Scope and for the same reason: an uninhabited type has no values, so Cx::note cannot be called for it. The absence is checked rather than trusted.

A vocabulary belongs to the run, not to a layer, exactly as a TimerId does. A composed stack narrates in one, and a note passes through composition untouched — no mapper, no conversion, nothing for a parent to re-wrap. A parent never restates a child’s decision, because the decision was the child’s; it merely lets it through, which is why Cx::with_child hands the child the parent’s own note sink the way it hands down the source of timer identities.

Narration is output-only. Nothing a protocol can observe reveals whether anything received a note, so no behaviour may depend on one and a run is reproducible whether or not it was read.

Required Methods§

Source

fn on_cmd(&mut self, cmd: Self::Cmd, cx: &mut ProtoCx<'_, Self>)

Handle a request from the layer above.

Source

fn on_msg(&mut self, from: NodeId, msg: Self::Msg, cx: &mut ProtoCx<'_, Self>)

Handle a message received from from.

Source

fn on_timer(&mut self, id: TimerId, cx: &mut ProtoCx<'_, Self>)

Handle a timer that fired somewhere in this protocol or in what it composes.

The identity says nothing about which layer registered it, so a protocol that composes children hands the expiry to each of them, and a protocol that registered timers acts only on one it registered. A protocol that registered none does nothing but pass it on. That is the price of a timer’s type not encoding the composition path, and it is a handful of calls deep rather than a type that grows with the stack.

Provided Methods§

Source

fn on_init(&mut self, _cx: &mut ProtoCx<'_, Self>)

Begin, on a first start — when nothing has been written down.

Exactly one of this and Protocol::on_recovery runs at startup, which is the branch the book draws with ⟨ Init ⟩ and ⟨ Recovery ⟩. It matters because some first-start work must not happen on a restart: writing an initial value down is the standard case, and doing it again on recovery would overwrite what was being recovered.

The constructor cannot serve this purpose. It runs in both cases — it is the common prefix of the two branches, not one of them — and it cannot emit effects, so it has nowhere to put a write. Volatile setup belongs there; anything a first start must do belongs here.

Source

fn on_recovery(&mut self, _cx: &mut ProtoCx<'_, Self>)

Resume after a crash, reading what survived.

Distinct from construction, and deliberately: the algorithms that need it act on recovering — re-announcing what they had already delivered, re-sending what was still pending — and those are effects, which a constructor cannot emit.

What survived is read from Cx::storage rather than handed over, since a protocol may need only part of it. Exactly one of this and Protocol::on_init runs at startup.

Nothing else is dispatched between being told to recover and this returning, which is what makes it safe to hold state not yet loaded.

Source

fn on_scope_event(&mut self, _scope: Self::Scope, _cx: &mut ProtoCx<'_, Self>)

Handle a boundary of a scope this protocol’s guarantees depend on — its end, or the beginning of the one that succeeds it.

Scope events travel downward, like messages: they originate outside the stack and are routed by each layer to whichever child cares, in the concrete type the bottom layer declares. What travels back up is an indication — a layer that cannot restore its guarantee says so in its own terms.

The default does nothing, which is unreachable for a protocol whose Scope is uninhabited.

Dyn Compatibility§

This trait is dyn compatible.

In older versions of Rust, dyn compatibility was called "object safety".

Implementors§