Skip to main content

windows_ioring_sys/
ring.rs

1// Copyright (c) 2026 Mike Grier
2//! The owned `IoRing` handle (M1.2), and the op capability set (M1.4).
3
4use std::collections::HashMap;
5use std::marker::PhantomData;
6use std::mem::ManuallyDrop;
7
8use crate::token::OperationId;
9use std::ffi::c_void;
10use std::io;
11use std::os::windows::io::{AsRawHandle, FromRawHandle, OwnedHandle};
12use std::time::{Duration, Instant};
13
14use windows_sys::Win32::Storage::FileSystem::{
15    CloseIoRing, CreateIoRing, GetIoRingInfo, IORING_BUFFER_INFO, IORING_CQE,
16    IORING_CREATE_ADVISORY_FLAGS_NONE, IORING_CREATE_FLAGS, IORING_CREATE_REQUIRED_FLAGS_NONE,
17    IORING_INFO, IORING_OP_CANCEL, IORING_OP_CODE, IORING_OP_FLUSH, IORING_OP_NOP, IORING_OP_READ,
18    IORING_OP_REGISTER_BUFFERS, IORING_OP_REGISTER_FILES, IORING_OP_WRITE, IsIoRingOpSupported,
19};
20use windows_sys::Win32::System::Threading::{CreateEventW, SetEvent};
21
22use crate::accounting::Accounting;
23use crate::capability::{RingVersion, capabilities};
24use crate::error::check;
25
26pub(crate) use crate::accounting::RingId;
27
28/// One `IoRing` operation.
29///
30/// `#[non_exhaustive]`: the kernel's op table has grown before (M1.4, D-7)
31/// and will again. A consumer must not be able to write an exhaustive
32/// `match` that a new variant would break. [`IoRing::supports_raw`] reaches
33/// an op this enum does not yet name.
34///
35/// Naming an op here is not the same as offering a way to push it: every
36/// variant except [`Op::Nop`] gates one or more [`crate::Batch`] methods
37/// (`Read` gates the four read pushes, `Write` the four write pushes,
38/// `Flush` and `Cancel` two each, and the two registration ops one each),
39/// while `Nop` gates none and is reachable only through
40/// [`IoRing::push_raw`]. See [`IoRing::supports`] (M10.1).
41#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
42#[non_exhaustive]
43pub enum Op {
44    /// `IORING_OP_NOP`.
45    Nop,
46    /// `IORING_OP_READ`.
47    Read,
48    /// `IORING_OP_WRITE`.
49    Write,
50    /// `IORING_OP_FLUSH`.
51    Flush,
52    /// `IORING_OP_REGISTER_FILES`.
53    RegisterFiles,
54    /// `IORING_OP_REGISTER_BUFFERS`.
55    RegisterBuffers,
56    /// `IORING_OP_CANCEL`.
57    Cancel,
58}
59
60impl Op {
61    /// Every op this crate names, in a fixed order used to index the cached
62    /// capability set.
63    const ALL: [Op; 7] = [
64        Op::Nop,
65        Op::Read,
66        Op::Write,
67        Op::Flush,
68        Op::RegisterFiles,
69        Op::RegisterBuffers,
70        Op::Cancel,
71    ];
72
73    /// The raw `IORING_OP_CODE` value.
74    #[must_use]
75    pub fn code(self) -> IORING_OP_CODE {
76        match self {
77            Op::Nop => IORING_OP_NOP,
78            Op::Read => IORING_OP_READ,
79            Op::Write => IORING_OP_WRITE,
80            Op::Flush => IORING_OP_FLUSH,
81            Op::RegisterFiles => IORING_OP_REGISTER_FILES,
82            Op::RegisterBuffers => IORING_OP_REGISTER_BUFFERS,
83            Op::Cancel => IORING_OP_CANCEL,
84        }
85    }
86}
87
88/// Which ops a ring supports, probed once at construction.
89///
90/// A `u8` bitmask indexed by position in [`Op::ALL`] rather than a `HashSet`:
91/// there are exactly seven possible members, known at compile time, so a
92/// heap-allocating set would cost more than it buys.
93#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
94struct OpSupport(u8);
95
96impl OpSupport {
97    /// # Safety
98    ///
99    /// `handle` must be a live `HIORING`.
100    unsafe fn probe(handle: *mut c_void) -> Self {
101        let mut mask = 0_u8;
102        for (index, op) in Op::ALL.iter().enumerate() {
103            // SAFETY: forwarded from the caller.
104            let supported = unsafe { IsIoRingOpSupported(handle, op.code()) };
105            if supported != 0 {
106                mask |= 1 << index;
107            }
108        }
109        Self(mask)
110    }
111
112    fn contains(self, op: Op) -> bool {
113        let index = Op::ALL
114            .iter()
115            .position(|&candidate| candidate == op)
116            .expect("Op::ALL is exhaustive");
117        self.0 & (1 << index) != 0
118    }
119}
120
121/// What [`IoRing::info`] reports back from `GetIoRingInfo`.
122#[derive(Clone, Copy, Debug, PartialEq, Eq)]
123pub struct RingInfo {
124    /// The version this ring was actually created at.
125    pub version: RingVersion,
126    /// The ring's submission queue size.
127    pub submission_queue_size: u32,
128    /// The ring's completion queue size.
129    pub completion_queue_size: u32,
130}
131
132/// A failure to report in place of a completion's real result (M16.3).
133///
134/// Three spellings of the same thing, chosen so a call site reads as what it
135/// is testing rather than as a hexadecimal constant.
136///
137/// Available only under the `fault-injection` feature; see
138/// [`Completion::with_injected_failure`].
139#[cfg(any(test, feature = "fault-injection"))]
140#[derive(Clone, Copy, Debug, PartialEq, Eq)]
141pub enum InjectedFailure {
142    /// One of the ring's own `IORING_E_*` conditions.
143    Ring(crate::RingCondition),
144    /// A Win32 error code, wrapped the way the kernel wraps one -- so
145    /// `(hresult as u32) & 0xFFFF` recovers it, which is how this crate's
146    /// tests read a real one back.
147    Win32(u32),
148    /// A raw `HRESULT`, for a failure neither of the above names.
149    Hresult(windows_sys::core::HRESULT),
150}
151
152#[cfg(any(test, feature = "fault-injection"))]
153impl InjectedFailure {
154    /// The `HRESULT` this failure puts in the completion's result code.
155    ///
156    /// # Panics
157    ///
158    /// If the result would not actually be a failure. Only
159    /// [`InjectedFailure::Hresult`] can express that -- the other two spellings
160    /// always produce a failing code -- and it is rejected rather than
161    /// accepted, so that
162    /// [`Completion::with_injected_failure`]'s "failure only, never success"
163    /// guarantee is true by construction rather than by convention. A seam
164    /// that could turn a real failure into an apparent success would let a
165    /// test conceal the very defects it exists to find.
166    pub(crate) fn as_hresult(self) -> windows_sys::core::HRESULT {
167        let code = match self {
168            Self::Ring(condition) => condition.code(),
169            // `HRESULT_FROM_WIN32`: severity 1, facility 7 (`FACILITY_WIN32`),
170            // and the low 16 bits of the code.
171            //
172            // The `|` here is provably equivalent to `^`, and a mutation run
173            // will report that mutant surviving: `0x8007_0000`'s low sixteen
174            // bits are zero and `code & 0xFFFF`'s high sixteen bits are zero,
175            // so the two operands never share a set bit and every bitwise
176            // combinator that agrees on disjoint inputs agrees here too. `|`
177            // is kept because it reads as "these are separate fields" where
178            // `^` would read as an arithmetic accident; no test can tell them
179            // apart, so none is written.
180            Self::Win32(code) => (0x8007_0000_u32 | (code & 0xFFFF)).cast_signed(),
181            Self::Hresult(code) => code,
182        };
183        assert!(
184            code < 0,
185            "an injected failure must be a failing HRESULT, but {code:#010x} is not: \
186             this seam injects failure only, never success"
187        );
188        code
189    }
190}
191
192/// What the crate itself is holding for an in-flight operation.
193///
194/// Concrete rather than generic, and [D-73](../DESIGN-NOTES.md#d-73) explains
195/// why that is sound: `FileTarget` is sealed to `SharedHandle` and
196/// `RegisteredFile`, so this set is closed and crate-owned. A caller never
197/// names it. Unsealing that trait would break the arrangement -- the
198/// alternatives are type erasure, which [D-4](../DESIGN-NOTES.md#d-4) forbids,
199/// or a third generic parameter on every consumer.
200#[derive(Default)]
201pub(crate) struct Held {
202    /// Keeps the file valid for the operation's life.
203    ///
204    /// Never read, and `allow` rather than `expect` says so deliberately:
205    /// this field exists **for its `Drop`**, not for its value. Holding it
206    /// until the pop that completes the operation is the whole job, and
207    /// reading it would serve nothing. An `expect` here would be a promise
208    /// that some later change makes it read, which is not the intent.
209    #[allow(
210        dead_code,
211        reason = "held so the file outlives the operation; dropped at reclaim"
212    )]
213    pub(crate) guard: Option<FileGuard>,
214    /// Keeps a registered buffer's use counted while the kernel has it.
215    ///
216    /// Held for its `Drop`, as `guard` above.
217    #[allow(dead_code, reason = "held so the registration outlives the operation")]
218    pub(crate) registration: Option<crate::batch::RegisteredUse>,
219}
220
221/// The closed set of file guards, per `D-73`.
222///
223/// Public only because [`crate::FileTarget`]'s `Guard` bound names it, and a
224/// bound may not be more private than the trait carrying it. It is opaque on
225/// purpose: a caller cannot construct one, and the sealed trait means nobody
226/// outside this crate implements the thing that produces one.
227pub enum FileGuard {
228    Shared(win_shared_os_owned_handle::SharedHandle),
229    Registered(crate::batch::RegisteredFile),
230}
231
232impl From<win_shared_os_owned_handle::SharedHandle> for FileGuard {
233    fn from(guard: win_shared_os_owned_handle::SharedHandle) -> Self {
234        Self::Shared(guard)
235    }
236}
237
238impl From<crate::batch::RegisteredFile> for FileGuard {
239    fn from(guard: crate::batch::RegisteredFile) -> Self {
240        Self::Registered(guard)
241    }
242}
243
244/// A popped completion and whatever the ring was holding for it.
245///
246/// Named rather than left as a nested tuple in the signature: the outer
247/// `Option` is "was there a completion", and the inner one is "was this ring
248/// holding anything for it", and those are different questions that read
249/// badly stacked.
250pub type HeldCompletion<T, X> = (Completion, Option<(Option<T>, X)>);
251
252/// One in-flight operation's entry in the ring's inventory.
253pub(crate) struct Entry<T, X> {
254    /// What the caller handed over. `None` for a push that carries nothing to
255    /// give back -- the `_raw` flush and cancel entry points, whose shape is
256    /// `M28.5`'s to settle.
257    pub(crate) payload: Option<T>,
258    /// The caller's per-operation sidecar. Two thirds of the census sites keep
259    /// one, which is why it is a parameter rather than a convenience.
260    pub(crate) extra: X,
261    /// The crate's own half, which the caller never sees.
262    #[expect(
263        dead_code,
264        reason = "read when the guarded pushes migrate in M28.4.1; see M28.3+M28.4 in CHECKLIST.md"
265    )]
266    pub(crate) held: Held,
267}
268/// One popped completion (M3.7): the operation's identity, from
269/// `IORING_CQE::UserData`, and its result.
270#[derive(Clone, Copy, Debug)]
271pub struct Completion {
272    user_data: usize,
273    result_code: windows_sys::core::HRESULT,
274    information: usize,
275    ring_id: RingId,
276}
277
278impl Completion {
279    /// The `UserData` identity this completion reports.
280    ///
281    /// **Not a way to reach what the operation was holding.** The pop that
282    /// produced this completion already returned that, and it is the only
283    /// call that can -- see [`IoRing::try_pop`]. This is the integer for
284    /// correlation and for naming a cancel target, which is the same thing
285    /// [`crate::OperationId`] is and for the same reason.
286    ///
287    /// It used to say "match it against a held `Token`", and that instruction
288    /// was [D-55](../DESIGN-NOTES.md#d-55)'s evidence that the crate handed a
289    /// caller two halves and connected them with nothing.
290    #[must_use]
291    pub fn user_data(&self) -> usize {
292        self.user_data
293    }
294
295    /// The identity of the ring that popped this completion (PR #20 review
296    /// response): an entry or registration only ever matches a
297    /// `Completion` whose `ring_id` is also its own, so a `UserData` value
298    /// that happens to coincide across two different rings (every ring's
299    /// own counter starts at the same value) can never be confused for a
300    /// match.
301    #[must_use]
302    pub(crate) fn ring_id(&self) -> RingId {
303        self.ring_id
304    }
305
306    /// This op's result: the transferred byte count (read/write) or other
307    /// op-specific value in `IORING_CQE::Information`, once `ResultCode`
308    /// says success.
309    ///
310    /// # The count may be short, and the remainder is yours
311    ///
312    /// A successful read or write may report **fewer bytes than were
313    /// requested**, including zero (`RS-P-8` in
314    /// [RESPONSE-SPACE.md](../RESPONSE-SPACE.md)). `WriteFile` documents this
315    /// for non-blocking byte-mode pipes; sockets report a short send when the
316    /// transmit buffer is full, and reads are short at end of file. This crate
317    /// takes a handle and does not constrain what kind it is, so a consumer
318    /// must compare this count against the length it submitted rather than
319    /// assume they are equal.
320    ///
321    /// Nothing here reissues the remainder. Whether to submit another
322    /// operation for it, how many times, and when to give up are the caller's
323    /// (D-67), and a consumer that loops must tolerate a completion that makes
324    /// no progress.
325    ///
326    /// A consumer that has narrowed its *own* handle type can rely on more --
327    /// for an ordinary file on a local volume a successful completion is
328    /// expected to carry the full length, and a full volume is an error rather
329    /// than a short success. That is a guarantee such a consumer earns by
330    /// constraining the handle, and it belongs in its contract rather than
331    /// being assumed from this one.
332    ///
333    /// # Errors
334    ///
335    /// Returns the wrapped [`crate::IoRingError`] if `ResultCode` is a
336    /// failure -- for example `ERROR_NOT_FOUND` when a cancel target was not
337    /// actually outstanding.
338    pub fn result(&self) -> io::Result<usize> {
339        check(self.result_code)?;
340        Ok(self.information)
341    }
342
343    /// Report `failure` from [`Completion::result`] instead of what this
344    /// operation actually returned (M16.3).
345    ///
346    /// # What this is for
347    ///
348    /// Failure paths that are otherwise unreachable in a test. Most of what a
349    /// consumer must handle -- a write that fails partway, a flush the device
350    /// rejects, a registration the kernel refuses -- cannot be provoked on
351    /// demand from a healthy machine, so that code is typically written once
352    /// and never executed again. This crate shipped a defect of exactly that
353    /// shape: a claim path that returned early on a failed write and leaked
354    /// its registered-buffer slot permanently, on a branch no test had ever
355    /// taken.
356    ///
357    /// # Why this is sound, where fabricating a completion would not be
358    ///
359    /// The pop's safety argument is that a [`Completion`] for some `UserData`
360    /// **existing at all** proves the kernel has finished with that operation,
361    /// and therefore that handing its buffer back is sound.
362    ///
363    /// This method consumes a completion the ring genuinely popped and returns
364    /// one carrying the same `UserData` and the same ring identity. The
365    /// operation really did finish; the only thing that changes is what
366    /// [`Completion::result`] says about it. So the argument above is
367    /// untouched, and claiming against the result is exactly as sound as
368    /// claiming against the original.
369    ///
370    /// `Completion::synthetic` is the opposite, and is why it is not
371    /// reachable from here. A fabricated completion can name an operation that
372    /// is **still in flight**, and reclaiming a payload against one hands a
373    /// buffer back to the caller while the kernel is still writing through it -- the
374    /// precise use-after-free this crate exists to prevent. That is a
375    /// test-only, crate-only tool and stays one.
376    ///
377    /// # What it cannot do
378    ///
379    /// Only failure can be injected, never success. Turning a real failure
380    /// into an apparent success would let a test conceal a genuine defect,
381    /// which is the wrong affordance for a tool whose whole purpose is
382    /// exercising error handling. That is enforced rather than merely stated:
383    /// a non-failing `HRESULT` panics.
384    ///
385    /// # Panics
386    ///
387    /// If `failure` does not resolve to a failing `HRESULT`. Only
388    /// [`InjectedFailure::Hresult`] can express that at all.
389    ///
390    /// # Prefer a real failure when one is reachable
391    ///
392    /// A genuine kernel error -- writing through a read-only handle, cancelling
393    /// something that is not outstanding -- tests the real path *and* confirms
394    /// the crate's own error translation. Reach for this only where no such
395    /// route exists.
396    ///
397    /// # The one place injection is *not* inert: registration completions
398    ///
399    /// The soundness argument above is about the pop, where a failed
400    /// completion changes nothing the caller does with memory: the buffer
401    /// comes back either way. **A registration claim is different.**
402    /// [`crate::PendingBufferRegistration::claim_if`] treats a failed
403    /// completion as proof the kernel did *not* retain the addresses, and so
404    /// **drops the buffers**. Inject a failure there and it frees memory the
405    /// kernel genuinely does hold registered -- leaving the ring with a
406    /// registration pointing at freed pages.
407    ///
408    /// That is inert only while nothing uses it. A test doing this must not
409    /// push any registered-buffer operation on that ring afterwards, and
410    /// should tear the ring down immediately.
411    ///
412    /// This is a limitation of *what a failed registration means*, not
413    /// something the seam can check: a `Completion` does not carry which
414    /// operation produced it, so this cannot be refused at the call. Prefer a
415    /// genuine failure for registration paths wherever one can be provoked.
416    ///
417    /// # Example
418    ///
419    /// ```ignore
420    /// // ONE pop. The completion being injected into has already been popped,
421    /// // and that same pop is what retired the inventory entry and handed the
422    /// // payload back -- there is no second pop that could return it again.
423    /// let (completion, payload) = ring
424    ///     .pop_within(WAIT)
425    ///     .expect("pop_within")
426    ///     .expect("a completion arrives within the wait");
427    ///
428    /// // The payload comes back whatever the result says: the operation did
429    /// // complete, and a failed completion is still the proof that frees the
430    /// // buffer.
431    /// let completion = completion.with_injected_failure(
432    ///     InjectedFailure::Win32(ERROR_ACCESS_DENIED),
433    /// );
434    /// assert!(completion.result().is_err());
435    /// ```
436    #[cfg(any(test, feature = "fault-injection"))]
437    #[must_use]
438    pub fn with_injected_failure(self, failure: InjectedFailure) -> Self {
439        Self {
440            result_code: failure.as_hresult(),
441            // Zeroed deliberately: a failed operation transferred nothing, and
442            // leaving a success's byte count behind would model a state the
443            // kernel never produces.
444            information: 0,
445            ..self
446        }
447    }
448
449    /// Report a **successful** transfer of `transferred` bytes instead of what
450    /// this operation actually reported (M26.11).
451    ///
452    /// # What this is for, and why the failure seam cannot do it
453    ///
454    /// [`Completion::with_injected_failure`] models an operation that failed.
455    /// `RS-P-8` describes something different and stranger: an operation that
456    /// **succeeded** while moving fewer bytes than were asked for. Windows
457    /// documents that for non-blocking byte-mode pipes and it happens on
458    /// sockets, but an ordinary file on a local volume does not do it -- so a
459    /// consumer's handling of a short count is written once against the
460    /// documentation and then never executed again, which is exactly the class
461    /// of path the failure seam was introduced for.
462    ///
463    /// A consumer that *narrows* its handle type may legitimately require
464    /// complete transfers. This seam is how such a consumer tests that its
465    /// requirement is enforced rather than merely stated -- `epoch_log`'s
466    /// appender uses it for precisely that.
467    ///
468    /// # Why this one is inert
469    ///
470    /// The result code is left successful and only the byte count moves, so
471    /// the pop behaves exactly as it would for the real completion: the buffer
472    /// comes back, and no path keys memory ownership off the transferred
473    /// count. That makes this seam free of the
474    /// registration hazard documented on
475    /// [`Completion::with_injected_failure`], which arises only because a
476    /// *failed* registration is taken as proof the kernel retained nothing.
477    ///
478    /// # Example
479    ///
480    /// ```ignore
481    /// // A handle whose successful writes are complete is a requirement this
482    /// // consumer states; here we hand it one that violates the requirement.
483    /// let completion = completion.with_injected_transfer(RECORD_STRIDE - 1);
484    /// assert_eq!(completion.result().expect("still a success"), RECORD_STRIDE - 1);
485    /// ```
486    #[cfg(any(test, feature = "fault-injection"))]
487    #[must_use]
488    pub fn with_injected_transfer(self, transferred: usize) -> Self {
489        Self {
490            // Deliberately *not* touched: a short transfer under `RS-P-8` is a
491            // success, and turning it into a failure would model the one thing
492            // this seam exists to distinguish it from.
493            information: transferred,
494            ..self
495        }
496    }
497
498    /// Build a `Completion` without popping a real one, for tests that
499    /// exercise the pop without real I/O.
500    ///
501    /// Not available outside `#[cfg(test)]`: production code has no
502    /// legitimate reason to fabricate a completion, since the whole safety
503    /// argument for returning a payload depends on every `Completion` in
504    /// existence tracing back to a real `IORING_CQE` `IoRing::try_pop`
505    /// observed.
506    #[cfg(test)]
507    pub(crate) fn synthetic(
508        user_data: usize,
509        result_code: windows_sys::core::HRESULT,
510        ring_id: RingId,
511    ) -> Self {
512        Self {
513            user_data,
514            result_code,
515            information: 0,
516            ring_id,
517        }
518    }
519}
520
521/// How `S_FALSE` reads as a raw `HRESULT`: `PopIoRingCompletion`'s documented
522/// "the completion queue is empty" result. Not a failure (`FAILED(hr)` is
523/// false for it), so [`check`](crate::error::check) alone cannot distinguish
524/// it from `S_OK`.
525const S_FALSE: windows_sys::core::HRESULT = 1;
526
527/// How long each rundown poll blocks before rechecking `outstanding`, rather
528/// than one unbounded wait -- the same discipline
529/// `windows-overlapped-io-sys`'s own rundown uses (its DESIGN-NOTES: "Rundown
530/// waits are bounded and rechecked, not unbounded").
531const RUN_DOWN_POLL_MS: u32 = 50;
532
533/// `IORING_E_WAIT_TIMEOUT`: `SubmitIoRing` submitted every entry successfully
534/// and the subsequent wait then timed out.
535///
536/// **This is the documented code, not an observed one.** `SubmitIoRing`'s
537/// reference page gives it its own row and states the consequence that matters
538/// here: *"All operations were submitted without error and the subsequent wait
539/// timed out."* Its Remarks then draw the line this crate depends on -- *"If
540/// this function returns an error other than IORING_E_WAIT_TIMEOUT, then all
541/// entries remain in the submission queue."* So this value is the difference
542/// between "the work went in" and "the work is still queued", which is why it
543/// is classified rather than passed to [`check`](crate::error::check).
544///
545/// **Spelled by derivation because the bindings do not carry the name.**
546/// `IORING_E_WAIT_TIMEOUT` is a macro over `HRESULT_FROM_WIN32(ERROR_TIMEOUT)`
547/// rather than a `FACILITY_IORING` code -- that facility defines only
548/// `0x8046_0001` through `0x8046_0008`, none of them a timeout -- so
549/// `windows-sys` emits no constant for it and there is nothing to import. The
550/// derivation below is therefore the name, and is written out so the next
551/// reader does not re-derive it from a run. The `0x8007_0000` is
552/// `HRESULT_FROM_WIN32`'s severity-plus-`FACILITY_WIN32` prefix, which that
553/// macro ors onto any code of `0xFFFF` or less.
554pub(crate) const IORING_E_WAIT_TIMEOUT: windows_sys::core::HRESULT =
555    (0x8007_0000_u32 | windows_sys::Win32::Foundation::ERROR_TIMEOUT) as windows_sys::core::HRESULT;
556
557/// The largest `timeout_ms` a [`CompletionWait`] is ever handed: one below
558/// `u32::MAX`, because `u32::MAX` is Win32's `INFINITE`.
559///
560/// A waiter built on `WaitForSingleObject` or `WaitForMultipleObjects` -- both
561/// of which [`CompletionWait`] explicitly invites -- reads that value as "no
562/// timeout", so saturating onto it would convert a long but finite bound into
563/// an unbounded block, with the pop loop unable to re-check its own deadline
564/// until the wait returned.
565const MAX_WAIT_MS: u32 = u32::MAX - 1;
566
567/// Whether a `SubmitIoRing` result means every entry was submitted.
568///
569/// `S_OK` and `IORING_E_WAIT_TIMEOUT` both do, per that call's documented
570/// return values; every other error means the opposite, and its Remarks say
571/// so in as many words -- the entries remain in the submission queue.
572///
573/// Exposed as one predicate because three callers need the same answer and a
574/// fourth got it wrong for a year: [`IoRing::pop_within`] reported a timeout
575/// as a failure until `M21.6`, and [`crate::Batch::submit_and_wait`] still did
576/// until `M26.8`, because that fix swept two of the three sites.
577pub(crate) fn every_entry_was_submitted(hr: windows_sys::core::HRESULT) -> bool {
578    hr == IORING_E_WAIT_TIMEOUT || check(hr).is_ok()
579}
580
581/// Classify the result of a `SubmitIoRing` call made **only** to wait.
582///
583/// An expired wait is the ordinary outcome of asking to block for a bounded
584/// time, not a failure -- so it is `Ok`, and the caller re-checks whatever it
585/// was waiting for. Everything else is a real error.
586///
587/// This exists because getting it wrong is silent and was: `check(hr)`
588/// straight through turns every timed-out wait into an `Err`, which made
589/// [`IoRing::pop_within`] report a timeout as a failure rather than as the
590/// `Ok(None)` it documents, and made [`IoRing::run_down`] treat any operation
591/// slower than [`RUN_DOWN_POLL_MS`] as fatal. One classification, two callers,
592/// so they cannot disagree again.
593fn wait_outcome(hr: windows_sys::core::HRESULT) -> io::Result<()> {
594    if hr == IORING_E_WAIT_TIMEOUT {
595        return Ok(());
596    }
597    check(hr)
598}
599
600/// An owned `IoRing`, closed with `CloseIoRing` on drop.
601///
602/// Not `Clone`: cloning would give two owners of the same native ring, and
603/// `CloseIoRing` would run twice. Not `Sync`: building a submission is not
604/// thread-safe (D-5 in `DESIGN-NOTES.md`), so sharing `&IoRing` across
605/// threads is deliberately not offered -- a consumer wanting concurrent
606/// access chooses a delivery architecture (M4 / M6+) rather than relying on
607/// this type to serialize for them.
608// `Debug` is hand-written rather than derived: `IORING_BUFFER_INFO` does not
609// implement it, and the array's contents (raw addresses and lengths) are not
610// useful to print anyway -- its length is.
611#[repr(C)]
612pub struct IoRing<T = (), X = (), K = Open> {
613    handle: *mut c_void,
614    version: RingVersion,
615    supported_ops: OpSupport,
616    /// The half of this ring that never touches the kernel: identity,
617    /// operation identities, and the counts (M24.2). Split out so those rules
618    /// can be tested without opening a ring -- see [`crate::accounting`].
619    accounting: Accounting,
620    /// The arrays handed to the two registration `Build*` calls, kept alive
621    /// because the kernel reads both when the registration op *runs*, not
622    /// when the `Build*` call returns (D-32, measured) -- see
623    /// [`LateReadArrays`].
624    ///
625    /// Held by the ring rather than by the `Batch` that built them: a failed
626    /// `SubmitIoRing` leaves the SQE queued as ring state (D-5), so a later,
627    /// unrelated submit can be what finally runs it -- after that batch is
628    /// long gone. A ring accepts at most one registration of each kind, so
629    /// this is at most two small allocations per ring.
630    ///
631    /// `ManuallyDrop` for the same reason as `inventory` below, and released
632    /// on the same proof: `Drop` frees it only once rundown has shown nothing
633    /// is outstanding, and leaks it otherwise. Before this was `ManuallyDrop`
634    /// the buffer array was an ordinary field, freed after the `Drop` body on
635    /// every path -- including the one where rundown failed with the
636    /// registration perhaps still pending, and, in a debug build, by the
637    /// unwind out of that path's assert before any later line of the body
638    /// could intervene. PR #113 review.
639    late_read: ManuallyDrop<LateReadArrays>,
640    /// The files [`crate::Batch::register_shared_files`] registered, held for
641    /// the ring's life because Win32 has no unregister (M30.2,
642    /// [D-81](../DESIGN-NOTES.md#d-81)). Released by `Drop` after
643    /// `CloseIoRing`, and only on the proof `late_read` waits for; leaked
644    /// otherwise.
645    registered_files: ManuallyDrop<Vec<win_shared_os_owned_handle::SharedHandle>>,
646    /// The completion event this ring created and attached, once
647    /// [`IoRing::completion_event`] has been called (M11.1, D-20).
648    ///
649    /// The ring owns it and hands callers duplicates, which is what makes
650    /// the returned handle safe to hold for any length of time: closing a
651    /// duplicate cannot leave the ring signalling a closed handle, and the
652    /// kernel still signals the surviving duplicates.
653    ///
654    /// Dropped *after* `CloseIoRing` runs, because a manual `Drop` impl's
655    /// body runs before its fields are dropped -- so the ring is closed, and
656    /// can no longer signal, before the event it referenced goes away.
657    completion_event: Option<OwnedHandle>,
658    /// What each in-flight operation is holding on the caller's behalf
659    /// (`D-71`, `M28.3`).
660    ///
661    /// The ring owns this rather than handing a caller a `Token` to keep
662    /// beside its own map, because a consumer that never holds one cannot
663    /// lose one ([D-55](../DESIGN-NOTES.md#d-55)). An entry goes in when a
664    /// push queues and comes out at the pop that observes its completion --
665    /// which is why there is no call turning an identity back into memory the
666    /// kernel may still be using.
667    ///
668    /// `T` defaults to `()` so a consumer holding nothing never names it.
669    inventory: ManuallyDrop<HashMap<usize, Entry<T, X>>>,
670    /// Whether the ring can have an operation in flight that it holds nothing for (M41.1,
671    /// [D-91](../DESIGN-NOTES.md#d-91)); no data. `repr(C)` above is what lets
672    /// [IoRing::into_owned] change it without moving a field: two rings that differ only here
673    /// have one layout.
674    kind: PhantomData<fn() -> K>,
675}
676
677/// The arrays a registration op reads when it *runs*, during a later
678/// `SubmitIoRing`, rather than when its `Build*` call returns (D-32).
679///
680/// Both registrations behave this way. D-32 once recorded the file-handle
681/// array as read synchronously; that was measured in a debug build, where a
682/// caller's temporary `&[handle]` happened to survive in its stack slot until
683/// the submit. A release build reuses the slot, the kernel registered whatever
684/// it found there, and the first read through the index failed with
685/// `ERROR_INVALID_HANDLE`. Overwriting the caller's array before the submit
686/// reproduces it in every profile (PR #113).
687///
688/// One struct, so the two arrays have one owner, one release point and one
689/// rule for when that release is safe.
690#[derive(Default)]
691pub(crate) struct LateReadArrays {
692    /// The array handed to `BuildIoRingRegisterBuffers`.
693    pub(crate) buffer_infos: Vec<IORING_BUFFER_INFO>,
694    /// The array handed to `BuildIoRingRegisterFileHandles`.
695    pub(crate) file_handles: Vec<*mut c_void>,
696}
697
698impl<T, X, K> std::fmt::Debug for IoRing<T, X, K> {
699    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
700        f.debug_struct("IoRing")
701            .field("handle", &self.handle)
702            .field("version", &self.version)
703            .field("supported_ops", &self.supported_ops)
704            // One field rather than five, because the ledger derives `Debug`
705            // and prints its own. Keeping the five spelled out here would be a
706            // second copy of the field list, drifting the moment either side
707            // gains a field.
708            .field("accounting", &self.accounting)
709            .field("late_read_buffer_infos", &self.late_read.buffer_infos.len())
710            .field("late_read_file_handles", &self.late_read.file_handles.len())
711            .field("registered_files", &self.registered_files.len())
712            .field("completion_event", &self.completion_event)
713            .finish()
714    }
715}
716
717// SAFETY: HIORING is a Windows kernel object handle. Windows handles are not
718// tied to the thread that created them and may be closed from any thread, so
719// moving ownership of one to another thread is sound. This does not imply
720// `Sync`: submitting to the ring is not thread-safe (D-5), so only `Send` is
721// implemented.
722unsafe impl<T: Send, X: Send, K> Send for IoRing<T, X, K> {}
723
724/// Constructors for a ring that holds nothing on a caller's behalf.
725///
726/// These sit on `IoRing<()>` rather than on the generic impl for a reason that
727/// is about inference, not about capability: a defaulted type parameter
728/// applies in *type* position, so `IoRing::new(..)` on a generic impl would be
729/// ambiguous and every existing call site would have to write `IoRing::<()>`.
730/// A ring that holds payloads is built with
731/// [`IoRing::with_inventory`](IoRing::with_inventory) instead.
732impl IoRing<()> {
733    /// Create a ring, negotiating the version as `min(RingVersion::HIGHEST_KNOWN,
734    /// capabilities()?.max_version)` (D-6).
735    ///
736    /// # Errors
737    ///
738    /// Returns any error from `QueryIoRingCapabilities` or `CreateIoRing`.
739    pub fn new(submission_queue_size: u32, completion_queue_size: u32) -> io::Result<Self> {
740        let caps = capabilities()?;
741        let version = RingVersion::HIGHEST_KNOWN.min(caps.max_version);
742        Self::with_version(version, submission_queue_size, completion_queue_size)
743    }
744
745    /// Create a ring at exactly `version`, without negotiation.
746    ///
747    /// # Errors
748    ///
749    /// Returns [`IoRingError`](crate::IoRingError) wrapping
750    /// `IORING_E_VERSION_NOT_SUPPORTED` if `version` exceeds what the system
751    /// supports, or any other error from `CreateIoRing`.
752    pub fn with_version(
753        version: RingVersion,
754        submission_queue_size: u32,
755        completion_queue_size: u32,
756    ) -> io::Result<Self> {
757        Self::with_version_and_inventory(version, submission_queue_size, completion_queue_size)
758    }
759}
760
761impl<T, X> IoRing<T, X, Open> {
762    /// Create a ring at exactly `version`, whose inventory holds `T`.
763    ///
764    /// # Errors
765    ///
766    /// As [`IoRing::with_version`].
767    pub fn with_version_and_inventory(
768        version: RingVersion,
769        submission_queue_size: u32,
770        completion_queue_size: u32,
771    ) -> io::Result<Self> {
772        let flags = IORING_CREATE_FLAGS {
773            Required: IORING_CREATE_REQUIRED_FLAGS_NONE,
774            Advisory: IORING_CREATE_ADVISORY_FLAGS_NONE,
775        };
776        let mut handle: *mut c_void = std::ptr::null_mut();
777        // SAFETY: `handle` is a valid out-pointer; `flags` is a documented,
778        // all-`_NONE` value.
779        let hr = unsafe {
780            CreateIoRing(
781                version.raw(),
782                flags,
783                submission_queue_size,
784                completion_queue_size,
785                &raw mut handle,
786            )
787        };
788        check(hr)?;
789        // SAFETY: `handle` was just created successfully above and is not
790        // shared with anything else yet.
791        let supported_ops = unsafe { OpSupport::probe(handle) };
792        Ok(Self {
793            handle,
794            version,
795            supported_ops,
796            accounting: Accounting::new(),
797            late_read: ManuallyDrop::new(LateReadArrays::default()),
798            registered_files: ManuallyDrop::new(Vec::new()),
799            completion_event: None,
800            inventory: ManuallyDrop::new(HashMap::new()),
801            kind: PhantomData,
802        })
803    }
804}
805
806/// A ring that holds `T` on the caller's behalf for each in-flight operation.
807impl<T, X, K> IoRing<T, X, K> {
808    /// Whether this ring can be believed when it says nothing is outstanding.
809    ///
810    /// Asks the ledger, which tracks outstanding operations by **identity**:
811    /// [`Accounting::record_completion`] retires only a `user_data` it minted
812    /// and has not yet retired, so a CQE this ring never minted -- a duplicate,
813    /// or one carrying foreign user data -- retires nothing. That holds for
814    /// every push alike. An earlier revision kept a saturating counter and
815    /// consulted the inventory beside it as the witness the counter had been
816    /// corrupted, but only owned pushes stow, so for `push_raw` and the raw
817    /// flush and cancel forms the inventory was empty whether or not anything
818    /// was outstanding, and the false quiesce survived on exactly the seam
819    /// whose caller-managed memory it endangers.
820    ///
821    /// Every owned entry's identity is still in flight until the pop that
822    /// retires it also reclaims it, so the inventory is a subset of what this
823    /// checks; the `debug_assert` records that rather than relying on it.
824    ///
825    /// Every quiescence decision asks here rather than re-deriving it. The
826    /// first version of this check was written inline in `Drop` and nowhere
827    /// else, so [`IoRing::run_down_within`] went on reporting the false quiesce
828    /// as `Ok(true)` and `pop_within_raw_with` as "nothing can arrive": one
829    /// rule at three sites, corrected at one.
830    fn is_quiescent(&self) -> bool {
831        let quiescent = self.accounting.is_quiescent();
832        // Not asserted during an unwind: this runs inside `Drop`'s rundown,
833        // and a second panic there aborts the process instead of failing the
834        // test that is already unwinding.
835        debug_assert!(
836            !quiescent || self.inventory.is_empty() || std::thread::panicking(),
837            "an owned entry outlived its identity's retirement"
838        );
839        quiescent
840    }
841
842    /// Pop every currently available completion, recording each -- without
843    /// interpreting it, since rundown only needs to know a completion
844    /// happened, not what it was.
845    ///
846    /// Whatever each entry held is dropped here rather than handed back,
847    /// because there is nobody to hand it to: rundown is the one path that
848    /// pops without a caller waiting for the result. Dropping is correct
849    /// rather than merely convenient -- the completion is the proof the kernel
850    /// has finished, which is exactly what makes freeing safe.
851    fn drain_for_rundown(&mut self) -> io::Result<()> {
852        while let Some(completion) = self.pop_raw()? {
853            drop(self.reclaim(completion.user_data()));
854        }
855        Ok(())
856    }
857
858    /// A completion was popped for an identity that is not in flight on this
859    /// ring ([D-79](../DESIGN-NOTES.md#d-79)).
860    ///
861    /// It was never minted here, has already completed, or belongs to a
862    /// reservation released because its `Build*` call failed. Every queued SQE
863    /// produces exactly one completion carrying the identity it was built
864    /// with (M10.2), so each of those is a defect -- in a [`IoRing::push_raw`]
865    /// closure that built with some other `user_data`, in a `kernel-seam`
866    /// responder that broke `RS-C-2`, or in the kernel.
867    ///
868    /// Traced always, so the event survives in a trace dump whatever happens
869    /// next, and then a panic -- **except during an unwind**. Rundown runs in
870    /// `Drop`, so this can be reached while the thread is already panicking,
871    /// and a second panic there would abort the process; the panic already in
872    /// flight is left to carry the failure. The completion has retired
873    /// nothing either way, so quiescence is unaffected ([D-78](../DESIGN-NOTES.md#d-78)).
874    #[cold]
875    fn unminted_completion(&self, user_data: usize) {
876        #[cfg(feature = "threadpool")]
877        windows_threadpool_sys::trace_record!(
878            "ring",
879            "unminted-completion",
880            user_data,
881            self.accounting.outstanding()
882        );
883        if !std::thread::panicking() {
884            panic!(
885                "IoRing popped a completion for user_data {user_data:#x}, which is not in flight \
886                 on this ring: it was never minted here, has already completed, or its build \
887                 failed. Every queued SQE completes exactly once with the identity it was built \
888                 with, so this is a defect in a push_raw closure, a kernel-seam responder, or \
889                 the kernel (D-79)"
890            );
891        }
892    }
893
894    /// Pop a completion without touching the inventory.
895    ///
896    /// The shared body of every pop. It is deliberately private: a pop that
897    /// leaves the entry behind strands whatever the ring was holding, which is
898    /// a defect rather than a mode -- `drain_for_rundown` had exactly that bug
899    /// before `M28.4.1d.1`. Both public forms retire the entry; they differ
900    /// only in whether the caller is handed what it contained.
901    fn pop_raw(&mut self) -> io::Result<Option<Completion>> {
902        let mut cqe = IORING_CQE {
903            UserData: 0,
904            ResultCode: 0,
905            Information: 0,
906        };
907        // SAFETY: `self.handle` is a live ring; valid out-pointer.
908        let hr = unsafe { crate::sys::pop(self.ring_id(), self.handle, &raw mut cqe) };
909        if hr == S_FALSE {
910            return Ok(None);
911        }
912        check(hr)?;
913        if !self.record_completion(cqe.UserData) {
914            self.unminted_completion(cqe.UserData);
915        }
916        Ok(Some(Completion {
917            user_data: cqe.UserData,
918            result_code: cqe.ResultCode,
919            information: cqe.Information,
920            ring_id: self.accounting.ring_id(),
921        }))
922    }
923}
924
925impl<T, X, K: RingKind> IoRing<T, X, K> {
926    /// Pop one completion, blocking in the ring's own wait until one is
927    /// available or `timeout` elapses (M21.2).
928    ///
929    /// This is the join between [`IoRing::try_pop`], whose `None` means
930    /// "empty at this instant", and [`crate::Batch::submit_and_wait`], whose
931    /// return deliberately promises nothing about poppability because its
932    /// timeout may have expired. Neither one alone answers "give me the
933    /// completion I just caused", and before this existed every caller wrote
934    /// that loop again -- four different ways across five sites, two of them
935    /// unbounded spins.
936    ///
937    /// `Ok(None)` means the timeout elapsed, or that **nothing can arrive**:
938    /// with no operation outstanding and an empty queue, no completion is
939    /// possible, so this returns immediately rather than sleeping out the
940    /// full `timeout`. That early return is what turns "you forgot to submit"
941    /// from a timeout into an instant answer.
942    ///
943    /// A zero `timeout` is exactly one [`IoRing::try_pop`], which is the
944    /// honest reading of "wait no time at all".
945    ///
946    /// Uses [`SubmitWait`], which blocks inside `SubmitIoRing` with no new
947    /// entries queued. It does **not** touch the ring's completion event, so
948    /// it cannot disturb a caller who owns that event under
949    /// [D-21](../DESIGN-NOTES.md#d-21). Use
950    /// [`IoRing::pop_within_with`] to supply a different wait.
951    ///
952    /// # Errors
953    ///
954    /// Any error from `SubmitIoRing` or `PopIoRingCompletion`.
955    ///
956    /// # Panics
957    ///
958    /// As [`IoRing::try_pop`], on a completion for an identity not in flight.
959    pub fn pop_within(&mut self, timeout: Duration) -> io::Result<Option<K::Popped<T, X>>> {
960        self.pop_within_with(&mut SubmitWait, timeout)
961    }
962
963    /// [`IoRing::pop_within`] with a caller-chosen wait.
964    ///
965    /// The crate cannot pick the wait for you, and that is a contract rather
966    /// than a shrug: the completion event is auto-reset with exactly one
967    /// waiter per ring ([D-21](../DESIGN-NOTES.md#d-21)), so a wait this crate
968    /// chose could consume an edge the caller's own loop was entitled to. The
969    /// owner of the ring is the only party who can discharge that obligation,
970    /// which is why the choice is a parameter.
971    ///
972    /// # Errors
973    ///
974    /// Any error from the wait or from `PopIoRingCompletion`.
975    ///
976    /// # Panics
977    ///
978    /// As [`IoRing::try_pop`], on a completion for an identity not in flight.
979    pub fn pop_within_with<W: CompletionWait + ?Sized>(
980        &mut self,
981        wait: &mut W,
982        timeout: Duration,
983    ) -> io::Result<Option<K::Popped<T, X>>> {
984        let Some(completion) = self.pop_within_raw_with(wait, timeout)? else {
985            return Ok(None);
986        };
987        let held = self
988            .reclaim(completion.user_data())
989            .map(|entry| (entry.payload, entry.extra));
990        K::shape(completion, held).map(Some)
991    }
992}
993
994impl<T, X, K> IoRing<T, X, K> {
995    /// [`IoRing::pop_within_with`] without touching the inventory.
996    ///
997    /// The counterpart to [`IoRing::pop_raw`], and private for the same
998    /// reason: it is the shared body, not a mode any caller should choose.
999    fn pop_within_raw_with<W: CompletionWait + ?Sized>(
1000        &mut self,
1001        wait: &mut W,
1002        timeout: Duration,
1003    ) -> io::Result<Option<Completion>> {
1004        let deadline = Instant::now().checked_add(timeout);
1005        loop {
1006            if let Some(completion) = self.pop_raw()? {
1007                return Ok(Some(completion));
1008            }
1009            // Checked *after* the pop, never before: `record_completion` runs
1010            // during `try_pop`, so reading it first would race the very
1011            // completion being drained.
1012            //
1013            // `is_quiescent`, which is keyed by identity, so a foreign or
1014            // duplicate completion cannot cut the wait short by claiming
1015            // nothing can arrive.
1016            if self.is_quiescent() {
1017                return Ok(None);
1018            }
1019            // `checked_add` rather than `+`: `Instant + Duration` panics on
1020            // overflow, so a caller passing `Duration::MAX` -- a reasonable
1021            // spelling of "no deadline" -- would take down the process. A
1022            // deadline the clock cannot represent is treated as one that
1023            // never arrives, which is what the caller asked for.
1024            let remaining = match deadline {
1025                Some(deadline) => deadline.saturating_duration_since(Instant::now()),
1026                None => Duration::MAX,
1027            };
1028            if remaining.is_zero() {
1029                return Ok(None);
1030            }
1031            // Clamped into `1..=MAX_WAIT_MS`. The low end stops a
1032            // sub-millisecond remainder becoming a zero timeout, which
1033            // `SubmitIoRing` reads as "poll and return" and would turn the
1034            // tail of every wait into a spin. The high end keeps the waiter
1035            // from ever being handed `u32::MAX`, which is Win32's `INFINITE`
1036            // -- a `WaitForMultipleObjects`-based waiter would block forever
1037            // on a bound its caller believed was finite.
1038            let ms = u32::try_from(remaining.as_millis())
1039                .unwrap_or(MAX_WAIT_MS)
1040                .clamp(1, MAX_WAIT_MS);
1041            wait.wait(
1042                &mut RingWait {
1043                    handle: self.handle,
1044                    accounting: &self.accounting,
1045                },
1046                ms,
1047            )?;
1048        }
1049    }
1050}
1051
1052/// How [`IoRing::pop_within_with`] blocks between checks of the completion
1053/// queue (M21.2).
1054///
1055/// Implement this to drive a bounded pop from a wait this crate does not own
1056/// -- a completion event the caller already holds, a multiplexed
1057/// `WaitForMultipleObjects`, or a pure spin on a thread that must not block
1058/// in the kernel.
1059pub trait CompletionWait {
1060    /// Block until a completion *may* be available, or `timeout_ms` elapses.
1061    ///
1062    /// **Returning early or spuriously is always permitted**, and requires no
1063    /// apology: [D-19](../DESIGN-NOTES.md#d-19) makes a wake with nothing to
1064    /// pop a normal event, so the caller re-checks the queue either way. An
1065    /// implementation therefore cannot be subtly wrong about *when* to
1066    /// return; it can only waste time or burn CPU.
1067    ///
1068    /// What it must not do is block past `timeout_ms`, because that is the
1069    /// only thing standing between a stuck ring and a hung process.
1070    ///
1071    /// # An expired wait is `Ok(())`, never an error
1072    ///
1073    /// This is the one part of the contract an implementation can get wrong
1074    /// silently, and the crate's own [`SubmitWait`] got it wrong first: Win32
1075    /// reports an expired wait as a *failure* code (`ERROR_TIMEOUT` from
1076    /// `SubmitIoRing`, `WAIT_TIMEOUT` from the `WaitFor*` family), so
1077    /// forwarding the underlying result verbatim turns every ordinary timeout
1078    /// into an `Err`. [`IoRing::pop_within`] then reports a timeout as a
1079    /// failure rather than as the `Ok(None)` it promises, and every caller
1080    /// that matches on `Ok(None)` to detect a timeout stops working.
1081    ///
1082    /// So: the bound running out is a **successful** wait that happened to
1083    /// observe nothing. Report the error only when the wait itself failed.
1084    ///
1085    /// `timeout_ms` is never zero and never `u32::MAX`, so it can be passed
1086    /// straight to a Win32 wait without being mistaken for "poll and return"
1087    /// or for `INFINITE`.
1088    ///
1089    /// # Errors
1090    ///
1091    /// Whatever the underlying wait reports as a genuine failure. An error
1092    /// ends the pop.
1093    fn wait(&mut self, ring: &mut RingWait<'_>, timeout_ms: u32) -> io::Result<()>;
1094}
1095
1096/// The ring, narrowed to what a [`CompletionWait`] needs (M21.2).
1097///
1098/// Deliberately exposes no way to pop and no way to submit work. A waiter that
1099/// could pop would consume the completion its own caller is waiting for, and
1100/// one that could submit would queue entries the caller never asked for --
1101/// both of which a bare `&mut IoRing` would permit. This is the same
1102/// narrowing, for the same reason, as
1103#[cfg_attr(
1104    feature = "threadpool",
1105    doc = "[`RingScope`](crate::RingScope) under [D-43](../DESIGN-NOTES.md#d-43)."
1106)]
1107#[cfg_attr(
1108    not(feature = "threadpool"),
1109    doc = "`RingScope` (the default `threadpool` feature) under [D-43](../DESIGN-NOTES.md#d-43)."
1110)]
1111pub struct RingWait<'ring> {
1112    /// Narrowed to what a wait actually uses -- the handle to submit on, and
1113    /// the ledger to ask how much is outstanding -- rather than the whole
1114    /// ring. That is the same narrowing `M24.7` made for `Token::new`, and
1115    /// here it also keeps [`CompletionWait`] free of `IoRing`'s payload
1116    /// parameter: a waiter blocks on a ring, and what the ring is holding for
1117    /// its caller is none of its business.
1118    handle: *mut c_void,
1119    accounting: &'ring Accounting,
1120}
1121
1122impl RingWait<'_> {
1123    /// Block in the ring's own wait for up to `timeout_ms`, queueing nothing.
1124    ///
1125    /// With no new entries of its own, `SubmitIoRing`'s effect here is to
1126    /// submit whatever is already queued and wait for an outstanding
1127    /// operation to complete -- the same call [`IoRing::run_down`] uses to
1128    /// quiesce. Note *submit*: a `Build*` that has queued an SQE but not yet
1129    /// submitted it will be submitted by this call, which is why the
1130    /// narrowing below is about not letting a waiter **build** work, not
1131    /// about suppressing submission.
1132    ///
1133    /// **An expired wait is `Ok`, not an error.** Returning therefore does
1134    /// not mean a completion is poppable -- the bound may simply have run out
1135    /// -- which is why the loop that calls this re-checks either way.
1136    ///
1137    /// # At least one operation must really be outstanding
1138    ///
1139    /// Measured while building this: `SubmitIoRing` answers
1140    /// `E_INVALIDARG` (`0x80070057`) -- not a timeout -- when asked to wait
1141    /// for a completion the kernel has no pending operation for. A `RingWait`
1142    /// is only ever constructed by [`IoRing::pop_within_with`], which checks
1143    /// [`IoRing::outstanding`] before consulting the wait, so that
1144    /// precondition holds structurally rather than by the caller remembering
1145    /// it. A waiter that wants to block some other way is free to ignore this
1146    /// method entirely.
1147    ///
1148    /// # Errors
1149    ///
1150    /// Any error from `SubmitIoRing` other than an expired wait.
1151    pub fn block(&mut self, timeout_ms: u32) -> io::Result<()> {
1152        let mut submitted = 0_u32;
1153        // SAFETY: the ring handle is live for the borrow, and the out-pointer
1154        // is valid. Zero new SQEs are queued, so this call's only effect is
1155        // to wait for and reap what is already outstanding.
1156        let hr = unsafe {
1157            crate::sys::submit(
1158                self.accounting.ring_id(),
1159                self.handle,
1160                1,
1161                timeout_ms,
1162                &raw mut submitted,
1163            )
1164        };
1165        wait_outcome(hr)
1166    }
1167
1168    /// Operations submitted but not yet observed complete, as
1169    /// [`IoRing::outstanding`].
1170    ///
1171    /// A waiter that multiplexes several sources can use this to decide
1172    /// whether blocking on this ring is worth a slot at all.
1173    #[must_use]
1174    pub fn outstanding(&self) -> usize {
1175        self.accounting.outstanding()
1176    }
1177}
1178
1179/// The default [`CompletionWait`]: block inside the ring's own
1180/// `SubmitIoRing` wait (M21.2).
1181///
1182/// Costs no kernel object and touches no event, so it composes with a caller
1183/// who owns the ring's completion event under
1184/// [D-21](../DESIGN-NOTES.md#d-21).
1185#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
1186pub struct SubmitWait;
1187
1188impl CompletionWait for SubmitWait {
1189    fn wait(&mut self, ring: &mut RingWait<'_>, timeout_ms: u32) -> io::Result<()> {
1190        ring.block(timeout_ms)
1191    }
1192}
1193
1194#[cfg(test)]
1195impl IoRing {
1196    /// An `IoRing` that owns no kernel ring, for driving `Drop`'s two error
1197    /// paths (M23.5).
1198    ///
1199    /// The handle is null **deliberately and specifically**. Measured:
1200    /// `CloseIoRing(null)` and `crate::sys::submit(null, ..)` both return
1201    /// `0x80070006` -- `HRESULT_FROM_WIN32(ERROR_INVALID_HANDLE)` -- so both
1202    /// of `Drop`'s failure branches can be reached without a fault-injection
1203    /// seam over the raw HRESULTs, which is what M23.5 was opened to price.
1204    ///
1205    /// A *non-null* fabricated handle is not a substitute and must never be
1206    /// swapped in: `CloseIoRing` on a plausible-looking `0xDEAD_0000` raises
1207    /// `STATUS_ACCESS_VIOLATION`, because a ring handle is a pointer the
1208    /// kernel dereferences rather than an index into a handle table.
1209    ///
1210    /// Nothing here opens a ring, so the tests built on it are not part of the
1211    /// ring-opening population `tools/check-ring-tests.ps1` tracks (D-49) --
1212    /// they need the kernel only to refuse them, which costs no ring.
1213    fn refused_by_the_kernel() -> Self {
1214        Self {
1215            handle: std::ptr::null_mut(),
1216            version: RingVersion::V1,
1217            supported_ops: OpSupport::default(),
1218            accounting: Accounting::new(),
1219            late_read: ManuallyDrop::new(LateReadArrays::default()),
1220            registered_files: ManuallyDrop::new(Vec::new()),
1221            completion_event: None,
1222            inventory: ManuallyDrop::new(HashMap::new()),
1223            kind: PhantomData,
1224        }
1225    }
1226}
1227
1228impl<T, X, K> Drop for IoRing<T, X, K> {
1229    fn drop(&mut self) {
1230        // A count of how many times this body has run, so a test can confirm
1231        // the rundown-and-close actually executes rather than trusting the
1232        // impl exists. The counter is thread-local: a process-wide one is
1233        // incremented by every other test's rings as they drop, which would
1234        // let an `after > before` assertion be satisfied by somebody else's
1235        // drop and mask the very mutation it exists to catch.
1236        #[cfg(test)]
1237        DROP_RUNS.with(|runs| runs.set(runs.get() + 1));
1238
1239        // Best-effort rundown: a ring with an operation still outstanding at
1240        // drop time is a use bug (popping every completion, or calling
1241        // `run_down`, before the drop is the sanctioned way to avoid it), but
1242        // Drop cannot propagate the error, so this asserts in
1243        // debug builds rather than silently closing a ring the kernel may
1244        // still be writing through.
1245        //
1246        // Both asserts here are silent while already panicking (M23.4): a
1247        // second panic during unwind aborts, replacing whatever failure
1248        // started the unwind with `STATUS_STACK_BUFFER_OVERRUN`. A ring is
1249        // dropped on the way out of almost every failing test in this crate,
1250        // so an unguarded assert here would convert a readable assertion
1251        // message into a crash in the common case rather than a rare one.
1252        //
1253        // Both are covered, and neither needed a fault-injection seam to get
1254        // there (M23.5). `a_ring_whose_rundown_the_kernel_refuses_..` and
1255        // `a_ring_whose_close_the_kernel_refuses_..` put a null handle in the
1256        // field and let this body run for real: measured, `CloseIoRing(null)`
1257        // and `crate::sys::submit(null, ..)` both return `0x80070006`
1258        // (`ERROR_INVALID_HANDLE`). Whether `run_down` submits at all is what
1259        // selects between the two, since it loops only while something is
1260        // outstanding.
1261        //
1262        // A *non-null* bad handle is not equivalent and must never be
1263        // substituted: `CloseIoRing(0xDEAD_0000)` raises
1264        // `STATUS_ACCESS_VIOLATION`, because a ring handle is a pointer the
1265        // kernel dereferences rather than an index into a handle table.
1266        let quiesced = match self.run_down() {
1267            Ok(()) => true,
1268            Err(error) => {
1269                debug_assert!(
1270                    std::thread::panicking(),
1271                    "IoRing rundown failed before close: {error}"
1272                );
1273                false
1274            }
1275        };
1276
1277        // A responder registered for this ring answers through the rundown
1278        // above and no further (D-85): it may hold operations the kernel never
1279        // saw, so the rundown is its to answer, and nothing after this line
1280        // calls through the seam. Ended before `CloseIoRing`, so the handle
1281        // cannot be reused while it is registered.
1282        #[cfg(feature = "kernel-seam")]
1283        crate::sys::ring_dropped(self.accounting.ring_id());
1284
1285        // The inventory is dropped only when rundown actually quiesced the
1286        // ring, and **forgotten otherwise** (D-73). This is `Token`'s
1287        // leak-on-unclaimed-drop, relocated: it used to be the caller's,
1288        // because the caller held the buffers and could not prove the kernel
1289        // was finished with them. The ring can prove it -- rundown returning
1290        // `Ok` is that proof -- but only on the path where rundown succeeds,
1291        // and the rundown above is deliberately best-effort. On the other path
1292        // the close below runs with operations possibly still outstanding, so
1293        // freeing what they point at would hand the kernel a dangling write.
1294        //
1295        // Leaking is the correct answer there, exactly as it was for a `Token`
1296        // dropped unclaimed before the token API retired: memory is lost, which
1297        // is finite and visible,
1298        // rather than reused, which is neither.
1299        //
1300        // `is_quiescent` is rechecked rather than trusting `quiesced` alone so
1301        // that the free below and rundown's answer rest on the one predicate.
1302        // It is keyed by identity, so a CQE the ring never minted -- a
1303        // duplicate, or one carrying foreign user data -- cannot make it read
1304        // true while any operation, owned or raw, is still in flight.
1305        //
1306        // The registration arrays follow the same rule for the same reason.
1307        // The kernel reads them when the registration op runs (D-32), and a
1308        // failed rundown is precisely the case where this ring cannot say
1309        // whether that has happened.
1310        let mut released_files = None;
1311        if quiesced && self.is_quiescent() {
1312            // SAFETY: nothing is outstanding, so no kernel write can still be
1313            // aimed at anything this holds and no registration op can still
1314            // read either array; neither field is used again -- this
1315            // is `Drop`, and both are `ManuallyDrop` so nothing drops them a
1316            // second time.
1317            unsafe { ManuallyDrop::drop(&mut self.inventory) };
1318            // Taken out rather than dropped in place so the test counter below
1319            // counts the very value that is freed: deleting the release
1320            // deletes the count with it.
1321            //
1322            // SAFETY: as for the inventory above; the field is not read again.
1323            let arrays = unsafe { ManuallyDrop::take(&mut self.late_read) };
1324            #[cfg(test)]
1325            {
1326                let non_empty = usize::from(!arrays.buffer_infos.is_empty())
1327                    + usize::from(!arrays.file_handles.is_empty());
1328                LATE_READ_ARRAYS_RELEASED.with(|released| released.set(released.get() + non_empty));
1329            }
1330            drop(arrays);
1331            // Taken now, released only after the close below (D-81).
1332            //
1333            // SAFETY: as above; the field is not read again.
1334            released_files = Some(unsafe { ManuallyDrop::take(&mut self.registered_files) });
1335        }
1336        // SAFETY: `self.handle` is a live ring this `IoRing` exclusively
1337        // owns, and `run_down` just established that nothing is outstanding
1338        // (or made a best-effort attempt to, above).
1339        let hr = unsafe { CloseIoRing(self.handle) };
1340        // Whether the kernel references a registered handle until the ring
1341        // closes is undocumented, so the handles outlive the close. They are
1342        // released before the assert below, which would otherwise unwind past
1343        // this line.
1344        if let Some(files) = released_files {
1345            #[cfg(test)]
1346            REGISTERED_FILES_RELEASED.with(|released| released.set(released.get() + files.len()));
1347            drop(files);
1348        }
1349        debug_assert!(
1350            hr >= 0 || std::thread::panicking(),
1351            "CloseIoRing failed: 0x{:08X}",
1352            hr as u32
1353        );
1354    }
1355}
1356
1357// How many times `IoRing`'s `Drop` impl has run **on the calling thread**; see
1358// its use there.
1359//
1360// A plain comment rather than a doc comment: rustdoc does not document items
1361// produced by a macro invocation, so a doc comment here is an
1362// `unused_doc_comments` warning, which CI escalates with `-D warnings`.
1363//
1364// Thread-local rather than a shared `static`, and that is load-bearing rather
1365// than tidiness. `cargo test` runs tests as threads in one process, so a
1366// process-wide counter is incremented by every other test's rings as they
1367// drop -- and an assertion of the form `after > before` is then satisfied by
1368// *somebody else's* drop, which is exactly the mutant it was written to catch.
1369// A thread-local is only touched by rings dropped on this test's own thread.
1370#[cfg(test)]
1371thread_local! {
1372    pub(crate) static DROP_RUNS: std::cell::Cell<usize> = const { std::cell::Cell::new(0) };
1373}
1374
1375// How many non-empty registration arrays (`LateReadArrays`) `IoRing`'s `Drop`
1376// has released on the calling thread. Thread-local for the reason `DROP_RUNS`
1377// is.
1378//
1379// What it observes is the explicit release, not the absence of the compiler's
1380// own drop of the field; that half is the field's `ManuallyDrop` type, which
1381// `ring::tests` pins at compile time.
1382#[cfg(test)]
1383thread_local! {
1384    pub(crate) static LATE_READ_ARRAYS_RELEASED: std::cell::Cell<usize> = const { std::cell::Cell::new(0) };
1385}
1386
1387// How many registered `SharedHandle`s `IoRing`'s `Drop` has released on the
1388// calling thread (D-81). Thread-local for the reason `DROP_RUNS` is.
1389#[cfg(test)]
1390thread_local! {
1391    pub(crate) static REGISTERED_FILES_RELEASED: std::cell::Cell<usize> = const { std::cell::Cell::new(0) };
1392}
1393
1394/// Pop one completion, bounded, for tests that need a real one.
1395///
1396/// `Batch::submit_and_wait` returning does **not** mean a completion is
1397/// poppable -- its own documentation says so, because the timeout can expire
1398/// first. That leaves two ways for a test to be wrong, and this exists so
1399/// neither is spelled out at each call site.
1400///
1401/// A single `try_pop` flakes: under load the completion arrives just after the
1402/// check. A bare `loop` around `try_pop` is worse, because it converts that
1403/// flake into a hang -- and `cargo test` runs tests as threads in one process,
1404/// so a hung test stops the *whole harness* reporting and the failure arrives
1405/// with no test name attached. A bounded wait fails loudly instead, naming what
1406/// it waited for.
1407///
1408/// Thirty seconds matches the deadline the crate's own `failure_paths`
1409/// integration test already uses; it is a hang bound, not a latency
1410/// expectation, so it is far above any real completion time.
1411///
1412/// Since M21.2 this is a thin panicking wrapper over the public
1413/// [`IoRing::pop_within`] rather than its own loop. The panic is the only
1414/// thing left that is specific to tests: a test wants the name of what it
1415/// waited for in the failure message, where a consumer wants an `Option` it
1416/// can act on.
1417#[cfg(test)]
1418pub(crate) fn pop_within<T, X>(ring: &mut IoRing<T, X>, what: &str) -> Completion {
1419    // Named once so the bound and the message it reports cannot drift apart.
1420    const BOUND: std::time::Duration = std::time::Duration::from_secs(30);
1421
1422    ring.pop_within(BOUND)
1423        .expect("pop")
1424        .unwrap_or_else(|| panic!("timed out after {BOUND:?} waiting for {what}"))
1425        .0
1426}
1427
1428// Both are children rather than siblings so they can reach this module's
1429// private items -- `pop_raw`, `is_quiescent`, `drain_for_rundown` -- which stay
1430// here because `Drop` needs them too and a parent cannot see into its children.
1431// No public path changes: an inherent `impl` is found by its type.
1432mod bookkeeping;
1433mod inventory;
1434mod kind;
1435
1436pub use kind::{AllowsRaw, Open, Owned, RingKind};
1437
1438#[cfg(test)]
1439mod tests;