Skip to main content

Crate ridl_loopback

Crate ridl_loopback 

Source
Expand description

ridl-loopback — the in-process reference runtime.

This crate implements the twelve port traits of ridl_rt::port over one in-memory store. It is the first runtime in this workspace (ADR-0020 decision 6, which names the crate and fixes ridl-rt as its only dependency), and it is what the generated interaction face runs against in a test, an example or a single-process application.

What it is not:

  • Not a transport. It carries no frame, opens no socket and has no wire format. A value published here is read here, in the same process.
  • Not the engine. The store below is a map and a queue, not the seqlock store, the sans-IO session, the platform traits and the scheduler, which are outside this repository.
  • Not a checker. Payload bytes are opaque to it: it carries them and never verifies one. Payload::verify is the generated face’s, on both sides.

§The handles

A runtime presents one handle type per port role rather than one type implementing them all, and may also offer an aggregate handle covering the port set one interface’s face needs (ADR-0021 decision 12). This crate offers both. Its six handles group eleven roles the way that decision derives the threading split: a handle each for the five roles with a &mut self method, and one handle for the six whose methods all take &self, which are exactly the roles several threads may hold at once. The twelfth port trait, Wakeable, is implemented on every handle, because each handle wakes its own waiters (see “Waking” below). Loopback is the aggregate: it implements all twelve port traits by delegating to the six role handles it holds, and it is what a generated Client, Publisher or dispatch is normally built over.

use ridl_loopback::Loopback;
use ridl_rt::contract::{CatalogHash, CatalogRef, InterfaceNo, Ordinal};
use ridl_rt::port::{SignalReader, SignalWriter};

let catalog = CatalogRef { name: "face.demo", hash: CatalogHash([0u8; 32]) };
let mut rt = Loopback::new(catalog);

rt.set(InterfaceNo(1), Ordinal(1), &[42]).expect("staged");
rt.commit();

let mut out = [0u8; 8];
let sample = rt.read(InterfaceNo(1), Ordinal(1), &mut out).expect("read");
assert_eq!(&out[..sample.len], &[42]);

Loopback::split hands out the six role handles for a program that wants them apart — one thread reading while another publishes, or two callers on one provider. ReaderHandle is Send + Sync; the other five are Send and driven by one thread each. Nothing here declares either: both follow from the fields, and the assertions at the bottom of this file pin them. Loopback::attach makes a second aggregate on the same store, for a program that holds several faces over one runtime, each owning its own.

§What it reports, and what it cannot

The loopback holds no catalog descriptor — driftsys/ridl#381 writes the descriptor file, and giving the loopback one is not yet assigned — so it has no member table, and there is no ordinal it can call unknown, no member it can call unowned, and no timing annotation it can measure a value’s freshness or a call’s remaining time against. What it therefore never returns: WriteError::NotOwner, RaiseError::NotOwner, ServeError::NotOwner, any port error’s Contract variant except FixedReader::read_fixed’s, and Freshness::Fresh or Freshness::Stale. Nothing detaches, because every handle holds the store alive, so Detached never appears either. The one bound is the call table’s Loopback::SLOTS: a send with every slot taken answers SendError::Busy. Nothing else is bounded, so TooLarge appears only from Loopback::fail_next_settle, and Transport::Busy reaches a caller only when a provider settles a call with it.

§Waking

Every handle implements Wakeable, and a handle stores a waker only under a kind of key one of its roles observes: a caller handle under Interest::Outcome, kept with each call, and one waker under Interest::Slot, a source handle one waker under Interest::Event, and a handler handle one waker under Interest::Claim. For Event and Claim the rule is one waker per kind, and a change to any key of the kind wakes the stored waker, whatever interface it was registered under (ADR-0021 decision 13); an Outcome waker is per call, and no change but that call’s settlement, its forget, or the drop of the caller handle that sent it wakes it (a displacement by another task does, as for every kind). A settlement wakes the call’s waiter, a raise wakes each source it queues the occurrence for, and a send wakes each handler that serves the member, and a reclaimed slot of the call table wakes every caller’s Slot waiter. A registration whose key already holds — the outcome is known, an occurrence or a call is waiting, a slot is free — is woken at once, and so is one under a kind the handle does not observe. A registration of the waker already stored refreshes it without waking it. No waker is woken while the store is locked.

Attached::catalog returns the CatalogRef the runtime was built with, unexamined. ADR-0021 decision 3 places a check of it against the interface’s own CATALOG in a generated face’s constructor, once, when the face is built; the constructor the Rust backend emits makes that check and panics on a mismatch (ADR-0023 decision 8). It is the face’s check and not the runtime’s: the loopback carries the value and compares nothing.

The crate’s as-built design record, with the reasoning behind each of these choices, is docs/design/ridl-loopback.md in this repository.

Structs§

CallerHandle
The Caller port role.
HandlerHandle
The Handler port role.
Handles
The six role handles of one runtime, as Loopback::split hands them out.
Loopback
The aggregate handle: one value implementing all twelve port traits by delegating to the six role handles it holds.
ReaderHandle
The &self port roles: Attached, Clock, SignalReader, FixedReader, and the two signal extensions.
SinkHandle
The EventSink port role.
SourceHandle
The EventSource port role: one subscription set and one queue, both in the store under this handle’s identity.
WriterHandle
The SignalWriter port role.