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;