Skip to main content

windows_ioring_sys/
contract.rs

1// Copyright (c) 2026 Mike Grier
2//! [`RingContract`](crate::contract::RingContract) -- this crate's conservation
3//! rules, made executable.
4//!
5//! # Why an oracle rather than more assertions
6//!
7//! Some of what this crate promises is a property of a *sequence*, not of any
8//! single value or call, and a per-value type cannot carry it. "Every SQE that
9//! successfully queues produces exactly one completion" is only observable by
10//! counting pushes against completions across a whole run, and so is "no
11//! registered buffer is still in use once everything has finished".
12//!
13//! Those rules are stated in `DESIGN-NOTES.md` and, before this module, were
14//! checked nowhere. That was not a hypothetical gap: two real defects in this
15//! repository were conservation failures. `Appender::claim` returned early on
16//! a failed write and permanently leaked the arena slot its token held, and a
17//! strategy harness shared one deferred-commit slot between two lanes so half
18//! its commits were never awaited. Both were found by review and by
19//! measurement respectively, and both would have fallen out of a quiescence
20//! check automatically. (The first was a token leak, and the rule that caught
21//! that kind of leak retired with the token API -- see below.)
22//!
23//! # Why it lives here
24//!
25//! The layer that owns an invariant owns the oracle for it. A copy written in
26//! a test harness is a second implementation of the rule rather than a check
27//! of it -- if the two disagree, the harness is what gets "fixed", and the
28//! disagreement is precisely the bug worth finding. Being public means a
29//! consumer can hold its own harness, its own test doubles, or a captured run
30//! to the same definition this crate's tests use.
31//!
32//! # What it checks
33//!
34//! | Rule | Where it comes from |
35//! |---|---|
36//! | Every queued SQE produces exactly one completion | [`DESIGN-NOTES.md`'s category-2 audit](../DESIGN-NOTES.md#one-sqe-one-completion) |
37//! | A push that failed synchronously produces none | the same section: it is un-counted, not merely uncompleted |
38//! | No completion arrives for an operation never pushed | corollary of the above |
39//! | Nothing is outstanding at quiescence | what `IoRing::run_down`'s termination depends on |
40//!
41//! Every rule here is about the **kernel**, not about a caller's bookkeeping.
42//! A fifth row used to sit above -- "every token is claimed, or deliberately
43//! leaked" -- and it retired with the token API: a push now hands back only an
44//! `OperationId`, which owns nothing, so there is nothing a caller could fail
45//! to claim. [D-74](../DESIGN-NOTES.md#d-74) records why that is a retirement
46//! rather than a narrowing.
47//!
48//! What replaced it is **not** `held() == outstanding()`. Those two are not
49//! equal in general and the ring does not compare them: a `_raw` flush or
50//! cancel is outstanding while deliberately stowing no inventory
51//! entry, so a perfectly valid push makes them differ. Reading equality as an
52//! invariant would report that valid push as a conservation failure, which is
53//! the over-constraining this oracle's next section exists to avoid.
54//!
55//! The owned-push invariant is narrower: **every push that stows an entry
56//! retires exactly that entry at the pop which observes its completion**, so
57//! what remains in the inventory is precisely the set of owned pushes whose
58//! completions have not yet been seen. [`IoRing::held`] counts that set, which
59//! is a subset of what [`IoRing::outstanding`] counts: `held() <= outstanding()`,
60//! and the gap is the raw operations in flight.
61//! Teardown checks the matching property rather than the equality: that rundown
62//! succeeded **and** no identity the ring minted is still in flight, which
63//! covers the inventory as well ([D-78](../DESIGN-NOTES.md#d-78)).
64//!
65//! # What it deliberately does **not** check
66//!
67//! Being explicit about this matters as much as the list above: over-
68//! constraining is the same defect as under-specifying, and an oracle that
69//! reports a violation which is not one trains its reader to ignore it.
70//!
71//! - **Completion order.** The ring makes no ordering promise between
72//!   independent operations, so this counts them and never sequences them.
73//!   There is one promised ordering -- a covering flush against the operations
74//!   *preceding* it, and nothing about those following it
75//!   ([D-24](../DESIGN-NOTES.md#d-24), as corrected by
76//!   [D-47](../DESIGN-NOTES.md#d-47-detail)) -- and this oracle does not check
77//!   it. Not because it is unobservable: tag the operations and it is entirely
78//!   observable from the completion stream, which is exactly how
79//!   [tests/flush_barrier.rs](../tests/flush_barrier.rs) tests it and how D-47
80//!   measured the flag's one-sidedness over some 4,500 trials. It is that
81//!   *this* oracle is a conservation checker -- it matches each completion
82//!   against an outstanding claim and never relates that claim to the order
83//!   anything was submitted in, so it holds nothing it could compare against.
84//!
85//!   An observer that does want to check it needs tagged identities, so that a
86//!   popped completion can be attributed to the operation it belongs to. Pop
87//!   order itself is a faithful witness of the order the kernel *posted* those
88//!   completions -- that is precisely the observable D-47 measured, and what
89//!   this crate's contract is stated in terms of. What it does not witness is
90//!   the order the operations *began executing*, which is invisible from user
91//!   mode -- see [D-47](../DESIGN-NOTES.md#d-47-detail) for why the contract is
92//!   stated in terms of completion order and stops there.
93//! - **Whether an operation succeeded.** A failed operation still completes
94//!   exactly once; conservation and success are different questions.
95//! - **Anything about the device.** Whether a flush truly reached durable
96//!   media is invisible here and everywhere else short of a power cut.
97//! - **That a caller pushed anything sensible.** This checks conservation of
98//!   what was pushed, not that pushing it was a good idea.
99
100use std::collections::{HashMap, HashSet, VecDeque};
101use std::fmt;
102
103/// A conservation rule this crate promises, broken.
104#[derive(Clone, Debug, PartialEq, Eq)]
105pub enum Violation {
106    /// A completion arrived carrying `user_data` that no observed push
107    /// produced.
108    ///
109    /// Either the ring completed something twice, or the caller's own
110    /// bookkeeping lost a push -- and distinguishing those is why the message
111    /// says which is possible rather than asserting one.
112    ///
113    /// When the completions reported here come from an [`crate::IoRing`]'s own
114    /// pops, the ring has already checked the identity against what it minted
115    /// and panics on one that is not in flight
116    /// ([D-79](../DESIGN-NOTES.md#d-79)), so from such a ring this points at
117    /// the caller's reporting rather than at the kernel.
118    UnexpectedCompletion {
119        /// The identity the unrecognised completion carried.
120        user_data: usize,
121    },
122    /// Two completions arrived for the same `user_data`.
123    ///
124    /// The "exactly" half of "exactly one completion". Counted separately from
125    /// [`Violation::UnexpectedCompletion`] because a duplicate is a much
126    /// stronger signal than an unrecognised identity: it cannot be explained
127    /// by a caller forgetting to report a push. A ring's own pop panics on a
128    /// duplicate first ([D-79](../DESIGN-NOTES.md#d-79)), so from a real ring
129    /// this means the caller reported one completion twice.
130    DuplicateCompletion {
131        /// The identity that completed more than once.
132        user_data: usize,
133    },
134    /// An operation was pushed and never completed.
135    Outstanding {
136        /// The identity that was pushed and never completed.
137        user_data: usize,
138    },
139    /// A registered buffer still had operations outstanding at quiescence.
140    BufferStillInUse {
141        /// Position of the buffer within its registration.
142        index: u32,
143        /// How many operations it still had in flight.
144        outstanding: usize,
145    },
146}
147
148impl fmt::Display for Violation {
149    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
150        match self {
151            Self::UnexpectedCompletion { user_data } => write!(
152                f,
153                "completion for user_data {user_data:#x} matches no observed push: either it \
154                 completed twice, or a push was not reported to the contract"
155            ),
156            Self::DuplicateCompletion { user_data } => write!(
157                f,
158                "user_data {user_data:#x} completed more than once, but every queued SQE \
159                 produces exactly one completion"
160            ),
161            Self::Outstanding { user_data } => {
162                write!(f, "user_data {user_data:#x} was pushed and never completed")
163            }
164            Self::BufferStillInUse { index, outstanding } => write!(
165                f,
166                "registered buffer {index} still has {outstanding} operation(s) outstanding at \
167                 quiescence"
168            ),
169        }
170    }
171}
172
173/// What one operation *still being tracked* is known to have done so far.
174///
175/// Terminal outcomes are deliberately absent. An operation that finishes --
176/// which since [D-74](../DESIGN-NOTES.md#d-74) means simply that its
177/// completion was popped -- leaves this map entirely and its identity moves
178/// to the bounded history described
179/// on [`RingContract`], because retaining a terminal entry per operation is
180/// what made the oracle grow without limit (M28.2).
181#[derive(Clone, Copy, Debug, PartialEq, Eq)]
182enum State {
183    /// Queued, no completion seen.
184    ///
185    /// **One push state, as of `D-74`.** There were two, because a `_raw`
186    /// push returned a bare `user_data` while the others returned a
187    /// `Token`, and conflating them reported a leak for every
188    /// tokenless push -- a violation the caller could not satisfy, since
189    /// there was no token to claim. With the ring holding what an operation
190    /// carries, no push hands the caller anything to lose, so the distinction
191    /// has nothing left to distinguish.
192    Pushed,
193}
194
195/// How many finished identities the oracle remembers for duplicate diagnosis.
196///
197/// A **diagnostic window, not a correctness parameter.** Nothing is missed
198/// when a duplicate falls outside it: the completion is still reported, as
199/// [`Violation::UnexpectedCompletion`] rather than
200/// [`Violation::DuplicateCompletion`]. What the window buys is the *stronger*
201/// of the two claims, which the variants' own docs explain is worth keeping --
202/// a duplicate cannot be explained away by a caller forgetting to report a
203/// push, and an unrecognised identity can.
204///
205/// Changing it is not a breaking change. It trades a fixed amount of memory
206/// for how far back a duplicate is still named precisely.
207const FINISHED_HISTORY: usize = 1024;
208
209/// An executable form of this crate's conservation rules.
210///
211/// Fed by the caller rather than wired into [`crate::Batch`], for two reasons.
212/// A ring can be driven through `push_raw` and the `_raw` entry points without
213/// any of this crate's bookkeeping being involved at all, so an internal hook
214/// would silently cover less than it appears to. And a consumer validating its
215/// own harness needs to drive the same rules from outside.
216///
217/// # What this costs to run, which is bounded (M28.2)
218///
219/// Memory here is a function of **operations in flight**, not of operations
220/// ever performed. An operation that finishes leaves the tracking map, so a
221/// consumer that pushes and claims forever holds a map whose size follows its
222/// own concurrency rather than its uptime.
223///
224/// That was not true before M28.2: a claimed operation was marked `Completed`
225/// and kept, so the oracle retained one entry per operation for the life of
226/// the process. The sample never showed it because it appends 24 records, and
227/// nothing documented it -- so a long-running consumer following this crate's
228/// own recommendation grew without limit. It is also why an always-on checked
229/// inventory was ruled out in `M23.3`, since a check that cannot be left
230/// running is not a check.
231///
232/// Retiring an entry would lose the ability to call a later completion for it
233/// a *duplicate* rather than merely unrecognised, so the last
234/// `FINISHED_HISTORY` finished identities are remembered for that purpose
235/// alone. Beyond that window a duplicate is still reported, under the weaker
236/// name.
237///
238/// # Example
239///
240/// ```
241/// use windows_ioring_sys::contract::RingContract;
242///
243/// let mut contract = RingContract::new();
244/// contract.observe_push(0x1234);
245/// contract.observe_completion(0x1234);
246/// assert!(contract.check_quiescent().is_empty());
247/// ```
248#[derive(Debug, Default)]
249pub struct RingContract {
250    /// Operations still being tracked. Terminal outcomes are not kept here;
251    /// see the type's own documentation for why.
252    operations: HashMap<usize, State>,
253    /// Identities that finished, most recent last, capped at
254    /// [`FINISHED_HISTORY`]. Paired with `finished_set` so the membership test
255    /// stays O(1) -- the queue exists only to know which one to evict.
256    finished: VecDeque<usize>,
257    /// Membership index over `finished`, held in step with it.
258    finished_set: HashSet<usize>,
259    /// Per registered-buffer outstanding counts, as the caller reports them.
260    buffers: HashMap<u32, usize>,
261    violations: Vec<Violation>,
262}
263
264impl RingContract {
265    /// A contract with nothing observed yet.
266    #[must_use]
267    pub fn new() -> Self {
268        Self::default()
269    }
270
271    /// Record that an SQE was successfully queued with this `user_data`.
272    ///
273    /// Report only pushes that **queued**. A `Build*` call that failed
274    /// synchronously releases its reservation and produces no completion, so
275    /// reporting it would manufacture an [`Violation::Outstanding`] at
276    /// teardown.
277    ///
278    /// There is one push observer, as of `M28.5`. There were two:
279    /// `observe_push` recorded a push that returned a token, and
280    /// `observe_tokenless_push` recorded an SQE that carried no token to
281    /// claim -- the `_raw` flush and cancel entry points -- because reporting
282    /// one through `observe_push` produced a leak violation its caller had no
283    /// way to satisfy, there being no token to claim. [D-74](../DESIGN-NOTES.md#d-74)
284    /// removed the leak rule, which made the two identical in body as well as
285    /// in purpose, and two names for one behaviour is a restatement waiting
286    /// for the next change to reach only one of them.
287    pub fn observe_push(&mut self, user_data: usize) {
288        self.operations.insert(user_data, State::Pushed);
289    }
290
291    /// Record a completion popped from the ring.
292    pub fn observe_completion(&mut self, user_data: usize) {
293        match self.operations.get(&user_data) {
294            None => {
295                // Not tracked. Either it finished already -- which the
296                // history can still prove, and which is the stronger claim --
297                // or no observed push ever produced it.
298                if self.finished_set.contains(&user_data) {
299                    self.violations
300                        .push(Violation::DuplicateCompletion { user_data });
301                } else {
302                    self.violations
303                        .push(Violation::UnexpectedCompletion { user_data });
304                }
305            }
306            Some(State::Pushed) => {
307                // Terminal. A completion used to be *provisional* here,
308                // parked in a leaked state until `observe_claim` corrected
309                // it, because a completion whose token was never claimed was
310                // a real leak. The pop is the claim now (`D-74`), so there is
311                // no second step for the caller to omit and nothing for this
312                // to wait on.
313                self.retire(user_data);
314            }
315        }
316    }
317
318    /// Retire a finished identity: out of the tracking map, into the bounded
319    /// history.
320    ///
321    /// This is the one place an operation stops costing memory proportional to
322    /// how many have run, which is what `M28.2` was about.
323    fn retire(&mut self, user_data: usize) {
324        self.operations.remove(&user_data);
325        if self.finished_set.insert(user_data) {
326            self.finished.push_back(user_data);
327            if self.finished.len() > FINISHED_HISTORY
328                && let Some(evicted) = self.finished.pop_front()
329            {
330                self.finished_set.remove(&evicted);
331            }
332        }
333    }
334
335    /// How many operations have been pushed and not yet completed.
336    ///
337    /// The same number [`RingContract::check_quiescent`] turns into one
338    /// [`Violation::Outstanding`] each, offered directly because a consumer
339    /// usually wants the count rather than the diagnosis -- a drain loop asks
340    /// "is anything left?", not "what is wrong?".
341    ///
342    /// **This exists because two callers had already written it by hand**, and
343    /// both were a restatement of what this type already knew.
344    /// `epoch_log`'s appender kept a `usize` beside its contract and drove it
345    /// at every push and every completion, which is precisely the
346    /// record-the-same-event-twice shape that
347    /// [D-55](../DESIGN-NOTES.md#d-55) exists to remove. The other counted
348    /// `Outstanding` violations out of `check_quiescent`, allocating a `Vec`
349    /// to answer a question about a map's length.
350    #[must_use]
351    pub fn in_flight(&self) -> usize {
352        self.operations.len()
353    }
354    /// Record a registered buffer's outstanding count, as
355    /// [`crate::RegisteredBuffers::outstanding`] reports it.
356    pub fn observe_buffer(&mut self, index: u32, outstanding: usize) {
357        self.buffers.insert(index, outstanding);
358    }
359
360    /// Violations seen so far, without asking about quiescence.
361    #[must_use]
362    pub fn violations(&self) -> &[Violation] {
363        &self.violations
364    }
365
366    /// Every violation, including operations still outstanding and buffers
367    /// still in use.
368    ///
369    /// Call once everything is expected to have finished. An empty result is
370    /// the claim that nothing was lost.
371    #[must_use]
372    pub fn check_quiescent(&self) -> Vec<Violation> {
373        let mut all = self.violations.clone();
374
375        // Sorted so a failure reads the same way twice. `HashMap` iteration
376        // order is deliberately unspecified, and an oracle whose output
377        // reorders between runs is one nobody can diff.
378        let mut pending: Vec<_> = self
379            .operations
380            .iter()
381            // Not a `filter_map`: since `M28.2` retired the terminal states,
382            // every entry still tracked is a violation at quiescence.
383            .map(|(user_data, state)| match state {
384                State::Pushed => Violation::Outstanding {
385                    user_data: *user_data,
386                },
387            })
388            .collect();
389        pending.sort_by_key(|violation| match violation {
390            Violation::Outstanding { user_data } => *user_data,
391            _ => 0,
392        });
393        all.extend(pending);
394
395        let mut busy: Vec<_> = self
396            .buffers
397            .iter()
398            .filter(|(_, outstanding)| **outstanding > 0)
399            .map(|(index, outstanding)| Violation::BufferStillInUse {
400                index: *index,
401                outstanding: *outstanding,
402            })
403            .collect();
404        busy.sort_by_key(|violation| match violation {
405            Violation::BufferStillInUse { index, .. } => *index,
406            _ => 0,
407        });
408        all.extend(busy);
409
410        all
411    }
412
413    /// Panic with every violation if the contract was broken.
414    ///
415    /// The convenience form for a test. Reports **all** violations rather than
416    /// the first, because they are usually one cause and seeing only one of
417    /// them invites fixing a symptom.
418    ///
419    /// # Panics
420    ///
421    /// If [`RingContract::check_quiescent`] finds anything.
422    pub fn assert_quiescent(&self) {
423        let violations = self.check_quiescent();
424        assert!(
425            violations.is_empty(),
426            "the ring contract was broken:\n{}",
427            violations
428                .iter()
429                .map(|violation| format!("  - {violation}"))
430                .collect::<Vec<_>>()
431                .join("\n")
432        );
433    }
434}
435
436#[cfg(test)]
437mod tests;