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;