windows_ioring_sys/ring/bookkeeping.rs
1// Copyright (c) 2026 Mike Grier
2// Split from ring.rs at 265a7a04.
3//! What the ring knows about itself: negotiated capabilities, the completion
4//! event, registration counts, the operation ledger, and rundown.
5//!
6//! A child of [`super`] rather than a sibling, because Rust's privacy is
7//! asymmetric: rundown here calls the parent's private `is_quiescent` and
8//! `drain_for_rundown`, which must stay up there so `Drop` can reach them too.
9
10use super::*;
11
12impl<T, X, K> IoRing<T, X, K> {
13 /// The version this ring was created at.
14 #[must_use]
15 pub fn version(&self) -> RingVersion {
16 self.version
17 }
18
19 /// Query this ring's current info via `GetIoRingInfo`.
20 ///
21 /// # Errors
22 ///
23 /// Returns any error from `GetIoRingInfo`.
24 pub fn info(&self) -> io::Result<RingInfo> {
25 let mut raw = IORING_INFO::default();
26 // SAFETY: `self.handle` is a live ring; `raw` is a valid out-pointer.
27 let hr = unsafe { GetIoRingInfo(self.handle, &raw mut raw) };
28 check(hr)?;
29 Ok(RingInfo {
30 version: RingVersion::from_raw(raw.IoRingVersion),
31 submission_queue_size: raw.SubmissionQueueSize,
32 completion_queue_size: raw.CompletionQueueSize,
33 })
34 }
35
36 /// Whether this ring supports `op`, from the capability set cached at
37 /// construction.
38 ///
39 /// Answers what the *kernel's* op table contains, not what this crate's
40 /// safe push surface reaches (M10.1, [`Op`]'s own docs list the mapping).
41 /// The two coincide for every op except [`Op::Nop`], which has no
42 /// [`crate::Batch`] method at all: a nop owns no buffer, so there is
43 /// nothing for the ring to hand back, and it is reachable only
44 /// through [`IoRing::push_raw`]. A `true` here therefore means "the
45 /// kernel would accept this op", not "a `Batch` method exists to push
46 /// it".
47 #[must_use]
48 pub fn supports(&self, op: Op) -> bool {
49 self.supported_ops.contains(op)
50 }
51
52 /// Overrides the cached capability set to exactly `ops`, for tests that
53 /// need a ring known to lack support for something.
54 ///
55 /// Every real host this crate has been tested against supports all seven
56 /// named ops, which is exactly why [`IoRing::supports`] and
57 /// [`Batch::require`](crate::Batch)'s use of it could not be told apart
58 /// from a constant `true` by any test that only ever asked a real ring:
59 /// the honest answer and the constant agree on every host available to
60 /// run the test. This seam constructs the disagreement instead of hoping
61 /// to find a host that has it.
62 ///
63 /// Not available outside `#[cfg(test)]`, for the same reason
64 /// [`Completion::synthetic`] is not: production code has no legitimate
65 /// reason to claim a capability the kernel did not actually report.
66 #[cfg(test)]
67 pub(crate) fn set_supported_ops_for_test(&mut self, ops: &[Op]) {
68 self.supported_ops = OpSupport(ops.iter().fold(0_u8, |mask, &op| {
69 let index = Op::ALL
70 .iter()
71 .position(|&candidate| candidate == op)
72 .expect("Op::ALL is exhaustive");
73 mask | (1 << index)
74 }));
75 }
76
77 /// An owned duplicate of this ring's completion event, so a caller can
78 /// wait on the ring alongside other handles without surrendering it
79 /// (M11.1, D-20).
80 ///
81 /// The ring creates the event, attaches it with
82 /// `SetIoRingCompletionEvent`, keeps ownership, and hands back a
83 /// duplicate. Closing the returned handle is therefore always safe: the
84 /// ring keeps signalling its own copy, and a `DuplicateHandle`'d event is
85 /// still signalled after the original is closed.
86 ///
87 /// **Idempotent.** Repeat calls return another duplicate of the *same*
88 /// event rather than attaching a new one, so two subsystems can each ask
89 /// without silently detaching the other's.
90 ///
91 /// This is not a third delivery architecture -- it is Model B with a
92 /// multiplexed wakeup source, changing only what the domain thread blocks
93 /// on, never who owns, submits, or drains ([`Batch::submit_and_wait`] is
94 /// still the single-source shape). Use it when ring I/O has to be waited
95 /// on alongside non-ring I/O, which
96 /// `IOSQE_FLAGS_DRAIN_PRECEDING_OPS` cannot order across.
97 ///
98 /// [`Batch::submit_and_wait`]: crate::Batch::submit_and_wait
99 ///
100 /// # The event is an edge, not a level
101 ///
102 /// **Measured, not inferred, and getting it wrong hangs rather than just
103 /// slowing down** (D-19). The event is signalled when the completion
104 /// queue transitions from **empty to non-empty**. It is *not* signalled
105 /// once per completion, and it is *not* level-triggered: eight
106 /// completions arriving at once into an empty queue produce exactly one
107 /// wakeup, and a completion arriving into an already-non-empty queue
108 /// produces none.
109 ///
110 /// Two rules follow, and they are contract rather than advice:
111 ///
112 /// 1. **Drain to empty before waiting again** -- [`IoRing::try_pop`]
113 /// until it yields `None`, on *every* pass through a multiplexed wait
114 /// loop, not only the pass where this handle signalled. A wait entered
115 /// with entries still in the queue blocks until some later completion
116 /// arrives *after* the queue has been emptied, which may be never.
117 /// That is a lost-wakeup deadlock, not a latency wobble.
118 /// 2. **A wake with nothing to pop is normal.** It must not be treated as
119 /// an error or as a spurious wakeup. This method deliberately produces
120 /// one: the event is signalled once before it returns, so a caller who
121 /// had already submitted work never misses a backlog that arrived
122 /// before the event was attached.
123 ///
124 /// The event is auto-reset, and **exactly one waiter per ring** is
125 /// supported (D-21): the drain that restores the empty state, and so
126 /// re-arms the edge, must run to empty exactly once. Two threads waiting
127 /// on one ring's event cannot be made correct.
128 ///
129 /// # If you are arming a thread-pool wait on this handle
130 ///
131 /// `SetThreadpoolWait` documents that "you must re-register the event
132 /// with the wait object before signaling it each time to trigger the wait
133 /// callback". This method signals the event *before* it returns (rule 2
134 /// above), so by the time you have a handle to build a wait object
135 /// around, that setup signal has already happened -- in the order the
136 /// rule forbids, and so with no documented guarantee that your callback
137 /// runs. Combined with the edge rule above, a ring whose queue never
138 /// returns to empty has no second wakeup coming, so a setup signal that
139 /// does not reach your callback strands the backlog permanently rather
140 /// than merely delaying it.
141 ///
142 /// The remedy needs nothing this method does not already give you --
143 /// after arming the wait, signal your own duplicate yourself: ///
144 /// ```no_run
145 /// # use windows_ioring_sys::IoRing;
146 /// # use std::os::windows::io::AsRawHandle;
147 /// # fn f(ring: &mut IoRing) -> std::io::Result<()> {
148 /// let event = ring.completion_event()?;
149 /// // ... build the wait object around `event`, then arm it ...
150 /// // Only now raise the wakeup, in the documented order. A wake with
151 /// // nothing to pop is normal (rule 2), so this is always safe.
152 /// unsafe { windows_sys::Win32::System::Threading::SetEvent(event.as_raw_handle()) };
153 /// # Ok(())
154 /// # }
155 /// ```
156 ///
157 #[cfg_attr(
158 feature = "threadpool",
159 doc = "[`EventDelivery`](crate::EventDelivery) already does this for you and"
160 )]
161 #[cfg_attr(
162 not(feature = "threadpool"),
163 doc = "`EventDelivery` (the default `threadpool` feature) already does this for you and"
164 )]
165 /// is the better answer if you do not need the handle itself.
166 ///
167 /// `examples/model_b_multiplexed.rs` is this whole shape worked end to
168 /// end -- a caller-owned ring waited on alongside a shutdown latch, with
169 /// the quiesce that shutdown-while-outstanding requires (M11.6).
170 ///
171 /// # Errors
172 ///
173 /// Returns [`io::ErrorKind::Unsupported`] if the running system does not
174 /// report `IORING_FEATURE_SET_COMPLETION_EVENT`; this crate refuses to
175 /// silently substitute a polling thread, since a caller who asked for an
176 /// event and got a spun-up thread has been told something false. Also
177 /// returns any error from `CreateEventW`,
178 /// `SetIoRingCompletionEvent`, `SetEvent`, or duplicating the handle.
179 pub fn completion_event(&mut self) -> io::Result<OwnedHandle> {
180 let event = self.attach_completion_event_unsignalled()?;
181 self.raise_setup_signal()?;
182 Ok(event)
183 }
184
185 /// Attach the ring's completion event *without* raising the setup signal,
186 /// reporting whether that signal is still owed.
187 ///
188 /// `SetThreadpoolWait` documents that "you must re-register the event with
189 /// the wait object before signaling it each time to trigger the wait
190 /// callback". A caller that is about to arm a threadpool wait on this
191 /// event therefore needs the attachment and the signal as two steps, so
192 /// that arming can be sequenced between them; handing back an
193 /// already-signalled event leaves that caller no way to obey the rule.
194 /// [`IoRing::completion_event`] is these two composed, for a caller who
195 /// does its own waiting and is not bound by that rule.
196 ///
197 /// The caller raises that signal **unconditionally**, whether or not this
198 /// call was the one that attached the event. The signal exists to make an
199 /// already-present backlog reachable, which is a property of the *waiter
200 /// about to arm* rather than of whoever attached the event first: a
201 /// caller that attaches, consumes the signal it raised, submits work, and
202 /// only then hands the ring to a waiter leaves a non-empty queue that the
203 /// edge rule (D-19) will never signal again. Owing the signal only to the
204 /// attaching call stranded exactly that backlog permanently, which is
205 /// `M26.12`.
206 pub(crate) fn attach_completion_event_unsignalled(&mut self) -> io::Result<OwnedHandle> {
207 // Already attached: hand back another duplicate rather than
208 // attaching a second event, which would silently detach the first
209 // (`SetIoRingCompletionEvent` replaces rather than adds). The
210 // capability was necessarily verified on the call that attached it.
211 if let Some(event) = &self.completion_event {
212 return event.try_clone();
213 }
214
215 if !capabilities()?.supports_completion_event {
216 return Err(io::Error::new(
217 io::ErrorKind::Unsupported,
218 "this system's IoRing does not report IORING_FEATURE_SET_COMPLETION_EVENT",
219 ));
220 }
221
222 // Auto-reset (manual_reset = FALSE) per D-21, initially unsignalled
223 // -- the deliberate setup signal is raised by `raise_setup_signal`,
224 // after the event is attached and owned, so it cannot be lost, and
225 // after any threadpool wait has been armed, so the arm-before-signal
226 // rule `SetThreadpoolWait` documents is obeyed.
227 // SAFETY: null attributes and name are documented defaults.
228 let raw = unsafe { CreateEventW(std::ptr::null(), 0, 0, std::ptr::null()) };
229 if raw.is_null() {
230 return Err(io::Error::last_os_error());
231 }
232 // SAFETY: `CreateEventW` just returned this handle and it is not
233 // owned anywhere else, so `OwnedHandle` is its sole owner from here.
234 let event = unsafe { OwnedHandle::from_raw_handle(raw) };
235
236 // SAFETY: `self.handle` is a live ring; `event` is a live event that
237 // this ring will own for the rest of its life once stored below.
238 let hr = unsafe {
239 crate::sys::set_completion_event(self.ring_id(), self.handle, event.as_raw_handle())
240 };
241 // On failure `event` drops here, closing a handle the ring never
242 // successfully referenced.
243 check(hr)?;
244
245 // Stored *before* the setup signal can be raised: from this point the
246 // ring owns the event, so no later failure can drop it and leave the
247 // ring signalling a closed (possibly recycled) handle.
248 self.completion_event.insert(event).try_clone()
249 }
250
251 /// Raise the one deliberate setup signal on the attached completion event.
252 ///
253 /// This is the single spurious wakeup the event's contract allows for: a
254 /// caller who submitted before attaching, **or who is about to start
255 /// waiting on an event someone else attached**, would otherwise never be
256 /// woken for that backlog, since the queue never returns to empty and so
257 /// never re-arms the edge (D-19). It is therefore raised for every such
258 /// caller rather than only for the one that performed the attachment --
259 /// see [`IoRing::attach_completion_event_unsignalled`] and `M26.12`.
260 ///
261 /// Separated from the attachment so that a caller arming a threadpool wait
262 /// can obey `SetThreadpoolWait`'s documented ordering -- register first,
263 /// signal second. Does nothing if no event is attached, which cannot
264 /// happen on the paths that call it.
265 pub(crate) fn raise_setup_signal(&self) -> io::Result<()> {
266 let Some(event) = &self.completion_event else {
267 return Ok(());
268 };
269 // SAFETY: `event` is a live event handle this ring owns.
270 if unsafe { SetEvent(event.as_raw_handle()) } == 0 {
271 return Err(io::Error::last_os_error());
272 }
273 // The setup signal is what a waiter attaching to a backlog depends on
274 // entirely, so M26.9's investigation needs to know it happened -- and
275 // that it happened on the ring's own handle rather than a duplicate
276 // handed to a caller, since only the former is what the kernel will go
277 // on signalling.
278 //
279 // Gated because `windows-threadpool-sys` is optional (D-22): this
280 // method is on the always-present path, unlike the delivery module,
281 // so an ungated reference breaks `--no-default-features`.
282 #[cfg(feature = "threadpool")]
283 windows_threadpool_sys::trace_record!(
284 "delivery",
285 "setup-signalled",
286 event.as_raw_handle() as usize,
287 self.accounting.outstanding()
288 );
289 Ok(())
290 }
291
292 /// Whether this ring supports a raw op code, including one this crate
293 /// does not yet name (D-7).
294 ///
295 /// Its reason to exist is an op outside [`Op`], which by definition this
296 /// ring's cached capability set was never probed for -- but passing a
297 /// named op's [`Op::code`] is equally in contract, and answers
298 /// identically to [`IoRing::supports`] (M10.1). The difference between
299 /// them is cost, not truth: `supports` is a bit test against the set
300 /// probed once at construction, this is an `IsIoRingOpSupported` call
301 /// every time.
302 ///
303 /// What a `true` here does *not* mean is that the op became pushable: an
304 /// op outside [`Op`] has no builder method whatever this answers, so
305 /// [`IoRing::push_raw`] remains the only route to one.
306 #[must_use]
307 pub fn supports_raw(&self, op_code: IORING_OP_CODE) -> bool {
308 // SAFETY: `self.handle` is a live ring.
309 unsafe { IsIoRingOpSupported(self.handle, op_code) != 0 }
310 }
311
312 /// How many file handles this ring has **reserved** for registration --
313 /// not how many are confirmed registered (M5.1, M10.3, D-31).
314 ///
315 /// The count advances the instant a `BuildIoRingRegisterFileHandles`
316 /// call queues, never when its completion is observed. Two consequences
317 /// a caller must not be surprised by:
318 ///
319 /// - it is already advanced before any completion has been popped, so it
320 /// cannot be used to decide whether a registration has taken effect --
321 /// claim the completion with
322 /// [`crate::PendingFileRegistration::claim_if`] for that;
323 /// - it stays advanced after a registration whose completion reported
324 /// *failure*, which is why such a registration cannot be retried on
325 /// this ring ([`crate::Batch::register_files`]).
326 ///
327 /// Because a ring accepts at most one registration that assigns an
328 /// index, this is `0` until that registration is queued and its count
329 /// thereafter; there is no second registration for it to serve as a base
330 /// index for.
331 #[must_use]
332 pub fn registered_file_count(&self) -> u32 {
333 self.accounting.registered_file_count()
334 }
335
336 /// As [`IoRing::registered_file_count`], for registered buffers (M5.2) --
337 /// a **reserved** count, not a confirmed one, with the same two
338 /// consequences (M10.3, D-31).
339 #[must_use]
340 pub fn registered_buffer_count(&self) -> u32 {
341 self.accounting.registered_buffer_count()
342 }
343
344 /// Advance the registered-file base index by `count`, the instant a
345 /// `BuildIoRingRegisterFileHandles` call successfully queues (not once
346 /// its completion is observed).
347 ///
348 /// D-14 recorded this as an explicitly unverified assumption, since this
349 /// crate cannot know whether the kernel claims these `count` indices
350 /// synchronously at build time or only once the registration op runs.
351 /// D-31 (M10.3) dissolved that: the collision it guarded against needs a
352 /// *second* registration, and `Batch::register_files`/`register_buffers`
353 /// forbid one, so no later base index is ever derived from this count and
354 /// the kernel's actual timing has no observable consequence. What the
355 /// eager advance does still determine is the *meaning* of the public
356 /// accessors, which is why they document a reserved rather than a
357 /// confirmed count.
358 ///
359 /// D-32 did not answer this question, despite being adjacent to it: it
360 /// established when the kernel reads the `IORING_BUFFER_INFO` *array*,
361 /// which is a different thing from when it claims the *indices*. The
362 /// latter remains unmeasured, and dissolved rather than resolved.
363 pub(crate) fn reserve_registered_files(&mut self, count: u32) {
364 self.accounting.reserve_registered_files(count);
365 }
366
367 /// As [`IoRing::reserve_registered_files`], for registered buffers.
368 pub(crate) fn reserve_registered_buffers(&mut self, count: u32) {
369 self.accounting.reserve_registered_buffers(count);
370 }
371
372 /// Take ownership of the `IORING_BUFFER_INFO` array a
373 /// `BuildIoRingRegisterBuffers` call is about to be handed, keeping it
374 /// alive until the ring can prove the op has run, and hand back a stable
375 /// pointer to it (D-32).
376 ///
377 /// The kernel reads this array when the registration op *runs*, not when
378 /// the `Build*` call returns, so the caller must not build the SQE from a
379 /// temporary: store the array here first and pass the returned pointer.
380 /// For an empty array the pointer is dangling, as `Vec::as_ptr` is for
381 /// any empty `Vec`, and is never dereferenced: the count passed beside it
382 /// is zero.
383 ///
384 /// If the `Build*` call then fails, call
385 /// [`IoRing::release_unqueued_buffer_infos`]: nothing was queued, and a
386 /// retry needs to hold its own array.
387 pub(crate) fn hold_registered_buffer_infos(
388 &mut self,
389 infos: Vec<IORING_BUFFER_INFO>,
390 ) -> *const IORING_BUFFER_INFO {
391 debug_assert!(
392 self.late_read.buffer_infos.is_empty(),
393 "a ring accepts at most one buffer registration, so this must only be set once"
394 );
395 self.late_read.buffer_infos = infos;
396 // `Vec::as_ptr` is stable for as long as the `Vec` is neither moved
397 // out of nor reallocated; it lives in `self` and is never mutated
398 // again, and moving the `IoRing` itself moves only the `Vec` header,
399 // not its heap allocation.
400 self.late_read.buffer_infos.as_ptr()
401 }
402
403 /// As [`IoRing::hold_registered_buffer_infos`], for the handle array a
404 /// `BuildIoRingRegisterFileHandles` call is about to be handed.
405 ///
406 /// The kernel reads this one late too (D-32). Copied in rather than
407 /// borrowed from the caller, so a caller's slice only has to live for
408 /// the `register_files` call.
409 pub(crate) fn hold_registered_file_handles(
410 &mut self,
411 handles: Vec<*mut c_void>,
412 ) -> *const *mut c_void {
413 debug_assert!(
414 self.late_read.file_handles.is_empty(),
415 "a ring accepts at most one file registration, so this must only be set once"
416 );
417 self.late_read.file_handles = handles;
418 // Stable for the reason given in `hold_registered_buffer_infos`.
419 self.late_read.file_handles.as_ptr()
420 }
421
422 /// Keep the files a safe file registration queued for the rest of the
423 /// ring's life (M30.2, [D-81](../../DESIGN-NOTES.md#d-81)). `Drop`
424 /// releases them after `CloseIoRing` on a quiesced rundown, and leaks
425 /// them otherwise.
426 ///
427 /// Called only once the registration has queued: a refused one hands its
428 /// files back instead, since the kernel never saw their handles.
429 pub(crate) fn hold_registered_shared_files(
430 &mut self,
431 files: Vec<win_shared_os_owned_handle::SharedHandle>,
432 ) {
433 debug_assert!(
434 self.registered_files.is_empty(),
435 "a ring accepts at most one file registration, so this must only be set once"
436 );
437 *self.registered_files = files;
438 }
439
440 /// Release the buffer array held for a `BuildIoRingRegisterBuffers` call
441 /// that then failed.
442 ///
443 /// A failed `Build*` queues no SQE, so the kernel will never read the
444 /// array, and the documented answer to a full queue is to submit and
445 /// retry -- which holds a fresh array and would otherwise trip the
446 /// set-once assertion above.
447 pub(crate) fn release_unqueued_buffer_infos(&mut self) {
448 self.late_read.buffer_infos = Vec::new();
449 }
450
451 /// As [`IoRing::release_unqueued_buffer_infos`], for the handle array.
452 pub(crate) fn release_unqueued_file_handles(&mut self) {
453 self.late_read.file_handles = Vec::new();
454 }
455
456 /// How many operations this ring believes are still outstanding: minted
457 /// (via `reserve_user_data`) but not yet observed to have completed (via
458 /// `record_completion`, which retires only an identity this ring minted).
459 #[must_use]
460 pub fn outstanding(&self) -> usize {
461 self.accounting.outstanding()
462 }
463
464 /// Mint a fresh `UserData` identity for a new operation, and account for
465 /// it as outstanding until `record_completion` or `cancel_reservation` is
466 /// called with it.
467 ///
468 /// The identity is the whole of what the inventory needs to validate
469 /// a completion (D-4): unlike `windows-overlapped-io-sys`'s
470 /// `OperationId`, there is no separate storage address to pair it with,
471 /// because `UserData` is a value this crate chooses rather than one Win32
472 /// hands back.
473 ///
474 /// # Errors
475 ///
476 /// Returns an error rather than reusing an identity if the `usize` space
477 /// is ever exhausted, mirroring `windows-threadpool-sys`'s own
478 /// "exhausting the generation sequence fails rather than wraps."
479 pub(crate) fn reserve_user_data(&mut self) -> io::Result<usize> {
480 self.accounting.reserve_user_data()
481 }
482
483 /// Record that a completion carrying `user_data` has been observed (a real
484 /// `IORING_CQE` was popped), whether or not the ring was still holding
485 /// something for it. Returns whether `user_data` was outstanding; a
486 /// foreign or duplicate completion retires nothing.
487 pub(crate) fn record_completion(&mut self, user_data: usize) -> bool {
488 self.accounting.record_completion(user_data)
489 }
490
491 /// Release a reservation for an operation that was never actually
492 /// queued -- a `Build*` call failed synchronously, after
493 /// [`IoRing::reserve_user_data`] had already minted its identity.
494 ///
495 /// Distinct from [`IoRing::record_completion`]: that marks a real
496 /// `IORING_CQE` observed; this marks one that will never arrive because
497 /// the op never entered the queue, so it must not count against
498 /// [`IoRing::run_down`] either.
499 pub(crate) fn cancel_reservation(&mut self, user_data: usize) {
500 self.accounting.cancel_reservation(user_data);
501 }
502
503 /// This ring's ledger, for the crate's own minting paths (M24.2).
504 ///
505 /// Handed out rather than proxied so that an [`OperationId`] can be minted
506 /// from the bookkeeping alone -- which is what lets `token.rs`'s tests run
507 /// without a kernel ring, since minting is all they ever needed one for.
508 pub(crate) fn accounting_mut(&mut self) -> &mut Accounting {
509 &mut self.accounting
510 }
511
512 /// This ring's native handle, for `batch.rs`'s `Build*`/`Submit` calls.
513 pub(crate) fn raw_handle(&self) -> *mut c_void {
514 self.handle
515 }
516
517 /// This ring's own identity, for stamping onto every [`crate::OperationId`]/
518 /// registration it mints and checking against on use (PR #20 review
519 /// response); see [`RingId`].
520 pub(crate) fn ring_id(&self) -> RingId {
521 self.accounting.ring_id()
522 }
523
524 /// Queue a raw, not-yet-wrapped SQE via a caller-supplied `Build*` call
525 /// (M3.5, D-7).
526 ///
527 /// `build` receives this ring's native handle and a freshly reserved
528 /// `UserData` value, and must call exactly one `BuildIoRing*` function
529 /// with them, returning its `HRESULT`. On success the `UserData` is
530 /// returned so the caller can match it against a later [`Completion`]
531 /// popped by [`IoRing::try_pop`]; on failure the reservation is
532 /// released, since the op was never actually queued.
533 ///
534 /// **Matching by hand is correct here, and only here.** This seam creates
535 /// no inventory entry -- it cannot, since it does not know what the
536 /// caller's `build` closure queued or what it needs held -- so a pop for
537 /// this operation reports `None` for what the ring was holding. That is
538 /// the legitimate half of [D-75](../../DESIGN-NOTES.md#d-75)'s two causes,
539 /// and choosing this seam is what makes it legitimate. Every other push
540 /// hands its payload to the ring and gets it back from the pop.
541 ///
542 /// # Errors
543 ///
544 /// Returns any error `build`'s `HRESULT` reports.
545 ///
546 /// # Safety
547 ///
548 /// `build` must queue an SQE for a *self-contained* op: everything the
549 /// kernel reads or writes for it must stay valid until the
550 /// corresponding completion is observed, and this crate cannot verify
551 /// what `build` does with the handle it is given. This is the same
552 /// framing as `windows-overlapped-io-sys`'s raw `ioctl` seam
553 /// (`device.rs`): the mechanics of building an SQE need nothing unsafe,
554 /// but this crate cannot audit an arbitrary `Build*` call, so the seam
555 /// itself is unsafe.
556 ///
557 /// `build` must also queue its SQE with **exactly the `user_data` it was
558 /// handed**, and queue only one. A completion carrying any other identity
559 /// is one this ring never minted, and the pop that receives it panics
560 /// ([D-79](../../DESIGN-NOTES.md#d-79)).
561 pub unsafe fn push_raw(
562 &mut self,
563 build: impl FnOnce(*mut c_void, usize) -> windows_sys::core::HRESULT,
564 ) -> io::Result<usize>
565 where
566 K: AllowsRaw,
567 {
568 let user_data = self.reserve_user_data()?;
569 let hr = build(self.handle, user_data);
570 if let Err(error) = check(hr) {
571 self.cancel_reservation(user_data);
572 return Err(error);
573 }
574 Ok(user_data)
575 }
576
577 /// Block until every outstanding operation has completed, so
578 /// `CloseIoRing` never runs while the kernel might still be touching a
579 /// buffer the ring holds for an in-flight operation.
580 ///
581 /// Waits in short, rechecked steps via `SubmitIoRing`'s own wait -- with
582 /// zero new entries queued, its only effect is to block for up to
583 /// `RUN_DOWN_POLL_MS` and reap whatever is already outstanding -- rather
584 /// than one unbounded call. This does not interpret what it pops: each
585 /// popped entry's payload is dropped, which is sound because its
586 /// completion is the proof the kernel has finished with it. Idempotent: calling it
587 /// again once `outstanding() == 0` is a no-op.
588 ///
589 /// # A poll that expires is not a failure
590 ///
591 /// Each poll blocks for `RUN_DOWN_POLL_MS` and then reports
592 /// `ERROR_TIMEOUT` if nothing finished in that window, which is the
593 /// ordinary outcome for any operation slower than 50ms. Treating that as
594 /// an error -- which this did until M21.6 -- made `run_down` return `Err`
595 /// with the operation still outstanding, so `Drop` asserted and then
596 /// called `CloseIoRing` anyway: exactly the "the kernel may still be
597 /// writing through a buffer the ring holds" hazard this function exists to
598 /// prevent.
599 ///
600 /// This loop therefore has no overall bound, and that is deliberate.
601 /// **Every SQE that successfully queues produces exactly one completion**
602 /// (M10.2), so it terminates. Blocking until that holds is the safe
603 /// failure mode; closing the ring early is not.
604 ///
605 /// # Choosing an unbounded wait is the caller's to make
606 ///
607 /// This spelling waits until rundown finishes, however long that takes,
608 /// and calling it is how a caller elects that. A caller who wants to
609 /// decide for themselves -- a deadline, a backoff, a number of attempts
610 /// before giving up -- calls [`IoRing::run_down_within`] instead and owns
611 /// the policy entirely. **This crate does not implement retry policy**
612 /// (M26.8): it supplies a bounded primitive and reports what happened.
613 ///
614 /// Note the difference from [`IoRing::pop_within`], which also waits in
615 /// segments: there the segments sit *inside a period the caller supplied*,
616 /// which is a bounded wait implemented properly rather than a policy. This
617 /// method had segments and no such period, which is what made it the one
618 /// waiting API in this crate shaped wrongly.
619 ///
620 /// # Errors
621 ///
622 /// Returns any error from `SubmitIoRing` other than an expired wait, or
623 /// from `PopIoRingCompletion`. **An error leaves the ring resumable**: see
624 /// [`IoRing::run_down_within`] for what is guaranteed about the operations
625 /// still queued.
626 ///
627 /// # Panics
628 ///
629 /// As [`IoRing::try_pop`], on a completion for an identity not in flight.
630 pub fn run_down(&mut self) -> io::Result<()> {
631 while !self.run_down_within(Duration::MAX)? {}
632 Ok(())
633 }
634
635 /// Run down for at most `bound`, reporting whether it finished.
636 ///
637 /// `Ok(true)` means nothing is outstanding and the ring is safe to drop.
638 /// `Ok(false)` means the bound elapsed with work still in flight -- call
639 /// again when your own policy says to. [`IoRing::outstanding`] says how
640 /// much is left.
641 ///
642 /// # Why this exists, and why it returns rather than retries
643 ///
644 /// Rundown can fail for a reason that a later attempt would survive, and
645 /// **deciding whether to make that attempt is not this crate's business**.
646 /// A caller running under a deadline, a supervisor with a backoff, and a
647 /// test that wants to fail fast all want different answers, and a policy
648 /// baked in here would be wrong for two of the three. So this waits for
649 /// exactly as long as it is told and then reports.
650 ///
651 /// # What an error guarantees, which is what makes retrying safe
652 ///
653 /// `SubmitIoRing` documents that *"If this function returns an error other
654 /// than IORING_E_WAIT_TIMEOUT, then all entries remain in the submission
655 /// queue."* So a failure here has **not** lost the operations and has not
656 /// rewound them ([D-5](../../DESIGN-NOTES.md#d-5)); they are still ring state,
657 /// a later submit is what runs them, and their buffers must stay alive
658 /// until they complete.
659 ///
660 /// The consequence worth stating plainly: after an `Err`, **do not drop
661 /// this ring**. Dropping it closes a ring the kernel may still write
662 /// through, which is the hazard rundown exists to prevent. Call again.
663 ///
664 /// # Errors
665 ///
666 /// Returns any error from `SubmitIoRing` other than an expired wait, or
667 /// from `PopIoRingCompletion`.
668 ///
669 /// # Panics
670 ///
671 /// As [`IoRing::try_pop`], on a completion for an identity not in flight.
672 pub fn run_down_within(&mut self, bound: Duration) -> io::Result<bool> {
673 let deadline = Instant::now().checked_add(bound);
674 loop {
675 if self.is_quiescent() {
676 return Ok(true);
677 }
678 // `is_quiescent` waits on IDENTITIES, not a count. With A and B
679 // outstanding, a foreign CQE plus A's real one retires only A, so
680 // this keeps waiting for B -- and B's will arrive, because every SQE
681 // that queues produces exactly one completion (M10.2). That is the
682 // invariant that already justifies this loop having no overall
683 // bound, and it holds for raw pushes, which stow nothing, exactly as
684 // for owned ones.
685 // `checked_add` rather than `+`, for the reason `pop_within_with`
686 // records: `Instant + Duration` panics on overflow, so
687 // `Duration::MAX` -- the honest spelling of "no deadline" -- would
688 // take down the process. A deadline the clock cannot represent is
689 // one that never arrives, which is what was asked for.
690 let remaining = match deadline {
691 Some(deadline) => deadline.saturating_duration_since(Instant::now()),
692 None => Duration::MAX,
693 };
694 if remaining.is_zero() {
695 return Ok(false);
696 }
697 // Reap before waiting, once the bound has said to wait at all: `SubmitIoRing` with a
698 // wait reports `IORING_E_COMPLETION_QUEUE_TOO_FULL` when the completion queue holds as
699 // many completions as it can, whatever it was asked to wait for, and an entry that
700 // failed to submit for the same want of room is still queued behind them. Waiting first
701 // fails the rundown of exactly the ring that most needs one, with the room it wants one
702 // pop away (M39.1; the same rule the completion event's edge states, D-19: drain, then
703 // wait).
704 self.drain_for_rundown()?;
705 if self.is_quiescent() {
706 return Ok(true);
707 }
708 // Segments sit inside the caller's period, never outside it: the
709 // poll is the shorter of the rundown step and what is left.
710 let poll_ms = u32::try_from(remaining.as_millis())
711 .unwrap_or(u32::MAX)
712 .clamp(1, RUN_DOWN_POLL_MS);
713 let mut submitted = 0_u32;
714 // SAFETY: `self.handle` is a live ring; valid out-pointer. Zero
715 // new SQEs are queued -- this call's only purpose is to wait for
716 // and reap already-outstanding completions.
717 let hr = unsafe {
718 crate::sys::submit(self.ring_id(), self.handle, 1, poll_ms, &raw mut submitted)
719 };
720 wait_outcome(hr)?;
721 self.drain_for_rundown()?;
722 }
723 }
724}