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::ffi::c_void;
5use std::io;
6use std::os::windows::io::{AsRawHandle, FromRawHandle, OwnedHandle};
7use std::sync::atomic::{AtomicU64, Ordering};
8
9use windows_sys::Win32::Storage::FileSystem::{
10    CloseIoRing, CreateIoRing, GetIoRingInfo, IORING_BUFFER_INFO, IORING_CQE,
11    IORING_CREATE_ADVISORY_FLAGS_NONE, IORING_CREATE_FLAGS, IORING_CREATE_REQUIRED_FLAGS_NONE,
12    IORING_INFO, IORING_OP_CANCEL, IORING_OP_CODE, IORING_OP_FLUSH, IORING_OP_NOP, IORING_OP_READ,
13    IORING_OP_REGISTER_BUFFERS, IORING_OP_REGISTER_FILES, IORING_OP_WRITE, IsIoRingOpSupported,
14    PopIoRingCompletion, SetIoRingCompletionEvent, SubmitIoRing,
15};
16use windows_sys::Win32::System::Threading::{CreateEventW, SetEvent};
17
18use crate::capability::{RingVersion, capabilities};
19use crate::error::check;
20
21/// A ring's identity, unique for the process's lifetime (PR #20 review
22/// response): every value a ring hands out that later gets checked back
23/// against it -- a [`crate::Token`], a [`crate::RegisteredFile`], a
24/// [`crate::RegisteredBuffers`] -- carries the id of the ring that minted
25/// it, and every [`Completion`] carries the id of the ring that popped it.
26///
27/// A monotonic counter rather than the ring's own `HANDLE`: a `HANDLE` is
28/// only unique while the object it names is still open, and Windows is free
29/// to hand a closed ring's numeric value to the *next* object created --
30/// which would let a stale identity from a closed ring collide with a
31/// brand-new one. This counter never repeats within one process run
32/// (`u64` overflow is not a practical concern), so a mismatch always means
33/// a genuine cross-ring mixup, never a false negative from handle reuse.
34#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
35pub(crate) struct RingId(u64);
36
37impl RingId {
38    fn next() -> Self {
39        static NEXT: AtomicU64 = AtomicU64::new(1);
40        Self(NEXT.fetch_add(1, Ordering::Relaxed))
41    }
42}
43
44/// One `IoRing` operation.
45///
46/// `#[non_exhaustive]`: the kernel's op table has grown before (M1.4, D-7)
47/// and will again. A consumer must not be able to write an exhaustive
48/// `match` that a new variant would break. [`IoRing::supports_raw`] reaches
49/// an op this enum does not yet name.
50///
51/// Naming an op here is not the same as offering a way to push it: every
52/// variant except [`Op::Nop`] gates one or more [`crate::Batch`] methods
53/// (`Read` gates the four read pushes, `Write` the four write pushes,
54/// `Flush` and `Cancel` two each, and the two registration ops one each),
55/// while `Nop` gates none and is reachable only through
56/// [`IoRing::push_raw`]. See [`IoRing::supports`] (M10.1).
57#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
58#[non_exhaustive]
59pub enum Op {
60    /// `IORING_OP_NOP`.
61    Nop,
62    /// `IORING_OP_READ`.
63    Read,
64    /// `IORING_OP_WRITE`.
65    Write,
66    /// `IORING_OP_FLUSH`.
67    Flush,
68    /// `IORING_OP_REGISTER_FILES`.
69    RegisterFiles,
70    /// `IORING_OP_REGISTER_BUFFERS`.
71    RegisterBuffers,
72    /// `IORING_OP_CANCEL`.
73    Cancel,
74}
75
76impl Op {
77    /// Every op this crate names, in a fixed order used to index the cached
78    /// capability set.
79    const ALL: [Op; 7] = [
80        Op::Nop,
81        Op::Read,
82        Op::Write,
83        Op::Flush,
84        Op::RegisterFiles,
85        Op::RegisterBuffers,
86        Op::Cancel,
87    ];
88
89    /// The raw `IORING_OP_CODE` value.
90    #[must_use]
91    pub fn code(self) -> IORING_OP_CODE {
92        match self {
93            Op::Nop => IORING_OP_NOP,
94            Op::Read => IORING_OP_READ,
95            Op::Write => IORING_OP_WRITE,
96            Op::Flush => IORING_OP_FLUSH,
97            Op::RegisterFiles => IORING_OP_REGISTER_FILES,
98            Op::RegisterBuffers => IORING_OP_REGISTER_BUFFERS,
99            Op::Cancel => IORING_OP_CANCEL,
100        }
101    }
102}
103
104/// Which ops a ring supports, probed once at construction.
105///
106/// A `u8` bitmask indexed by position in [`Op::ALL`] rather than a `HashSet`:
107/// there are exactly seven possible members, known at compile time, so a
108/// heap-allocating set would cost more than it buys.
109#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
110struct OpSupport(u8);
111
112impl OpSupport {
113    /// # Safety
114    ///
115    /// `handle` must be a live `HIORING`.
116    unsafe fn probe(handle: *mut c_void) -> Self {
117        let mut mask = 0_u8;
118        for (index, op) in Op::ALL.iter().enumerate() {
119            // SAFETY: forwarded from the caller.
120            let supported = unsafe { IsIoRingOpSupported(handle, op.code()) };
121            if supported != 0 {
122                mask |= 1 << index;
123            }
124        }
125        Self(mask)
126    }
127
128    fn contains(self, op: Op) -> bool {
129        let index = Op::ALL
130            .iter()
131            .position(|&candidate| candidate == op)
132            .expect("Op::ALL is exhaustive");
133        self.0 & (1 << index) != 0
134    }
135}
136
137/// What [`IoRing::info`] reports back from `GetIoRingInfo`.
138#[derive(Clone, Copy, Debug, PartialEq, Eq)]
139pub struct RingInfo {
140    /// The version this ring was actually created at.
141    pub version: RingVersion,
142    /// The ring's submission queue size.
143    pub submission_queue_size: u32,
144    /// The ring's completion queue size.
145    pub completion_queue_size: u32,
146}
147
148/// A failure to report in place of a completion's real result (M16.3).
149///
150/// Three spellings of the same thing, chosen so a call site reads as what it
151/// is testing rather than as a hexadecimal constant.
152///
153/// Available only under the `fault-injection` feature; see
154/// [`Completion::with_injected_failure`].
155#[cfg(any(test, feature = "fault-injection"))]
156#[derive(Clone, Copy, Debug, PartialEq, Eq)]
157pub enum InjectedFailure {
158    /// One of the ring's own `IORING_E_*` conditions.
159    Ring(crate::RingCondition),
160    /// A Win32 error code, wrapped the way the kernel wraps one -- so
161    /// `(hresult as u32) & 0xFFFF` recovers it, which is how this crate's
162    /// tests read a real one back.
163    Win32(u32),
164    /// A raw `HRESULT`, for a failure neither of the above names.
165    Hresult(windows_sys::core::HRESULT),
166}
167
168#[cfg(any(test, feature = "fault-injection"))]
169impl InjectedFailure {
170    /// The `HRESULT` this failure puts in the completion's result code.
171    ///
172    /// # Panics
173    ///
174    /// If the result would not actually be a failure. Only
175    /// [`InjectedFailure::Hresult`] can express that -- the other two spellings
176    /// always produce a failing code -- and it is rejected rather than
177    /// accepted, so that
178    /// [`Completion::with_injected_failure`]'s "failure only, never success"
179    /// guarantee is true by construction rather than by convention. A seam
180    /// that could turn a real failure into an apparent success would let a
181    /// test conceal the very defects it exists to find.
182    fn as_hresult(self) -> windows_sys::core::HRESULT {
183        let code = match self {
184            Self::Ring(condition) => condition.code(),
185            // `HRESULT_FROM_WIN32`: severity 1, facility 7 (`FACILITY_WIN32`),
186            // and the low 16 bits of the code.
187            Self::Win32(code) => (0x8007_0000_u32 | (code & 0xFFFF)).cast_signed(),
188            Self::Hresult(code) => code,
189        };
190        assert!(
191            code < 0,
192            "an injected failure must be a failing HRESULT, but {code:#010x} is not: \
193             this seam injects failure only, never success"
194        );
195        code
196    }
197}
198
199/// One popped completion (M3.7): the operation's identity, from
200/// `IORING_CQE::UserData`, and its result.
201#[derive(Clone, Copy, Debug)]
202pub struct Completion {
203    user_data: usize,
204    result_code: windows_sys::core::HRESULT,
205    information: usize,
206    ring_id: RingId,
207}
208
209impl Completion {
210    /// The `UserData` identity this completion reports -- match it against
211    /// a held [`crate::Token`] via [`crate::Token::claim_if`].
212    #[must_use]
213    pub fn user_data(&self) -> usize {
214        self.user_data
215    }
216
217    /// The identity of the ring that popped this completion (PR #20 review
218    /// response): a [`crate::Token`]/registration only ever matches a
219    /// `Completion` whose `ring_id` is also its own, so a `UserData` value
220    /// that happens to coincide across two different rings (every ring's
221    /// own counter starts at the same value) can never be confused for a
222    /// match.
223    #[must_use]
224    pub(crate) fn ring_id(&self) -> RingId {
225        self.ring_id
226    }
227
228    /// This op's result: the transferred byte count (read/write) or other
229    /// op-specific value in `IORING_CQE::Information`, once `ResultCode`
230    /// says success.
231    ///
232    /// # Errors
233    ///
234    /// Returns the wrapped [`crate::IoRingError`] if `ResultCode` is a
235    /// failure -- for example `ERROR_NOT_FOUND` when a cancel target was not
236    /// actually outstanding.
237    pub fn result(&self) -> io::Result<usize> {
238        check(self.result_code)?;
239        Ok(self.information)
240    }
241
242    /// Report `failure` from [`Completion::result`] instead of what this
243    /// operation actually returned (M16.3).
244    ///
245    /// # What this is for
246    ///
247    /// Failure paths that are otherwise unreachable in a test. Most of what a
248    /// consumer must handle -- a write that fails partway, a flush the device
249    /// rejects, a registration the kernel refuses -- cannot be provoked on
250    /// demand from a healthy machine, so that code is typically written once
251    /// and never executed again. This crate shipped a defect of exactly that
252    /// shape: a claim path that returned early on a failed write and leaked
253    /// its registered-buffer slot permanently, on a branch no test had ever
254    /// taken.
255    ///
256    /// # Why this is sound, where fabricating a completion would not be
257    ///
258    /// [`crate::Token::claim_if`]'s safety argument is that a `Completion` for
259    /// some `UserData` **existing at all** proves the kernel has finished with
260    /// that operation, and therefore that handing its buffer back is sound.
261    ///
262    /// This method consumes a completion the ring genuinely popped and returns
263    /// one carrying the same `UserData` and the same ring identity. The
264    /// operation really did finish; the only thing that changes is what
265    /// [`Completion::result`] says about it. So the argument above is
266    /// untouched, and claiming against the result is exactly as sound as
267    /// claiming against the original.
268    ///
269    /// `Completion::synthetic` is the opposite, and is why it is not
270    /// reachable from here. A fabricated completion can name an operation that
271    /// is **still in flight**, and claiming a token against one hands a buffer
272    /// back to the caller while the kernel is still writing through it -- the
273    /// precise use-after-free this crate exists to prevent. That is a
274    /// test-only, crate-only tool and stays one.
275    ///
276    /// # What it cannot do
277    ///
278    /// Only failure can be injected, never success. Turning a real failure
279    /// into an apparent success would let a test conceal a genuine defect,
280    /// which is the wrong affordance for a tool whose whole purpose is
281    /// exercising error handling. That is enforced rather than merely stated:
282    /// a non-failing `HRESULT` panics.
283    ///
284    /// # Panics
285    ///
286    /// If `failure` does not resolve to a failing `HRESULT`. Only
287    /// [`InjectedFailure::Hresult`] can express that at all.
288    ///
289    /// # Prefer a real failure when one is reachable
290    ///
291    /// A genuine kernel error -- writing through a read-only handle, cancelling
292    /// something that is not outstanding -- tests the real path *and* confirms
293    /// the crate's own error translation. Reach for this only where no such
294    /// route exists.
295    ///
296    /// # The one place injection is *not* inert: registration completions
297    ///
298    /// The soundness argument above is about [`crate::Token::claim_if`], where
299    /// a failed completion changes nothing the caller does with memory: the
300    /// buffer comes back either way. **A registration claim is different.**
301    /// [`crate::PendingBufferRegistration::claim_if`] treats a failed
302    /// completion as proof the kernel did *not* retain the addresses, and so
303    /// **drops the buffers**. Inject a failure there and it frees memory the
304    /// kernel genuinely does hold registered -- leaving the ring with a
305    /// registration pointing at freed pages.
306    ///
307    /// That is inert only while nothing uses it. A test doing this must not
308    /// push any registered-buffer operation on that ring afterwards, and
309    /// should tear the ring down immediately.
310    ///
311    /// This is a limitation of *what a failed registration means*, not
312    /// something the seam can check: a `Completion` does not carry which
313    /// operation produced it, so this cannot be refused at the call. Prefer a
314    /// genuine failure for registration paths wherever one can be provoked.
315    ///
316    /// # Example
317    ///
318    /// ```ignore
319    /// let completion = completion.with_injected_failure(
320    ///     InjectedFailure::Win32(ERROR_ACCESS_DENIED),
321    /// );
322    /// assert!(completion.result().is_err());
323    /// // The token still claims it: the operation did complete.
324    /// let buffer = token.claim_if(&completion).expect("claims its own");
325    /// ```
326    #[cfg(any(test, feature = "fault-injection"))]
327    #[must_use]
328    pub fn with_injected_failure(self, failure: InjectedFailure) -> Self {
329        Self {
330            result_code: failure.as_hresult(),
331            // Zeroed deliberately: a failed operation transferred nothing, and
332            // leaving a success's byte count behind would model a state the
333            // kernel never produces.
334            information: 0,
335            ..self
336        }
337    }
338
339    /// Build a `Completion` without popping a real one, for tests that
340    /// exercise [`crate::Token::claim_if`] without real I/O.
341    ///
342    /// Not available outside `#[cfg(test)]`: production code has no
343    /// legitimate reason to fabricate a completion, since `Token::claim_if`'s
344    /// whole safety argument depends on every `Completion` in existence
345    /// tracing back to a real `IORING_CQE` `IoRing::try_pop` observed.
346    #[cfg(test)]
347    pub(crate) fn synthetic(
348        user_data: usize,
349        result_code: windows_sys::core::HRESULT,
350        ring_id: RingId,
351    ) -> Self {
352        Self {
353            user_data,
354            result_code,
355            information: 0,
356            ring_id,
357        }
358    }
359}
360
361/// How `S_FALSE` reads as a raw `HRESULT`: `PopIoRingCompletion`'s documented
362/// "the completion queue is empty" result. Not a failure (`FAILED(hr)` is
363/// false for it), so [`check`](crate::error::check) alone cannot distinguish
364/// it from `S_OK`.
365const S_FALSE: windows_sys::core::HRESULT = 1;
366
367/// How long each rundown poll blocks before rechecking `outstanding`, rather
368/// than one unbounded wait -- the same discipline
369/// `windows-overlapped-io-sys`'s own rundown uses (its DESIGN-NOTES: "Rundown
370/// waits are bounded and rechecked, not unbounded").
371const RUN_DOWN_POLL_MS: u32 = 50;
372
373/// An owned `IoRing`, closed with `CloseIoRing` on drop.
374///
375/// Not `Clone`: cloning would give two owners of the same native ring, and
376/// `CloseIoRing` would run twice. Not `Sync`: building a submission is not
377/// thread-safe (D-5 in `DESIGN-NOTES.md`), so sharing `&IoRing` across
378/// threads is deliberately not offered -- a consumer wanting concurrent
379/// access chooses a delivery architecture (M4 / M6+) rather than relying on
380/// this type to serialize for them.
381// `Debug` is hand-written rather than derived: `IORING_BUFFER_INFO` does not
382// implement it, and the array's contents (raw addresses and lengths) are not
383// useful to print anyway -- its length is.
384pub struct IoRing {
385    handle: *mut c_void,
386    version: RingVersion,
387    supported_ops: OpSupport,
388    /// This ring's own identity (PR #20 review response); see [`RingId`].
389    ring_id: RingId,
390    /// The next `UserData` value [`IoRing::reserve_user_data`] will hand out.
391    next_user_data: usize,
392    /// Operations minted but not yet observed to have completed (M2.4).
393    outstanding: usize,
394    /// How many file handles are registered so far, across every confirmed
395    /// `BuildIoRingRegisterFileHandles` (M5.1). The base index of the next
396    /// registration.
397    registered_files: u32,
398    /// As `registered_files`, for `BuildIoRingRegisterBuffers` (M5.2).
399    registered_buffers: u32,
400    /// The `IORING_BUFFER_INFO` array handed to `BuildIoRingRegisterBuffers`,
401    /// kept alive because the kernel reads it when the registration op
402    /// *runs*, not when the `Build*` call returns (D-32, measured).
403    ///
404    /// Held by the ring rather than by the `Batch` that built it: a failed
405    /// `SubmitIoRing` leaves the SQE queued as ring state (D-5), so a later,
406    /// unrelated submit can be what finally runs it -- after that batch is
407    /// long gone. A ring accepts at most one buffer registration, so this is
408    /// one small allocation per ring, and it is released only by
409    /// `CloseIoRing`.
410    registered_buffer_infos: Vec<IORING_BUFFER_INFO>,
411    /// The completion event this ring created and attached, once
412    /// [`IoRing::completion_event`] has been called (M11.1, D-20).
413    ///
414    /// The ring owns it and hands callers duplicates, which is what makes
415    /// the returned handle safe to hold for any length of time: closing a
416    /// duplicate cannot leave the ring signalling a closed handle, and the
417    /// kernel still signals the surviving duplicates.
418    ///
419    /// Dropped *after* `CloseIoRing` runs, because a manual `Drop` impl's
420    /// body runs before its fields are dropped -- so the ring is closed, and
421    /// can no longer signal, before the event it referenced goes away.
422    completion_event: Option<OwnedHandle>,
423}
424
425impl std::fmt::Debug for IoRing {
426    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
427        f.debug_struct("IoRing")
428            .field("handle", &self.handle)
429            .field("version", &self.version)
430            .field("supported_ops", &self.supported_ops)
431            .field("ring_id", &self.ring_id)
432            .field("next_user_data", &self.next_user_data)
433            .field("outstanding", &self.outstanding)
434            .field("registered_files", &self.registered_files)
435            .field("registered_buffers", &self.registered_buffers)
436            .field(
437                "registered_buffer_infos",
438                &self.registered_buffer_infos.len(),
439            )
440            .field("completion_event", &self.completion_event)
441            .finish()
442    }
443}
444
445// SAFETY: HIORING is a Windows kernel object handle. Windows handles are not
446// tied to the thread that created them and may be closed from any thread, so
447// moving ownership of one to another thread is sound. This does not imply
448// `Sync`: submitting to the ring is not thread-safe (D-5), so only `Send` is
449// implemented.
450unsafe impl Send for IoRing {}
451
452impl IoRing {
453    /// Create a ring, negotiating the version as `min(RingVersion::HIGHEST_KNOWN,
454    /// capabilities()?.max_version)` (D-6).
455    ///
456    /// # Errors
457    ///
458    /// Returns any error from `QueryIoRingCapabilities` or `CreateIoRing`.
459    pub fn new(submission_queue_size: u32, completion_queue_size: u32) -> io::Result<Self> {
460        let caps = capabilities()?;
461        let version = RingVersion::HIGHEST_KNOWN.min(caps.max_version);
462        Self::with_version(version, submission_queue_size, completion_queue_size)
463    }
464
465    /// Create a ring at exactly `version`, without negotiation.
466    ///
467    /// # Errors
468    ///
469    /// Returns [`IoRingError`](crate::IoRingError) wrapping
470    /// `IORING_E_VERSION_NOT_SUPPORTED` if `version` exceeds what the system
471    /// supports, or any other error from `CreateIoRing`.
472    pub fn with_version(
473        version: RingVersion,
474        submission_queue_size: u32,
475        completion_queue_size: u32,
476    ) -> io::Result<Self> {
477        let flags = IORING_CREATE_FLAGS {
478            Required: IORING_CREATE_REQUIRED_FLAGS_NONE,
479            Advisory: IORING_CREATE_ADVISORY_FLAGS_NONE,
480        };
481        let mut handle: *mut c_void = std::ptr::null_mut();
482        // SAFETY: `handle` is a valid out-pointer; `flags` is a documented,
483        // all-`_NONE` value.
484        let hr = unsafe {
485            CreateIoRing(
486                version.raw(),
487                flags,
488                submission_queue_size,
489                completion_queue_size,
490                &raw mut handle,
491            )
492        };
493        check(hr)?;
494        // SAFETY: `handle` was just created successfully above and is not
495        // shared with anything else yet.
496        let supported_ops = unsafe { OpSupport::probe(handle) };
497        Ok(Self {
498            handle,
499            version,
500            supported_ops,
501            ring_id: RingId::next(),
502            next_user_data: 0,
503            outstanding: 0,
504            registered_files: 0,
505            registered_buffers: 0,
506            registered_buffer_infos: Vec::new(),
507            completion_event: None,
508        })
509    }
510
511    /// The version this ring was created at.
512    #[must_use]
513    pub fn version(&self) -> RingVersion {
514        self.version
515    }
516
517    /// Query this ring's current info via `GetIoRingInfo`.
518    ///
519    /// # Errors
520    ///
521    /// Returns any error from `GetIoRingInfo`.
522    pub fn info(&self) -> io::Result<RingInfo> {
523        let mut raw = IORING_INFO::default();
524        // SAFETY: `self.handle` is a live ring; `raw` is a valid out-pointer.
525        let hr = unsafe { GetIoRingInfo(self.handle, &raw mut raw) };
526        check(hr)?;
527        Ok(RingInfo {
528            version: RingVersion::from_raw(raw.IoRingVersion),
529            submission_queue_size: raw.SubmissionQueueSize,
530            completion_queue_size: raw.CompletionQueueSize,
531        })
532    }
533
534    /// Whether this ring supports `op`, from the capability set cached at
535    /// construction.
536    ///
537    /// Answers what the *kernel's* op table contains, not what this crate's
538    /// safe push surface reaches (M10.1, [`Op`]'s own docs list the mapping).
539    /// The two coincide for every op except [`Op::Nop`], which has no
540    /// [`crate::Batch`] method at all: a nop owns no buffer, so there is
541    /// nothing for a [`crate::Token`] to hand back, and it is reachable only
542    /// through [`IoRing::push_raw`]. A `true` here therefore means "the
543    /// kernel would accept this op", not "a `Batch` method exists to push
544    /// it".
545    #[must_use]
546    pub fn supports(&self, op: Op) -> bool {
547        self.supported_ops.contains(op)
548    }
549
550    /// An owned duplicate of this ring's completion event, so a caller can
551    /// wait on the ring alongside other handles without surrendering it
552    /// (M11.1, D-20).
553    ///
554    /// The ring creates the event, attaches it with
555    /// `SetIoRingCompletionEvent`, keeps ownership, and hands back a
556    /// duplicate. Closing the returned handle is therefore always safe: the
557    /// ring keeps signalling its own copy, and a `DuplicateHandle`'d event is
558    /// still signalled after the original is closed.
559    ///
560    /// **Idempotent.** Repeat calls return another duplicate of the *same*
561    /// event rather than attaching a new one, so two subsystems can each ask
562    /// without silently detaching the other's.
563    ///
564    /// This is not a third delivery architecture -- it is Model B with a
565    /// multiplexed wakeup source, changing only what the domain thread blocks
566    /// on, never who owns, submits, or drains ([`Batch::submit_and_wait`] is
567    /// still the single-source shape). Use it when ring I/O has to be waited
568    /// on alongside non-ring I/O, which
569    /// `IOSQE_FLAGS_DRAIN_PRECEDING_OPS` cannot order across.
570    ///
571    /// [`Batch::submit_and_wait`]: crate::Batch::submit_and_wait
572    ///
573    /// # The event is an edge, not a level
574    ///
575    /// **Measured, not inferred, and getting it wrong hangs rather than just
576    /// slowing down** (D-19). The event is signalled when the completion
577    /// queue transitions from **empty to non-empty**. It is *not* signalled
578    /// once per completion, and it is *not* level-triggered: eight
579    /// completions arriving at once into an empty queue produce exactly one
580    /// wakeup, and a completion arriving into an already-non-empty queue
581    /// produces none.
582    ///
583    /// Two rules follow, and they are contract rather than advice:
584    ///
585    /// 1. **Drain to empty before waiting again** -- [`IoRing::try_pop`]
586    ///    until it yields `None`, on *every* pass through a multiplexed wait
587    ///    loop, not only the pass where this handle signalled. A wait entered
588    ///    with entries still in the queue blocks until some later completion
589    ///    arrives *after* the queue has been emptied, which may be never.
590    ///    That is a lost-wakeup deadlock, not a latency wobble.
591    /// 2. **A wake with nothing to pop is normal.** It must not be treated as
592    ///    an error or as a spurious wakeup. This method deliberately produces
593    ///    one: the event is signalled once before it returns, so a caller who
594    ///    had already submitted work never misses a backlog that arrived
595    ///    before the event was attached.
596    ///
597    /// The event is auto-reset, and **exactly one waiter per ring** is
598    /// supported (D-21): the drain that restores the empty state, and so
599    /// re-arms the edge, must run to empty exactly once. Two threads waiting
600    /// on one ring's event cannot be made correct.
601    ///
602    /// `examples/model_b_multiplexed.rs` is this whole shape worked end to
603    /// end -- a caller-owned ring waited on alongside a shutdown latch, with
604    /// the quiesce that shutdown-while-outstanding requires (M11.6).
605    ///
606    /// # Errors
607    ///
608    /// Returns [`io::ErrorKind::Unsupported`] if the running system does not
609    /// report `IORING_FEATURE_SET_COMPLETION_EVENT`; this crate refuses to
610    /// silently substitute a polling thread, since a caller who asked for an
611    /// event and got a spun-up thread has been told something false. Also
612    /// returns any error from `CreateEventW`,
613    /// `SetIoRingCompletionEvent`, `SetEvent`, or duplicating the handle.
614    pub fn completion_event(&mut self) -> io::Result<OwnedHandle> {
615        // Already attached: hand back another duplicate rather than
616        // attaching a second event, which would silently detach the first
617        // (`SetIoRingCompletionEvent` replaces rather than adds). The
618        // capability was necessarily verified on the call that attached it.
619        if let Some(event) = &self.completion_event {
620            return event.try_clone();
621        }
622
623        if !capabilities()?.supports_completion_event {
624            return Err(io::Error::new(
625                io::ErrorKind::Unsupported,
626                "this system's IoRing does not report IORING_FEATURE_SET_COMPLETION_EVENT",
627            ));
628        }
629
630        // Auto-reset (manual_reset = FALSE) per D-21, initially unsignalled
631        // -- the deliberate setup signal is raised below, after the event is
632        // attached and owned, so it cannot be missed or lost.
633        // SAFETY: null attributes and name are documented defaults.
634        let raw = unsafe { CreateEventW(std::ptr::null(), 0, 0, std::ptr::null()) };
635        if raw.is_null() {
636            return Err(io::Error::last_os_error());
637        }
638        // SAFETY: `CreateEventW` just returned this handle and it is not
639        // owned anywhere else, so `OwnedHandle` is its sole owner from here.
640        let event = unsafe { OwnedHandle::from_raw_handle(raw) };
641
642        // SAFETY: `self.handle` is a live ring; `event` is a live event that
643        // this ring will own for the rest of its life once stored below.
644        let hr = unsafe { SetIoRingCompletionEvent(self.handle, event.as_raw_handle()) };
645        // On failure `event` drops here, closing a handle the ring never
646        // successfully referenced.
647        check(hr)?;
648
649        // Stored *before* signalling: from this point the ring owns the
650        // event, so no later failure can drop it and leave the ring
651        // signalling a closed (possibly recycled) handle.
652        let event = self.completion_event.insert(event);
653
654        // The one deliberate spurious wakeup (rule 2 above): a caller who
655        // submitted before attaching would otherwise never be woken for that
656        // backlog, since the queue never returns to empty to re-arm the edge.
657        // SAFETY: `event` is a live event handle this ring owns.
658        if unsafe { SetEvent(event.as_raw_handle()) } == 0 {
659            return Err(io::Error::last_os_error());
660        }
661
662        event.try_clone()
663    }
664
665    /// Whether this ring supports a raw op code, including one this crate
666    /// does not yet name (D-7).
667    ///
668    /// Its reason to exist is an op outside [`Op`], which by definition this
669    /// ring's cached capability set was never probed for -- but passing a
670    /// named op's [`Op::code`] is equally in contract, and answers
671    /// identically to [`IoRing::supports`] (M10.1). The difference between
672    /// them is cost, not truth: `supports` is a bit test against the set
673    /// probed once at construction, this is an `IsIoRingOpSupported` call
674    /// every time.
675    ///
676    /// What a `true` here does *not* mean is that the op became pushable: an
677    /// op outside [`Op`] has no builder method whatever this answers, so
678    /// [`IoRing::push_raw`] remains the only route to one.
679    #[must_use]
680    pub fn supports_raw(&self, op_code: IORING_OP_CODE) -> bool {
681        // SAFETY: `self.handle` is a live ring.
682        unsafe { IsIoRingOpSupported(self.handle, op_code) != 0 }
683    }
684
685    /// How many file handles this ring has **reserved** for registration --
686    /// not how many are confirmed registered (M5.1, M10.3, D-31).
687    ///
688    /// The count advances the instant a `BuildIoRingRegisterFileHandles`
689    /// call queues, never when its completion is observed. Two consequences
690    /// a caller must not be surprised by:
691    ///
692    /// - it is already advanced before any completion has been popped, so it
693    ///   cannot be used to decide whether a registration has taken effect --
694    ///   claim the completion with
695    ///   [`crate::PendingFileRegistration::claim_if`] for that;
696    /// - it stays advanced after a registration whose completion reported
697    ///   *failure*, which is why such a registration cannot be retried on
698    ///   this ring ([`crate::Batch::register_files`]).
699    ///
700    /// Because a ring accepts at most one registration that assigns an
701    /// index, this is `0` until that registration is queued and its count
702    /// thereafter; there is no second registration for it to serve as a base
703    /// index for.
704    #[must_use]
705    pub fn registered_file_count(&self) -> u32 {
706        self.registered_files
707    }
708
709    /// As [`IoRing::registered_file_count`], for registered buffers (M5.2) --
710    /// a **reserved** count, not a confirmed one, with the same two
711    /// consequences (M10.3, D-31).
712    #[must_use]
713    pub fn registered_buffer_count(&self) -> u32 {
714        self.registered_buffers
715    }
716
717    /// Advance the registered-file base index by `count`, the instant a
718    /// `BuildIoRingRegisterFileHandles` call successfully queues (not once
719    /// its completion is observed).
720    ///
721    /// D-14 recorded this as an explicitly unverified assumption, since this
722    /// crate cannot know whether the kernel claims these `count` indices
723    /// synchronously at build time or only once the registration op runs.
724    /// D-31 (M10.3) dissolved that: the collision it guarded against needs a
725    /// *second* registration, and `Batch::register_files`/`register_buffers`
726    /// forbid one, so no later base index is ever derived from this count and
727    /// the kernel's actual timing has no observable consequence. What the
728    /// eager advance does still determine is the *meaning* of the public
729    /// accessors, which is why they document a reserved rather than a
730    /// confirmed count.
731    ///
732    /// D-32 did not answer this question, despite being adjacent to it: it
733    /// established when the kernel reads the `IORING_BUFFER_INFO` *array*,
734    /// which is a different thing from when it claims the *indices*. The
735    /// latter remains unmeasured, and dissolved rather than resolved.
736    pub(crate) fn reserve_registered_files(&mut self, count: u32) {
737        self.registered_files = self.registered_files.saturating_add(count);
738    }
739
740    /// As [`IoRing::reserve_registered_files`], for registered buffers.
741    pub(crate) fn reserve_registered_buffers(&mut self, count: u32) {
742        self.registered_buffers = self.registered_buffers.saturating_add(count);
743    }
744
745    /// Take ownership of the `IORING_BUFFER_INFO` array a
746    /// `BuildIoRingRegisterBuffers` call was handed, keeping it alive for
747    /// this ring's remaining life, and hand back a stable pointer to it
748    /// (D-32).
749    ///
750    /// The kernel reads this array when the registration op *runs*, not when
751    /// the `Build*` call returns, so the caller must not build the SQE from a
752    /// temporary: store the array here first and pass the returned pointer.
753    /// Returns a null pointer for an empty array, which is what
754    /// `BuildIoRingRegisterBuffers` should be handed for a zero-length
755    /// registration anyway.
756    pub(crate) fn hold_registered_buffer_infos(
757        &mut self,
758        infos: Vec<IORING_BUFFER_INFO>,
759    ) -> *const IORING_BUFFER_INFO {
760        debug_assert!(
761            self.registered_buffer_infos.is_empty(),
762            "a ring accepts at most one buffer registration, so this must only be set once"
763        );
764        self.registered_buffer_infos = infos;
765        // `Vec::as_ptr` is stable for as long as the `Vec` is neither moved
766        // out of nor reallocated; it lives in `self` and is never mutated
767        // again, and moving the `IoRing` itself moves only the `Vec` header,
768        // not its heap allocation.
769        self.registered_buffer_infos.as_ptr()
770    }
771
772    /// How many operations this ring believes are still outstanding: minted
773    /// (via `reserve_user_data`) but not yet observed to have completed (via
774    /// `record_completion`).
775    #[must_use]
776    pub fn outstanding(&self) -> usize {
777        self.outstanding
778    }
779
780    /// Mint a fresh `UserData` identity for a new operation, and account for
781    /// it as outstanding until `record_completion` is called for it.
782    ///
783    /// The identity is the whole of what a [`crate::Token`] needs to validate
784    /// a completion (D-4): unlike `windows-overlapped-io-sys`'s
785    /// `OperationId`, there is no separate storage address to pair it with,
786    /// because `UserData` is a value this crate chooses rather than one Win32
787    /// hands back.
788    ///
789    /// # Errors
790    ///
791    /// Returns an error rather than reusing an identity if the `usize` space
792    /// is ever exhausted, mirroring `windows-threadpool-sys`'s own
793    /// "exhausting the generation sequence fails rather than wraps."
794    pub(crate) fn reserve_user_data(&mut self) -> io::Result<usize> {
795        let id = self.next_user_data;
796        self.next_user_data = id
797            .checked_add(1)
798            .ok_or_else(|| io::Error::other("IoRing operation identity space exhausted"))?;
799        self.outstanding += 1;
800        Ok(id)
801    }
802
803    /// Record that one outstanding operation's completion has been observed
804    /// (a real `IORING_CQE` was popped for it), whether or not a live
805    /// [`crate::Token`] was still around to claim it.
806    pub(crate) fn record_completion(&mut self) {
807        self.outstanding = self.outstanding.saturating_sub(1);
808    }
809
810    /// Release a reservation for an operation that was never actually
811    /// queued -- a `Build*` call failed synchronously, after
812    /// [`IoRing::reserve_user_data`] had already minted its identity.
813    ///
814    /// Distinct from [`IoRing::record_completion`]: that marks a real
815    /// `IORING_CQE` observed; this marks one that will never arrive because
816    /// the op never entered the queue, so it must not count against
817    /// [`IoRing::run_down`] either.
818    pub(crate) fn cancel_reservation(&mut self) {
819        self.outstanding = self.outstanding.saturating_sub(1);
820    }
821
822    /// This ring's native handle, for `batch.rs`'s `Build*`/`Submit` calls.
823    pub(crate) fn raw_handle(&self) -> *mut c_void {
824        self.handle
825    }
826
827    /// This ring's own identity, for stamping onto every [`crate::Token`]/
828    /// registration it mints and checking against on use (PR #20 review
829    /// response); see [`RingId`].
830    pub(crate) fn ring_id(&self) -> RingId {
831        self.ring_id
832    }
833
834    /// Queue a raw, not-yet-wrapped SQE via a caller-supplied `Build*` call
835    /// (M3.5, D-7).
836    ///
837    /// `build` receives this ring's native handle and a freshly reserved
838    /// `UserData` value, and must call exactly one `BuildIoRing*` function
839    /// with them, returning its `HRESULT`. On success the `UserData` is
840    /// returned so the caller can match it against a later [`Completion`]
841    /// popped by [`IoRing::try_pop`]; on failure the reservation is
842    /// released, since the op was never actually queued.
843    ///
844    /// # Errors
845    ///
846    /// Returns any error `build`'s `HRESULT` reports.
847    ///
848    /// # Safety
849    ///
850    /// `build` must queue an SQE for a *self-contained* op: everything the
851    /// kernel reads or writes for it must stay valid until the
852    /// corresponding completion is observed, and this crate cannot verify
853    /// what `build` does with the handle it is given. This is the same
854    /// framing as `windows-overlapped-io-sys`'s raw `ioctl` seam
855    /// (`device.rs`): the mechanics of building an SQE need nothing unsafe,
856    /// but this crate cannot audit an arbitrary `Build*` call, so the seam
857    /// itself is unsafe.
858    pub unsafe fn push_raw(
859        &mut self,
860        build: impl FnOnce(*mut c_void, usize) -> windows_sys::core::HRESULT,
861    ) -> io::Result<usize> {
862        let user_data = self.reserve_user_data()?;
863        let hr = build(self.handle, user_data);
864        if let Err(error) = check(hr) {
865            self.cancel_reservation();
866            return Err(error);
867        }
868        Ok(user_data)
869    }
870
871    /// Block until every outstanding operation has completed, so
872    /// `CloseIoRing` never runs while the kernel might still be touching a
873    /// token's buffer.
874    ///
875    /// Waits in short, rechecked steps via `SubmitIoRing`'s own wait -- with
876    /// zero new entries queued, its only effect is to block for up to
877    /// `RUN_DOWN_POLL_MS` and reap whatever is already outstanding -- rather
878    /// than one unbounded call. This does not interpret what it pops; M3/M4
879    /// add the typed completion path `Token` consumes. Idempotent: calling it
880    /// again once `outstanding() == 0` is a no-op.
881    ///
882    /// # Errors
883    ///
884    /// Returns any error from `SubmitIoRing` or `PopIoRingCompletion`.
885    pub fn run_down(&mut self) -> io::Result<()> {
886        while self.outstanding > 0 {
887            let mut submitted = 0_u32;
888            // SAFETY: `self.handle` is a live ring; valid out-pointer. Zero
889            // new SQEs are queued -- this call's only purpose is to wait for
890            // and reap already-outstanding completions.
891            let hr = unsafe { SubmitIoRing(self.handle, 1, RUN_DOWN_POLL_MS, &raw mut submitted) };
892            check(hr)?;
893            self.drain_for_rundown()?;
894        }
895        Ok(())
896    }
897
898    /// Pop every currently available completion, recording each -- without
899    /// interpreting it, since rundown only needs to know a completion
900    /// happened, not what it was.
901    fn drain_for_rundown(&mut self) -> io::Result<()> {
902        while self.try_pop()?.is_some() {}
903        Ok(())
904    }
905
906    /// Pop one completion if the queue has one ready, without blocking
907    /// (M3.7).
908    ///
909    /// Every popped completion is recorded via `record_completion`
910    /// regardless of whether the caller still holds a [`crate::Token`] for
911    /// it (D-4): accounting is driven by observing a real `IORING_CQE`,
912    /// never by a token being dropped.
913    ///
914    /// `None` says the completion queue is empty *at this instant*, never
915    /// that an operation will not complete. **Every SQE that successfully
916    /// queues produces exactly one completion** (M10.2) -- unconditionally,
917    /// which is what lets [`IoRing::run_down`] terminate. The only push that
918    /// yields no completion is one whose `Build*` call failed synchronously,
919    /// and that push's reservation is released rather than left outstanding.
920    ///
921    /// A popped completion matching no live [`crate::Token`] is **normal**,
922    /// not a bug, and a drain loop must not treat it as one. It happens for
923    /// a registration (claimed by [`crate::PendingFileRegistration`] or
924    /// [`crate::PendingBufferRegistration`] instead), for a
925    /// [`crate::Batch::flush_raw`]/[`crate::Batch::cancel_raw`] push (which
926    /// return a bare identity because they own no buffer), for a cancel's own
927    /// completion as distinct from its target's, and for a token the caller
928    /// dropped unclaimed.
929    ///
930    /// # Errors
931    ///
932    /// Returns any error from `PopIoRingCompletion` other than its
933    /// documented empty-queue result.
934    pub fn try_pop(&mut self) -> io::Result<Option<Completion>> {
935        let mut cqe = IORING_CQE {
936            UserData: 0,
937            ResultCode: 0,
938            Information: 0,
939        };
940        // SAFETY: `self.handle` is a live ring; valid out-pointer.
941        let hr = unsafe { PopIoRingCompletion(self.handle, &raw mut cqe) };
942        if hr == S_FALSE {
943            return Ok(None);
944        }
945        check(hr)?;
946        self.record_completion();
947        Ok(Some(Completion {
948            user_data: cqe.UserData,
949            result_code: cqe.ResultCode,
950            information: cqe.Information,
951            ring_id: self.ring_id,
952        }))
953    }
954}
955
956impl Drop for IoRing {
957    fn drop(&mut self) {
958        // Best-effort rundown: a ring with an operation still outstanding at
959        // drop time is a use bug (M3's Batch/Token are the sanctioned way to
960        // avoid it), but Drop cannot propagate the error, so this asserts in
961        // debug builds rather than silently closing a ring the kernel may
962        // still be writing through.
963        if let Err(error) = self.run_down() {
964            debug_assert!(false, "IoRing rundown failed before close: {error}");
965        }
966        // SAFETY: `self.handle` is a live ring this `IoRing` exclusively
967        // owns, and `run_down` just established that nothing is outstanding
968        // (or made a best-effort attempt to, above).
969        let hr = unsafe { CloseIoRing(self.handle) };
970        debug_assert!(hr >= 0, "CloseIoRing failed: 0x{:08X}", hr as u32);
971    }
972}
973
974#[cfg(test)]
975mod tests;