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;