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}