Skip to main content

windows_ioring_sys/sys/
resolver.rs

1// Copyright (c) Mike Grier
2//! A seeded resolver over the permitted kernel response space (M26.3).
3//!
4//! This is the thing [RESPONSE-SPACE.md](../../RESPONSE-SPACE.md) was written
5//! for. It implements [`Responses`] by answering the submission-path calls
6//! itself, choosing a point in that space from a seed.
7//!
8//! # It asserts nothing about Windows
9//!
10//! The distinction [D-52](../../DESIGN-NOTES.md#d-52) turns on, restated here
11//! because it is what makes this defensible where a mock was refused twice: a
12//! mock encodes a belief about what the platform *does*, and can be wrong
13//! about it. This encodes a specification of what this crate will *tolerate*,
14//! and the assertions made against it are about this crate. There is no belief
15//! here to be wrong -- only a space that could be too narrow, which is a
16//! reviewable defect rather than a hidden one.
17//!
18//! So every freedom below cites the clause that permits it, and every
19//! restriction cites the clause that requires it. A clause no code cites is
20//! visibly unimplemented; a behaviour citing no clause is a resolver inventing
21//! a platform.
22//!
23//! # Permissions are configurable; constraints are not
24//!
25//! [`ResolverConfig`] has a switch per `RS-P-n` and none for any `RS-C-n`, and
26//! that asymmetry is deliberate rather than incidental. A permission is a
27//! freedom a test may wish to narrow in order to isolate another -- a test
28//! about ordering does not want arbitrary operation failures on top. A
29//! constraint is what makes this crate's code reviewable at all, so a knob
30//! that relaxed one would let a test quietly assert against a platform that
31//! cannot exist. Constraints are therefore only reachable by editing this
32//! file, which is what the sabotage manifest does to show they are
33//! load-bearing.
34//!
35//! # What drives resolution
36//!
37//! A real kernel completes pending work on its own. This has no background, so
38//! its only clock is being consulted:
39//!
40//! - **A submit resolves.** Each still-unresolved operation independently
41//!   either completes now or pends (`RS-P-1`).
42//! - **A pop only prevents starvation.** It advances every operation's
43//!   deferral count and posts anything that has run out, but it flips no
44//!   coins. Without that split a consumer polling [`crate::IoRing::try_pop`]
45//!   would see its first poll resolve the work, and "the operation pended" --
46//!   the thing `RS-P-1` exists to let a test observe -- would be unobservable.
47//!
48//! The deferral bound is what makes `RS-C-1` finite. It is a property of this
49//! resolver, not of the space: the space deliberately carries no rates, and a
50//! bound on how long a resolver may defer is a mechanism for satisfying a
51//! constraint rather than a claim about how often Windows pends.
52//!
53//! # Registering one, and the one ordering requirement left
54//!
55//! [`Resolver::register`] registers a resolver for one ring
56//! ([D-85](../../DESIGN-NOTES.md#d-85)). It answers that ring's calls on
57//! whatever thread makes them, which is why it is `Send`, until the ring
58//! drops.
59//!
60//! **Register before the ring's first operation.** The resolver answers for
61//! operations the kernel never saw, so it must see every one: an operation
62//! the kernel built before it was registered is one it would be asked to
63//! complete without having been told of. Registering straight after creating
64//! the ring is the shape to reach for.
65//!
66//! The other requirement this module used to state -- that the resolver
67//! outlive the ring's rundown, which it answers -- is no longer the caller's:
68//! a registration ends at its ring's drop, after the rundown, and nothing
69//! the caller drops can end it sooner.
70
71use std::collections::VecDeque;
72use std::ffi::c_void;
73use std::sync::{Arc, Mutex, PoisonError};
74
75use windows_sys::Win32::Storage::FileSystem::{
76    FILE_FLUSH_MODE, IORING_BUFFER_INFO, IORING_BUFFER_REF, IORING_CQE, IORING_HANDLE_REF,
77    IOSQE_FLAGS_DRAIN_PRECEDING_OPS,
78};
79use windows_sys::Win32::System::Threading::SetEvent;
80use windows_sys::core::HRESULT;
81
82use super::Responses;
83use crate::IoRing;
84
85/// Environment override for the resolver seed (decimal, or `0x` hex).
86///
87/// **A third axis, kept separate on purpose.**
88/// [generated_sequences.rs](../../tests/generated_sequences.rs) already
89/// carries two -- its own sequence seed and the guard allocator's -- and
90/// [D-41](../../DESIGN-NOTES.md#d-41)'s discipline is that one number replays one
91/// thing. Folding the resolution choices into either of those would produce a
92/// replay that reproduced part of a run and not the rest, which is worse than
93/// no replay at all because it looks like one.
94pub const SEED_VAR: &str = "WINDOWS_IORING_RESOLVER_SEED";
95
96/// How many consultations an operation may pend across before this resolver
97/// posts it regardless.
98///
99/// This is how `RS-C-1` ("every submitted operation eventually completes")
100/// becomes finite rather than merely eventual. Four is chosen so that a
101/// pending operation survives a few polls -- enough for a test to observe that
102/// it pended -- without a rundown loop taking long enough to look like a hang.
103const MAX_DEFERRALS: u32 = 4;
104
105/// How many consecutive submits `RS-P-7` may fail before this resolver lets
106/// one through.
107///
108/// Same role as [`MAX_DEFERRALS`], for the other way an operation can be made
109/// to wait: a resolver free to fail every submit forever would never complete
110/// anything, which `RS-C-1` forbids.
111const MAX_CONSECUTIVE_SUBMIT_FAILURES: u32 = 2;
112
113/// `S_OK`.
114const S_OK: HRESULT = 0;
115/// `S_FALSE`, which is how `PopIoRingCompletion` reports an empty queue.
116const S_FALSE: HRESULT = 1;
117/// `IORING_E_WAIT_TIMEOUT`, which `SubmitIoRing` documents as "all operations
118/// were submitted without error and the subsequent wait timed out".
119///
120/// Spelled by derivation for the reason `ring.rs` records at its own copy: the
121/// name is a macro over `HRESULT_FROM_WIN32(ERROR_TIMEOUT)` rather than a
122/// `FACILITY_IORING` code, so the bindings emit no constant to import.
123const IORING_E_WAIT_TIMEOUT: HRESULT =
124    (0x8007_0000_u32 | windows_sys::Win32::Foundation::ERROR_TIMEOUT) as HRESULT;
125/// `HRESULT_FROM_WIN32(ERROR_NOT_ENOUGH_MEMORY)`, the failure this resolver
126/// reports for a submit it declines under `RS-P-7`.
127///
128/// Deliberately **not** `IORING_E_WAIT_TIMEOUT`: that code carries a positive
129/// guarantee that every entry was submitted, so using it here would make the
130/// resolver claim the work went in while holding it back.
131const SUBMIT_FAILED: HRESULT =
132    (0x8007_0000_u32 | windows_sys::Win32::Foundation::ERROR_NOT_ENOUGH_MEMORY) as HRESULT;
133
134/// Which permissions this resolver exercises.
135///
136/// Every field names its clause. All default to `true` -- the widest point in
137/// the space -- so that narrowing is a visible, deliberate act in a test
138/// rather than something a resolver quietly failed to do.
139#[derive(Debug, Clone, Copy, PartialEq, Eq)]
140pub struct ResolverConfig {
141    /// `RS-P-1`: an operation may complete inside `SubmitIoRing`, or pend.
142    ///
143    /// With this off every operation completes inside the submit that carried
144    /// it, which is the narrowest reading of the clause and the one a test
145    /// isolating some *other* freedom usually wants.
146    pub may_pend: bool,
147    /// `RS-P-2`: completion order is unconstrained.
148    ///
149    /// With this off completions are posted in submission order. That is not
150    /// a promise Windows makes -- [D-47](../../DESIGN-NOTES.md#d-47) measured it
151    /// broken -- so a test turning this off is narrowing to isolate, never
152    /// asserting FIFO.
153    pub may_reorder: bool,
154    /// `RS-P-3`: an operation may fail individually.
155    pub may_fail_operations: bool,
156    /// `RS-P-4`: a wait may expire, including when completions are available.
157    pub may_expire_waits: bool,
158    /// `RS-P-5`: a wait may return successfully with nothing poppable.
159    pub may_wake_empty: bool,
160    /// `RS-P-6`: a completion posted while the queue was already non-empty
161    /// need produce no signal.
162    ///
163    /// With this off every post signals. That is *wider* than the platform --
164    /// [D-19](../../DESIGN-NOTES.md#d-19) measured the event as edge triggered --
165    /// so it exists to show the edge behaviour is load-bearing, not as a
166    /// configuration any consumer should rely on.
167    pub edge_triggered_signal: bool,
168    /// `RS-P-7`: a submit may fail, leaving built operations queued for a
169    /// later one.
170    pub may_fail_submits: bool,
171    /// `RS-P-8`: a successful transfer may report fewer bytes than requested.
172    ///
173    /// With this off every successful read or write reports the full
174    /// requested length. That is the behaviour of an ordinary file on a local
175    /// volume, and so the narrowing a test wants when its subject is
176    /// something else -- but it is *narrower* than the platform, because this
177    /// crate does not constrain what kind of handle a caller registers.
178    pub may_transfer_partially: bool,
179}
180
181impl Default for ResolverConfig {
182    fn default() -> Self {
183        Self {
184            may_pend: true,
185            may_reorder: true,
186            may_fail_operations: true,
187            may_expire_waits: true,
188            may_wake_empty: true,
189            edge_triggered_signal: true,
190            may_fail_submits: true,
191            may_transfer_partially: true,
192        }
193    }
194}
195
196impl ResolverConfig {
197    /// Every permission off: operations complete inside their submit, in
198    /// order, successfully, and no wait misbehaves.
199    ///
200    /// The narrowest point in the space, and therefore the *weakest* test. It
201    /// exists so a test can turn on exactly the one freedom it is about, and
202    /// so a reader can tell that choice from an accident.
203    #[must_use]
204    pub const fn narrowest() -> Self {
205        Self {
206            may_pend: false,
207            may_reorder: false,
208            may_fail_operations: false,
209            may_expire_waits: false,
210            may_wake_empty: false,
211            edge_triggered_signal: true,
212            may_fail_submits: false,
213            may_transfer_partially: false,
214        }
215    }
216}
217
218/// What a resolution actually did, readable while the resolver is registered.
219///
220/// A test that narrows the config to isolate one freedom needs to know the
221/// freedom was *exercised*; otherwise a green run means only that the seed
222/// never took that branch. These counters are how a test asserts against the
223/// run rather than against the configuration.
224#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
225pub struct ResolverStats {
226    /// Operations built (any `Build*` call that returned success).
227    pub built: usize,
228    /// Operations moved from built to submitted.
229    pub submitted: usize,
230    /// Completions posted to the completion queue.
231    pub posted: usize,
232    /// Completions handed to a `PopIoRingCompletion`.
233    pub popped: usize,
234    /// Posts that carried a failure result (`RS-P-3` exercised).
235    pub failed_operations: usize,
236    /// Submits declined, leaving built operations queued (`RS-P-7`).
237    pub failed_submits: usize,
238    /// Waits answered as expired (`RS-P-4`).
239    pub expired_waits: usize,
240    /// Waits answered successfully with nothing poppable (`RS-P-5`).
241    pub empty_wakes: usize,
242    /// Event signals raised (`RS-P-6`; zero when no event is attached).
243    pub signals: usize,
244    /// Posts that took something other than the oldest unresolved operation
245    /// (`RS-P-2` exercised -- reordering actually happened, as distinct from
246    /// having been permitted).
247    pub reorderings: usize,
248    /// Times an operation was left unresolved by a consultation (`RS-P-1`
249    /// exercised -- something actually pended).
250    pub deferrals: usize,
251    /// Times a drain-flagged operation was held back because something queued
252    /// before it was still unresolved (`RS-C-4` actually bit, as distinct from
253    /// having been vacuously satisfied).
254    pub barrier_holds: usize,
255    /// Successful transfers reported short under `RS-P-8`.
256    pub partial_transfers: usize,
257}
258
259/// A live view of [`ResolverStats`] for a resolver in use.
260///
261/// Shared across threads, because a resolver registered for a ring answers
262/// that ring's calls on whatever thread makes them
263/// ([D-85](../../DESIGN-NOTES.md#d-85)).
264#[derive(Debug, Clone)]
265pub struct ResolverWatch(Arc<Mutex<ResolverStats>>);
266
267impl ResolverWatch {
268    /// The statistics as of now.
269    #[must_use]
270    pub fn stats(&self) -> ResolverStats {
271        *self.0.lock().unwrap_or_else(PoisonError::into_inner)
272    }
273}
274
275/// The ring's completion event, as the resolver keeps it: borrowed, never
276/// owned, and signalled from whichever thread the resolver is answering on.
277#[derive(Debug, Clone, Copy)]
278struct CompletionEvent(*mut c_void);
279
280// SAFETY: an event handle is a kernel object reference usable from any thread
281// in the process; the resolver neither owns nor closes it, and `SetEvent` is
282// the only call it makes with it.
283unsafe impl Send for CompletionEvent {}
284
285/// One operation the resolver is answering for.
286#[derive(Debug, Clone, Copy)]
287struct Op {
288    /// `RS-C-2`: echoed into the completion exactly as supplied.
289    user_data: usize,
290    /// Build order, ring-wide. `RS-C-4` is stated over this.
291    seq: u64,
292    /// Carries `IOSQE_FLAGS_DRAIN_PRECEDING_OPS`.
293    barrier: bool,
294    /// Consultations survived without being posted.
295    deferrals: u32,
296    /// Bytes asked for, when this operation is a transfer. `None` for flush
297    /// and cancel, whose `Information` is not a byte count at all -- which is
298    /// why `RS-P-8` is stated over transfers rather than over completions.
299    requested: Option<u32>,
300}
301
302/// A resolver over [RESPONSE-SPACE.md](../../RESPONSE-SPACE.md).
303///
304/// See the module documentation for what drives resolution and for the
305/// ordering requirement registering one imposes.
306pub struct Resolver {
307    seed: u64,
308    state: u64,
309    config: ResolverConfig,
310    /// Built, not yet submitted. `RS-C-3` is exactly the rule that nothing
311    /// here is eligible to complete.
312    staged: Vec<Op>,
313    /// Submitted, not yet posted. Held in build order, which is what lets
314    /// `RS-C-4` be decided by position.
315    pool: Vec<Op>,
316    /// Posted, not yet popped. The completion queue: identity, result code,
317    /// and the `Information` the completion will carry (`RS-P-8`).
318    posted: VecDeque<(usize, HRESULT, usize)>,
319    next_seq: u64,
320    consecutive_submit_failures: u32,
321    /// The ring's completion event, if one has been attached. Borrowed, never
322    /// owned: the ring closes it, and this resolver must not outlive the ring.
323    event: CompletionEvent,
324    stats: Arc<Mutex<ResolverStats>>,
325}
326
327impl std::fmt::Debug for Resolver {
328    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
329        f.debug_struct("Resolver")
330            .field("seed", &format_args!("0x{:016X}", self.seed))
331            .field("config", &self.config)
332            .field("staged", &self.staged.len())
333            .field("pool", &self.pool.len())
334            .field("posted", &self.posted.len())
335            .field(
336                "stats",
337                &*self.stats.lock().unwrap_or_else(PoisonError::into_inner),
338            )
339            .finish()
340    }
341}
342
343impl Resolver {
344    /// A resolver at the widest point in the space, from an explicit seed.
345    #[must_use]
346    pub fn new(seed: u64) -> Self {
347        Self::with_config(seed, ResolverConfig::default())
348    }
349
350    /// A resolver exercising exactly the permissions `config` allows.
351    #[must_use]
352    pub fn with_config(seed: u64, config: ResolverConfig) -> Self {
353        Self {
354            seed,
355            // `| 1` so a zero seed is not a fixed point of the mixer.
356            state: seed.wrapping_mul(0x9E37_79B9_7F4A_7C15) | 1,
357            config,
358            staged: Vec::new(),
359            pool: Vec::new(),
360            posted: VecDeque::new(),
361            next_seq: 0,
362            consecutive_submit_failures: 0,
363            event: CompletionEvent(std::ptr::null_mut()),
364            stats: Arc::new(Mutex::new(ResolverStats::default())),
365        }
366    }
367
368    /// The seed this resolver is replaying.
369    #[must_use]
370    pub fn seed(&self) -> u64 {
371        self.seed
372    }
373
374    /// A live view of what this resolution has done so far.
375    #[must_use]
376    pub fn watch(&self) -> ResolverWatch {
377        ResolverWatch(Arc::clone(&self.stats))
378    }
379
380    /// The seed for this run: pinned from [`SEED_VAR`] when set, otherwise
381    /// derived from the clock so successive runs explore different points.
382    ///
383    /// # Panics
384    ///
385    /// If [`SEED_VAR`] is set to something that is neither decimal nor `0x`
386    /// hex. A seed that silently fell back to the clock would make the
387    /// variable look like it worked while the run it was pinning drifted.
388    #[must_use]
389    pub fn seed_from_env() -> u64 {
390        match std::env::var(SEED_VAR) {
391            Ok(text) => {
392                let trimmed = text.trim();
393                trimmed
394                    .strip_prefix("0x")
395                    .or_else(|| trimmed.strip_prefix("0X"))
396                    .map_or_else(
397                        || trimmed.parse::<u64>().ok(),
398                        |hex| u64::from_str_radix(hex, 16).ok(),
399                    )
400                    .unwrap_or_else(|| {
401                        panic!("{SEED_VAR} is set to {text:?}, which is neither decimal nor 0x hex")
402                    })
403            }
404            Err(_) => std::time::SystemTime::now()
405                .duration_since(std::time::UNIX_EPOCH)
406                .map_or(0x243F_6A88_85A3_08D3, |elapsed| elapsed.as_nanos() as u64),
407        }
408    }
409
410    /// The command that replays this resolver's choices.
411    ///
412    /// Print it from any test that registers one. It names **only this axis**:
413    /// a test that also draws on another seeded source announces that source
414    /// too, because a partial replay is the failure `SEED_VAR` documents.
415    #[must_use]
416    pub fn replay_hint(&self) -> String {
417        format!("$env:{SEED_VAR}='0x{:016X}'", self.seed)
418    }
419
420    /// Register this resolver for `ring`, returning a live view of what it
421    /// does. It answers the ring's calls on whatever thread makes them until
422    /// the ring drops, after the ring's rundown (D-85).
423    ///
424    /// Register straight after creating the ring, before its first
425    /// operation; see the module documentation.
426    ///
427    /// # Panics
428    ///
429    /// If `ring` already has a responder registered.
430    pub fn register<T, X, K>(self, ring: &IoRing<T, X, K>) -> ResolverWatch {
431        let watch = self.watch();
432        // `Registration` ends nothing when dropped; the ring's drop ends it.
433        let _ = super::register(ring, Box::new(self));
434        watch
435    }
436
437    /// SplitMix64, matching the generator this crate already uses
438    /// ([D-41](../../DESIGN-NOTES.md#d-41)): one number replays a whole run.
439    fn next(&mut self) -> u64 {
440        self.state = self.state.wrapping_add(0x9E37_79B9_7F4A_7C15);
441        let mut z = self.state;
442        z = (z ^ (z >> 30)).wrapping_mul(0xBF58_476D_1CE4_E5B9);
443        z = (z ^ (z >> 27)).wrapping_mul(0x94D0_49BB_1331_11EB);
444        z ^ (z >> 31)
445    }
446
447    /// True with probability `percent`.
448    ///
449    /// The weighting is a property of this resolver, never of the space --
450    /// [RESPONSE-SPACE.md](../../RESPONSE-SPACE.md) carries no rates, deliberately,
451    /// because a space with observed probabilities in it is a recording.
452    fn chance(&mut self, percent: u64) -> bool {
453        self.next() % 100 < percent
454    }
455
456    fn record(&self, f: impl FnOnce(&mut ResolverStats)) {
457        f(&mut self.stats.lock().unwrap_or_else(PoisonError::into_inner));
458    }
459
460    /// Record a built operation and return the success every `Build*` call
461    /// returns when it queues an SQE.
462    fn build(&mut self, user_data: usize, flags: i32, requested: Option<u32>) -> HRESULT {
463        let seq = self.next_seq;
464        self.next_seq += 1;
465        self.staged.push(Op {
466            user_data,
467            seq,
468            barrier: flags & IOSQE_FLAGS_DRAIN_PRECEDING_OPS != 0,
469            deferrals: 0,
470            requested,
471        });
472        self.record(|s| s.built += 1);
473        S_OK
474    }
475
476    /// Post one completion, choosing which under `RS-P-2` and `RS-C-4`.
477    ///
478    /// Returns false only when there is nothing to post.
479    fn post_one(&mut self) -> bool {
480        if self.pool.is_empty() {
481            return false;
482        }
483
484        // `RS-C-4`: no operation queued before a drain-flagged operation may
485        // complete after it. `pool` is in build order, so the set queued
486        // before `pool[i]` is exactly `pool[..i]` -- a barrier is therefore
487        // eligible only when it is the oldest unresolved operation.
488        //
489        // That reduction is the whole of this constraint's implementation, and
490        // it is load-bearing rather than descriptive: it holds only while the
491        // pool stays sorted by build order. An edit that sorted or reshuffled
492        // the pool would relax `RS-C-4` to nothing while every line below kept
493        // reading as though it still applied, so the invariant is asserted
494        // rather than asserted-in-a-comment.
495        debug_assert!(
496            self.pool.windows(2).all(|pair| pair[0].seq < pair[1].seq),
497            "the pool must stay in build order or RS-C-4's positional test is meaningless"
498        );
499        //
500        // Note which way this resolves against `RS-P-2`: ordering is
501        // "unconstrained" as a permission, and a constraint overrides a
502        // permission. The hold-back half is *not* constrained -- D-24 claimed
503        // it and D-47 withdrew the claim -- so an operation queued after a
504        // barrier is eligible at any time, including before the barrier.
505        let eligible: Vec<usize> = (0..self.pool.len())
506            .filter(|&i| i == 0 || !self.pool[i].barrier)
507            .collect();
508        let held = self.pool.len() - eligible.len();
509        if held > 0 {
510            self.record(|s| s.barrier_holds += 1);
511        }
512
513        let pick = if self.config.may_reorder {
514            // `RS-P-2`: uniform over everything eligible. Repeated to
515            // exhaustion this is a uniform shuffle of each barrier-delimited
516            // segment, which is the widest order the constraint leaves.
517            let draw = self.next() % eligible.len() as u64;
518            eligible[usize::try_from(draw).expect("a modulus of a usize length fits a usize")]
519        } else {
520            eligible[0]
521        };
522        if pick != 0 {
523            self.record(|s| s.reorderings += 1);
524        }
525
526        let op = self.pool.remove(pick);
527
528        // `RS-P-3`: any single operation may fail while its neighbours
529        // succeed. The *set* of codes is a property of this resolver; the
530        // space enumerates none and says a consumer must not depend on the
531        // set being small, so this draws across the whole Win32 facility
532        // rather than from a handful of plausible ones.
533        let result = if self.config.may_fail_operations && self.chance(20) {
534            self.record(|s| s.failed_operations += 1);
535            let code = self.next() & 0xFFFF;
536            (0x8007_0000_u32 | u32::try_from(code).expect("masked to 16 bits")) as HRESULT
537        } else {
538            S_OK
539        };
540
541        let was_empty = self.posted.is_empty();
542        // `RS-P-8`: a successful transfer may report fewer bytes than were
543        // asked for. Documented for non-blocking byte-mode pipes -- and this
544        // crate constrains the handle type not at all -- so a consumer must
545        // read the count rather than assume it. A failed operation transfers
546        // nothing, and flush and cancel carry no byte count, so neither is
547        // eligible.
548        let information = match (result == S_OK, op.requested) {
549            (true, Some(requested)) => {
550                if self.config.may_transfer_partially && requested > 0 && self.chance(8) {
551                    self.record(|s| s.partial_transfers += 1);
552                    // Strictly short, including zero: a caller who loops on
553                    // the remainder must tolerate making no progress rather
554                    // than assuming every completion advances it.
555                    let short = self.next() % u64::from(requested);
556                    usize::try_from(short).expect("short count fits, it is below a u32")
557                } else {
558                    usize::try_from(requested).expect("a u32 fits a usize on this target")
559                }
560            }
561            _ => 0,
562        };
563        self.posted.push_back((op.user_data, result, information));
564        self.record(|s| s.posted += 1);
565
566        // `RS-P-6`: the completion event fires on the empty-to-non-empty
567        // edge, so a completion arriving behind another produces no signal of
568        // its own (D-19, measured). With `edge_triggered_signal` off this
569        // signals every post, which is wider than the platform and exists to
570        // show the edge is load-bearing.
571        if !self.event.0.is_null() && (was_empty || !self.config.edge_triggered_signal) {
572            self.record(|s| s.signals += 1);
573            // SAFETY: `event` was handed to `set_completion_event` by the ring
574            // that owns it, and this resolver's registration ends only as that
575            // ring drops (D-85), so the ring is not gone yet.
576            unsafe { SetEvent(self.event.0) };
577        }
578        true
579    }
580
581    /// Advance resolution by one consultation.
582    ///
583    /// `flip` distinguishes the two clocks the module documents: a submit
584    /// flips coins, a pop only rescues operations that have run out of
585    /// deferrals.
586    fn tick(&mut self, flip: bool) {
587        if self.pool.is_empty() {
588            return;
589        }
590
591        let mut want = 0_usize;
592        for i in 0..self.pool.len() {
593            let forced = self.pool[i].deferrals >= MAX_DEFERRALS;
594            // `RS-P-1`: each operation independently completes now or pends.
595            // A forced post is `RS-C-1` overriding that permission, which is
596            // the same precedence `RS-C-4` takes over `RS-P-2`.
597            if forced || (flip && (!self.config.may_pend || self.chance(50))) {
598                want += 1;
599            }
600        }
601
602        let deferred = self.pool.len() - want.min(self.pool.len());
603        if deferred > 0 {
604            self.record(|s| s.deferrals += deferred);
605        }
606        for op in &mut self.pool {
607            op.deferrals += 1;
608        }
609
610        for _ in 0..want {
611            if !self.post_one() {
612                break;
613            }
614        }
615    }
616}
617
618impl Responses for Resolver {
619    unsafe fn submit(
620        &mut self,
621        _ring: *mut c_void,
622        wait_operations: u32,
623        _milliseconds: u32,
624        submitted: *mut u32,
625    ) -> HRESULT {
626        // `RS-P-7`: a failed submit leaves already-built operations queued for
627        // a later submit -- there is no rewind once `Build*` returns (D-5).
628        // Bounded, because a resolver that failed every submit forever would
629        // never complete anything and `RS-C-1` forbids that.
630        if self.config.may_fail_submits
631            && !self.staged.is_empty()
632            && self.consecutive_submit_failures < MAX_CONSECUTIVE_SUBMIT_FAILURES
633            && self.chance(15)
634        {
635            self.consecutive_submit_failures += 1;
636            self.record(|s| s.failed_submits += 1);
637            // SAFETY: the caller passes a valid out-pointer, as the Win32 call
638            // requires.
639            unsafe { submitted.write(0) };
640            return SUBMIT_FAILED;
641        }
642        self.consecutive_submit_failures = 0;
643
644        // `RS-C-3`: nothing completes before it is submitted. That rule is
645        // this move and nothing else -- only `pool` is ever eligible to post.
646        let moved = self.staged.len();
647        self.pool.append(&mut self.staged);
648        self.record(|s| s.submitted += moved);
649        // SAFETY: as above.
650        unsafe {
651            submitted.write(u32::try_from(moved).unwrap_or(u32::MAX));
652        }
653
654        self.tick(true);
655
656        if wait_operations == 0 {
657            return S_OK;
658        }
659
660        // `RS-P-4`: a wait may expire, and may do so even when completions are
661        // available and on the very first call. This is not a ring failure --
662        // M21.6 fixed a defect in this crate that read it as one.
663        if self.config.may_expire_waits && self.chance(25) {
664            self.record(|s| s.expired_waits += 1);
665            return IORING_E_WAIT_TIMEOUT;
666        }
667
668        // `RS-P-5`: a wait returning success promises nothing about
669        // poppability. Reached whenever the queue is empty here -- either
670        // because resolution deferred everything, or because this clause
671        // chose to wake with work still pending.
672        if self.posted.is_empty() {
673            self.record(|s| s.empty_wakes += 1);
674        }
675        S_OK
676    }
677
678    unsafe fn pop(&mut self, _ring: *mut c_void, cqe: *mut IORING_CQE) -> HRESULT {
679        if self.posted.is_empty() {
680            // Starvation rescue only: no coins. See the module's note on the
681            // two clocks -- flipping here would make `RS-P-1`'s pending case
682            // unobservable to a polling consumer.
683            self.tick(false);
684        }
685
686        let Some((user_data, result, information)) = self.posted.pop_front() else {
687            return S_FALSE;
688        };
689        self.record(|s| s.popped += 1);
690        // SAFETY: the caller passes a valid out-pointer, as the Win32 call
691        // requires.
692        unsafe {
693            cqe.write(IORING_CQE {
694                // `RS-C-2`: a completion identifies the operation that
695                // produced it, by carrying back exactly the value supplied
696                // when it was built.
697                UserData: user_data,
698                ResultCode: result,
699                Information: information,
700            });
701        }
702        S_OK
703    }
704
705    unsafe fn build_read(
706        &mut self,
707        _ring: *mut c_void,
708        _file: IORING_HANDLE_REF,
709        _buffer: IORING_BUFFER_REF,
710        bytes: u32,
711        _offset: u64,
712        user_data: usize,
713        flags: i32,
714    ) -> HRESULT {
715        self.build(user_data, flags, Some(bytes))
716    }
717
718    unsafe fn build_write(
719        &mut self,
720        _ring: *mut c_void,
721        _file: IORING_HANDLE_REF,
722        _buffer: IORING_BUFFER_REF,
723        bytes: u32,
724        _offset: u64,
725        _caching: i32,
726        user_data: usize,
727        flags: i32,
728    ) -> HRESULT {
729        self.build(user_data, flags, Some(bytes))
730    }
731
732    unsafe fn build_flush(
733        &mut self,
734        _ring: *mut c_void,
735        _file: IORING_HANDLE_REF,
736        _mode: FILE_FLUSH_MODE,
737        user_data: usize,
738        flags: i32,
739    ) -> HRESULT {
740        self.build(user_data, flags, None)
741    }
742
743    unsafe fn build_cancel(
744        &mut self,
745        _ring: *mut c_void,
746        _file: IORING_HANDLE_REF,
747        _cancel_user_data: usize,
748        user_data: usize,
749    ) -> HRESULT {
750        // A cancel carries no SQE flags of its own in this crate's surface, so
751        // it is never a barrier. It is still an operation and still completes
752        // exactly once under `RS-C-1`.
753        self.build(user_data, 0, None)
754    }
755
756    unsafe fn build_register_files(
757        &mut self,
758        _ring: *mut c_void,
759        _count: u32,
760        _handles: *const *mut c_void,
761        user_data: usize,
762    ) -> HRESULT {
763        self.build(user_data, 0, None)
764    }
765
766    unsafe fn build_register_buffers(
767        &mut self,
768        _ring: *mut c_void,
769        _count: u32,
770        _buffers: *const IORING_BUFFER_INFO,
771        user_data: usize,
772    ) -> HRESULT {
773        self.build(user_data, 0, None)
774    }
775
776    unsafe fn set_completion_event(&mut self, _ring: *mut c_void, event: *mut c_void) -> HRESULT {
777        // Kept, not forwarded. The real ring would signal for operations it
778        // never received; this resolver signals for the ones it is answering
779        // for, on the edge `RS-P-6` describes.
780        self.event = CompletionEvent(event);
781        S_OK
782    }
783}
784
785#[cfg(test)]
786mod tests;