Skip to main content

ridl_rt/
correlate.rs

1//! The caller-side call table and the waiter registry a runtime keeps behind
2//! [`Wakeable`](crate::port::Wakeable) (ADR-0021 decision 15).
3//!
4//! Every runtime with asynchronous replies keeps a table of the calls it has
5//! sent and not yet released, and every runtime that implements `Wakeable`
6//! keeps the wakers its handles registered. These two types are that storage,
7//! written once so that each runtime does not write it alone. Both are pure
8//! data structures: they allocate nothing, hold no lock, and wake nothing. A
9//! runtime puts them behind its own lock, and every operation that would wake
10//! a task returns the waker instead, so the runtime wakes it after releasing
11//! that lock and no waker runs under the runtime's mutex.
12//!
13//! [`Table`] holds `N` calls. A [`Correlation`] is `(generation << 16) | slot`:
14//! the slot index in the low 16 bits and the slot's generation above it, so
15//! `N` is at most 65536 and 48 bits of generation remain. A slot is reclaimed
16//! by [`Table::forget`] alone — at once for a settled call, and at the
17//! settlement for a call in flight — and its generation advances when it is,
18//! so the correlation the slot had before answers as unknown. The table stores
19//! each call's outcome status and one `Interest::Outcome` waker, but no reply
20//! bytes: a runtime keeps those in storage of its own indexed by
21//! [`Table::slot`].
22//!
23//! [`Waiters`] holds one waker per kind of key a handle stores — `Slot`,
24//! `Event` and `Claim` — whatever interface an `Event` or `Claim` key names,
25//! so a change to any key of that kind takes it (ADR-0021 decision 13).
26//!
27//! `N` is checked at compile time: a table of more than 65536 slots does not
28//! build.
29//!
30//! ```compile_fail,E0080
31//! const TOO_MANY: ridl_rt::correlate::Table<65537> = ridl_rt::correlate::Table::new(None);
32//! ```
33//!
34//! ```
35//! const ENOUGH: ridl_rt::correlate::Table<65536> = ridl_rt::correlate::Table::new(None);
36//! ```
37
38use core::task::Waker;
39
40use crate::error::CallError;
41use crate::port::{Correlation, Interest};
42
43/// The bits of a correlation that hold the slot index.
44const SLOT_BITS: u32 = 16;
45const SLOT_MASK: u64 = (1 << SLOT_BITS) - 1;
46/// The 48 bits a generation keeps: it wraps to 0 after `2^48 - 1` reclaims of
47/// one slot.
48const GENERATION_MASK: u64 = (1 << (64 - SLOT_BITS)) - 1;
49
50/// What [`Table::settle`] did, and the waker it hands back to be woken.
51///
52/// The runtime wakes the waker after releasing its lock. On
53/// [`Reclaimed`](Settled::Reclaimed) it also wakes every `Interest::Slot`
54/// waiter it holds and frees whatever it stored for the slot.
55#[must_use = "a waker or a reclaim handed back and ignored leaves a task waiting"]
56#[derive(Debug)]
57pub enum Settled {
58    /// The outcome is recorded and [`Table::outcome`] reads it from now on.
59    /// The `Interest::Outcome` waker stored for the call, if any, is handed
60    /// back and is no longer stored.
61    Recorded(Option<Waker>),
62    /// The call had been forgotten while in flight: the outcome is not
63    /// recorded, the slot is free, and its reservation is credited to the
64    /// budget.
65    Reclaimed,
66    /// No call in flight has this correlation — never issued, already
67    /// settled, or of a generation the slot no longer has. Nothing changed.
68    Unknown,
69}
70
71/// What [`Table::forget`] did, and the waker it hands back to be woken.
72///
73/// On [`Reclaimed`](Forgotten::Reclaimed) the runtime wakes every
74/// `Interest::Slot` waiter it holds and frees whatever it stored for the slot.
75#[must_use = "a waker or a reclaim handed back and ignored leaves a task waiting"]
76#[derive(Debug)]
77pub enum Forgotten {
78    /// The call was settled: the slot is free now, and its reservation is
79    /// credited to the budget.
80    Reclaimed,
81    /// The call is in flight: it is marked, its outcome will not be recorded,
82    /// and the slot is reclaimed at its settlement, which [`Table::settle`]
83    /// reports as [`Settled::Reclaimed`]. The call's `Interest::Outcome`
84    /// waker, if any, is handed back, because no outcome will ever be readable
85    /// for it.
86    Marked(Option<Waker>),
87    /// No call this table holds has this correlation, or it was already
88    /// forgotten. Nothing changed.
89    Unknown,
90}
91
92/// Where one slot's call is.
93#[derive(Clone, Copy, Debug)]
94enum State {
95    Free,
96    InFlight { forgotten: bool },
97    Settled(Result<(), CallError>),
98}
99
100#[derive(Debug)]
101struct Entry {
102    generation: u64,
103    state: State,
104    /// The bytes debited from the budget at insert, credited at reclaim.
105    reservation: u64,
106    /// The call's one `Interest::Outcome` waker.
107    waker: Option<Waker>,
108}
109
110impl Entry {
111    const FREE: Entry = Entry {
112        generation: 0,
113        state: State::Free,
114        reservation: 0,
115        waker: None,
116    };
117}
118
119/// The caller-side call table: `N` slots, each with a generation, the call's
120/// outcome status, and one waker, and an optional byte budget.
121///
122/// Every method takes `&self` or `&mut self` and returns; a runtime keeps the
123/// table behind its own lock. See the [module documentation](self).
124#[derive(Debug)]
125pub struct Table<const N: usize> {
126    slots: [Entry; N],
127    /// The bytes still free, or `None` for no budget.
128    budget: Option<u64>,
129}
130
131impl<const N: usize> Table<N> {
132    /// Fails the build of a `Table<N>` whose slot index does not fit in the 16
133    /// bits a correlation keeps for it.
134    const SLOT_INDEX_FITS: () = assert!(
135        N <= 1 << SLOT_BITS,
136        "a correlate::Table holds at most 65536 slots"
137    );
138
139    /// An empty table. `budget` is the byte budget reservations are debited
140    /// from — a runtime with a catalog descriptor sizes it with
141    /// [`table_budget`](crate::contract::table_budget) — or `None` for no
142    /// budget, in which case only the slot count bounds the calls in flight.
143    pub const fn new(budget: Option<u64>) -> Self {
144        let () = Self::SLOT_INDEX_FITS;
145        Table {
146            slots: [Entry::FREE; N],
147            budget,
148        }
149    }
150
151    /// Takes the lowest free slot for a new call and debits `reservation`
152    /// from the budget. `None` when every slot is in flight or settled and
153    /// unforgotten, or when the budget has fewer than `reservation` bytes
154    /// free; a runtime answers either with `SendError::Busy`. Without a
155    /// budget, `reservation` is not read.
156    pub fn insert(&mut self, reservation: u64) -> Option<Correlation> {
157        let index = self
158            .slots
159            .iter()
160            .position(|entry| matches!(entry.state, State::Free))?;
161        if let Some(free) = self.budget {
162            self.budget = Some(free.checked_sub(reservation)?);
163        }
164        let entry = &mut self.slots[index];
165        entry.state = State::InFlight { forgotten: false };
166        entry.reservation = reservation;
167        Some(Correlation((entry.generation << SLOT_BITS) | index as u64))
168    }
169
170    /// Records the outcome of the call in flight under `c`, or, when the call
171    /// was forgotten, reclaims its slot. The first settlement of a call is
172    /// its outcome; a later one is [`Settled::Unknown`] and changes nothing.
173    pub fn settle(&mut self, c: Correlation, outcome: Result<(), CallError>) -> Settled {
174        let Some(index) = self.held(c) else {
175            return Settled::Unknown;
176        };
177        match self.slots[index].state {
178            State::InFlight { forgotten: false } => {
179                let entry = &mut self.slots[index];
180                entry.state = State::Settled(outcome);
181                Settled::Recorded(entry.waker.take())
182            }
183            State::InFlight { forgotten: true } => {
184                self.reclaim(index);
185                Settled::Reclaimed
186            }
187            State::Settled(_) | State::Free => Settled::Unknown,
188        }
189    }
190
191    /// The outcome recorded for `c`, or `None` while it is in flight, after it
192    /// is forgotten, or when the table holds no call under `c`. Reading it
193    /// does not reclaim the slot: only [`forget`](Table::forget) does.
194    #[must_use]
195    pub fn outcome(&self, c: Correlation) -> Option<Result<(), CallError>> {
196        match self.slots[self.held(c)?].state {
197            State::Settled(outcome) => Some(outcome),
198            State::InFlight { .. } | State::Free => None,
199        }
200    }
201
202    /// Releases `c`, the one operation that reclaims a slot: at once for a
203    /// settled call, and at its settlement for a call in flight, which this
204    /// marks. Either way `c` has no readable outcome afterwards.
205    pub fn forget(&mut self, c: Correlation) -> Forgotten {
206        let Some(index) = self.held(c) else {
207            return Forgotten::Unknown;
208        };
209        match self.slots[index].state {
210            State::Settled(_) => {
211                self.reclaim(index);
212                Forgotten::Reclaimed
213            }
214            State::InFlight { forgotten: false } => {
215                let entry = &mut self.slots[index];
216                entry.state = State::InFlight { forgotten: true };
217                Forgotten::Marked(entry.waker.take())
218            }
219            State::InFlight { forgotten: true } | State::Free => Forgotten::Unknown,
220        }
221    }
222
223    /// Stores `waker` as the `Interest::Outcome(c)` waker of the call in
224    /// flight under `c`, and returns the waker to wake:
225    ///
226    /// - the displaced waker, when the call held a waker of another task;
227    /// - nothing, when the stored waker [`will_wake`](Waker::will_wake) the
228    ///   same task as `waker`, which is a refresh: the stored waker is
229    ///   replaced and not woken (ADR-0021 decision 13);
230    /// - `waker` itself, not stored, when no outcome is still to be recorded
231    ///   under `c`: the outcome is already known, the call was forgotten, or
232    ///   the table holds no call under `c`. A stored waker would never be
233    ///   woken, and the task's read that follows finds what there is.
234    pub fn wake_on(&mut self, c: Correlation, waker: &Waker) -> Option<Waker> {
235        let Some(index) = self.held(c) else {
236            return Some(waker.clone());
237        };
238        let entry = &mut self.slots[index];
239        match entry.state {
240            State::InFlight { forgotten: false } => store(&mut entry.waker, waker),
241            State::InFlight { forgotten: true } | State::Settled(_) | State::Free => {
242                Some(waker.clone())
243            }
244        }
245    }
246
247    /// The slot index of `c`, which a runtime uses to index its own storage
248    /// for the call — reply bytes, arguments — beside the table.
249    #[must_use]
250    pub fn slot(c: Correlation) -> usize {
251        (c.0 & SLOT_MASK) as usize
252    }
253
254    /// The slot of `c` when that slot holds a call under `c`'s generation.
255    fn held(&self, c: Correlation) -> Option<usize> {
256        let index = Self::slot(c);
257        let entry = self.slots.get(index)?;
258        let generation = c.0 >> SLOT_BITS;
259        (entry.generation == generation && !matches!(entry.state, State::Free)).then_some(index)
260    }
261
262    /// Frees a slot: credits its reservation, drops its waker, and advances
263    /// its generation so the correlation it had answers as unknown.
264    fn reclaim(&mut self, index: usize) {
265        let entry = &mut self.slots[index];
266        if let Some(free) = self.budget {
267            self.budget = Some(free.saturating_add(entry.reservation));
268        }
269        entry.state = State::Free;
270        entry.reservation = 0;
271        entry.waker = None;
272        entry.generation = entry.generation.wrapping_add(1) & GENERATION_MASK;
273    }
274}
275
276/// The waiter registry a handle keeps behind
277/// [`Wakeable`](crate::port::Wakeable): one waker for each of the kinds
278/// `Slot`, `Event` and `Claim`.
279///
280/// An `Event` or `Claim` key's interface is not kept: the handle holds one
281/// waker per kind, and a change to any key of that kind takes it (ADR-0021
282/// decision 13). `Interest::Outcome` is not stored here; a call's outcome
283/// waker is kept with the call, in [`Table`]. Whether a key's change has
284/// already happened, and so whether a registration is woken at once, is the
285/// runtime's to know: it registers, and then takes the waker back when the
286/// change is already visible.
287#[derive(Debug, Default)]
288pub struct Waiters {
289    slot: Option<Waker>,
290    event: Option<Waker>,
291    claim: Option<Waker>,
292}
293
294impl Waiters {
295    /// A registry with no waker stored.
296    #[must_use]
297    pub const fn new() -> Self {
298        Waiters {
299            slot: None,
300            event: None,
301            claim: None,
302        }
303    }
304
305    /// Stores `waker` as the one waker of `what`'s kind, and returns the
306    /// waker to wake:
307    ///
308    /// - the displaced waker, when the kind held a waker of another task;
309    /// - nothing, when the stored waker [`will_wake`](Waker::will_wake) the
310    ///   same task as `waker`, which is a refresh: the stored waker is
311    ///   replaced and not woken, whatever interface either key named;
312    /// - `waker` itself, not stored, for `Interest::Outcome`, which a
313    ///   [`Table`] holds: a registration here could never be taken, and
314    ///   handing it back leaves no task waiting on it.
315    pub fn register(&mut self, what: Interest, waker: &Waker) -> Option<Waker> {
316        match self.kind(what) {
317            Some(stored) => store(stored, waker),
318            None => Some(waker.clone()),
319        }
320    }
321
322    /// Takes the waker of `what`'s kind, whatever interface it was registered
323    /// under, which clears that kind. `None` when none is stored, and always
324    /// for `Interest::Outcome`.
325    pub fn take(&mut self, what: Interest) -> Option<Waker> {
326        self.kind(what)?.take()
327    }
328
329    /// Takes every stored waker, which clears every kind, for a runtime with
330    /// one unkeyed "something changed" source. The wakers are taken when this
331    /// is called, whether or not the iterator is consumed, and the iterator
332    /// does not borrow the registry.
333    pub fn take_all(&mut self) -> impl Iterator<Item = Waker> + use<> {
334        [self.slot.take(), self.event.take(), self.claim.take()]
335            .into_iter()
336            .flatten()
337    }
338
339    fn kind(&mut self, what: Interest) -> Option<&mut Option<Waker>> {
340        match what {
341            Interest::Slot => Some(&mut self.slot),
342            Interest::Event(_) => Some(&mut self.event),
343            Interest::Claim(_) => Some(&mut self.claim),
344            Interest::Outcome(_) => None,
345        }
346    }
347}
348
349/// Stores `waker` in a one-waker slot and returns the displaced waker of
350/// another task, or nothing on a refresh by the same task.
351fn store(stored: &mut Option<Waker>, waker: &Waker) -> Option<Waker> {
352    stored
353        .replace(waker.clone())
354        .filter(|displaced| !displaced.will_wake(waker))
355}