windows_ioring_sys/ring/inventory.rs
1// Copyright (c) 2026 Mike Grier
2// Split from ring.rs at 265a7a04.
3//! The ring-owned inventory: stowing a payload at push and retiring it at the
4//! pop that observed its completion.
5//!
6//! A child of [`super`] rather than a sibling, because Rust's privacy is
7//! asymmetric and these call the parent's private `pop_raw`. The quiescence
8//! predicate, the pop internals and `Drop` stay up there, where `Drop` can
9//! still reach them.
10
11use super::*;
12
13impl<T, X> IoRing<T, X, Open> {
14 /// Create a ring whose inventory holds `T`, negotiating the version as
15 /// [`IoRing::new`] does.
16 ///
17 /// The generic counterpart to [`IoRing::new`], which exists separately
18 /// only so that the common no-payload call keeps inferring its parameter.
19 ///
20 /// # One `T` per ring
21 ///
22 /// `T` is a single type, and a pop returns it without a cast because of
23 /// that. A caller whose operations carry buffers of *different* types --
24 /// say a [`win_numa_sys::NumaBuffer`] arena alongside ad-hoc `Vec<u8>`
25 /// records -- therefore cannot put them through one ring as they stand.
26 /// Two ways round it:
27 ///
28 /// - **A ring per buffer type.** Each keeps its own `T`, and the operations
29 /// are independent at the ring level.
30 /// - **An enum payload.** One type with a variant per buffer, carrying its
31 /// own `unsafe impl IoBuf`. The obligation that impl takes on is the same
32 /// one [`IoBuf`](crate::IoBuf) states, and it has to hold for every
33 /// variant: the address must not move while the kernel holds it, which
34 /// for an enum means the bytes must not live inline in the variant.
35 ///
36 /// Erasing `T` is not among them -- see
37 /// [D-4](../../DESIGN-NOTES.md#d-4).
38 ///
39 /// # Errors
40 ///
41 /// As [`IoRing::new`].
42 pub fn with_inventory(
43 submission_queue_size: u32,
44 completion_queue_size: u32,
45 ) -> io::Result<Self> {
46 let caps = capabilities()?;
47 let version = RingVersion::HIGHEST_KNOWN.min(caps.max_version);
48 Self::with_version_and_inventory(version, submission_queue_size, completion_queue_size)
49 }
50}
51
52impl<T, X, K> IoRing<T, X, K> {
53 /// Record what an operation is holding, under the identity it will
54 /// complete with.
55 ///
56 /// The other half of [`IoRing::reclaim`]. Between them they are the whole
57 /// mechanism `D-55` asked for: a consumer never holds a token, so it
58 /// cannot lose one, and the map cannot drift from the ring because the
59 /// ring *is* the map.
60 pub(crate) fn stow(&mut self, id: OperationId, entry: Entry<T, X>) {
61 debug_assert_eq!(
62 id.ring_id(),
63 self.accounting.ring_id(),
64 "an identity minted by another ring must never reach this inventory"
65 );
66 self.inventory.insert(id.user_data(), entry);
67 }
68
69 /// Take back what an operation was holding, if this ring was holding
70 /// anything for it.
71 ///
72 /// `None` means a push that created no entry at all -- the `_raw` flush
73 /// and cancel forms and `push_raw`, which return a bare `user_data`.
74 ///
75 /// A completion for an identity this ring never minted cannot reach here
76 /// as a quiet `None` any more: the identity ledger notices it first, and
77 /// the pop panics ([D-79](../../DESIGN-NOTES.md#d-79)) -- except during an
78 /// unwind, where it is traced and this returns `None` for it. `M28.5`
79 /// settled that the inventory is the right place to stop: an entry for
80 /// every raw push would need an `X` the caller never supplied.
81 pub(crate) fn reclaim(&mut self, user_data: usize) -> Option<Entry<T, X>> {
82 self.inventory.remove(&user_data)
83 }
84
85 /// How many operations this ring is currently holding something for.
86 ///
87 /// Distinct from [`IoRing::outstanding`], which counts every operation the
88 /// *kernel* still owes a completion for. The two are **not** expected to
89 /// be equal: every operation held here is also outstanding, but a `_raw`
90 /// flush or cancel and [`IoRing::push_raw`] are outstanding while holding
91 /// nothing, by design. So `held() <= outstanding()` always, and the
92 /// difference is the number of raw operations in flight -- not a sign of
93 /// corruption ([D-78](../../DESIGN-NOTES.md#d-78)). On an [`Owned`] ring the
94 /// two are equal.
95 #[must_use]
96 pub fn held(&self) -> usize {
97 self.inventory.len()
98 }
99}
100
101impl<T, X, K: RingKind> IoRing<T, X, K> {
102 /// Pop a completion and take back whatever this ring was holding for it.
103 ///
104 /// The replacement for popping a [`Completion`] and matching it against a
105 /// held token (`D-71`). The payload is produced **by this call and by no
106 /// other**, which is what makes a use-after-free unrepresentable rather
107 /// than merely guarded: there is no way to name an operation and be handed
108 /// the memory it may still be using. [`crate::OperationId`] deliberately
109 /// cannot do it.
110 ///
111 /// # What each `None` means
112 ///
113 /// There are two, and they answer different questions.
114 ///
115 /// A `Some` whose **payload** is `None` is an operation that never had a
116 /// buffer -- a flush or a cancellation pushed through an `_owned` form.
117 /// Its sidecar still arrives, which is the point: a flush can say which
118 /// group of writes it belonged to.
119 ///
120 /// The **outer** `None` means this ring is holding nothing for that
121 /// identity because the push that produced it created no entry: a `_raw`
122 /// flush or cancel, or [`IoRing::push_raw`]. Those return a bare
123 /// `user_data` and deliberately create no entry -- choosing one *is*
124 /// choosing not to have the ring hold anything. Nothing is wrong. `M28.5`
125 /// decided not to give every raw push an entry: that would need an `X` the
126 /// caller never supplied, and the `_owned` forms already exist for a
127 /// caller who wants one.
128 ///
129 /// # On an [`Owned`] ring
130 ///
131 /// Neither case of nothing is possible: no call that queues an operation the ring holds
132 /// nothing for exists for it, so the return is the flat `(completion, payload, sidecar)`,
133 /// with the payload `None` for an operation that never had a buffer
134 /// ([D-91](../../DESIGN-NOTES.md#d-91)).
135 ///
136 /// # Errors
137 ///
138 /// Any error from `PopIoRingCompletion`.
139 ///
140 /// # Panics
141 ///
142 /// If the completion carries an identity that is not in flight on this
143 /// ring -- never minted here, already completed, or released after its
144 /// build failed. That is a defect, not an outcome
145 /// ([D-79](../../DESIGN-NOTES.md#d-79)); during an unwind it is traced
146 /// instead, and the completion is returned with an outer `None`.
147 pub fn try_pop(&mut self) -> io::Result<Option<K::Popped<T, X>>> {
148 let Some(completion) = self.pop_raw()? else {
149 return Ok(None);
150 };
151 // The outer `Option` is whether this ring stowed anything for that
152 // identity; the inner one is whether what it stowed included a buffer.
153 // Collapsing the two -- which an earlier version did -- loses the
154 // sidecar of every bufferless operation, so a flush could never say
155 // which group of writes it belonged to.
156 let held = self
157 .reclaim(completion.user_data())
158 .map(|entry| (entry.payload, entry.extra));
159 K::shape(completion, held).map(Some)
160 }
161}
162
163impl<T, X> IoRing<T, X, Open> {
164 /// Seal this ring against operations it holds nothing for, so it can be an [`Owned`]
165 /// ring ([D-91](../../DESIGN-NOTES.md#d-91)).
166 ///
167 /// An [`Open`] ring can have in flight an operation the ring holds nothing for: a
168 /// registration, a `_raw` flush or cancel, or [`IoRing::push_raw`]. Those are queued
169 /// by calls an [`Owned`] ring does not have, and the ring this returns can never have one
170 /// again; the conversion consumes the ring and there is no way back. So it is refused while
171 /// any is in flight -- [`IoRing::outstanding`] exceeds [`IoRing::held`] by exactly that
172 /// many -- and the ring is handed back, to be popped until it is not.
173 ///
174 /// Nothing is lost: the ring's identity, its registrations (a [`RegisteredFiles`](crate::RegisteredFiles) and a
175 /// `RegisteredBuffers` it made still name it), its completion event and its queue are the
176 /// same.
177 ///
178 /// # Errors
179 ///
180 /// The ring, unchanged, if an operation it holds nothing for is in flight.
181 // The `Err` is the ring itself, handed back so the caller can pop what is in flight and ask
182 // again; boxing it would make every caller of a once-per-ring call unbox it.
183 #[allow(clippy::result_large_err)]
184 pub fn into_owned(self) -> Result<IoRing<T, X, Owned>, Self> {
185 if self.outstanding() != self.held() {
186 return Err(self);
187 }
188 let this = ManuallyDrop::new(self);
189 // SAFETY: `IoRing` is `repr(C)` and `K` appears in it only as `PhantomData<fn() -> K>`,
190 // so `IoRing<T, X, Open>` and `IoRing<T, X, Owned>` have one layout, field for field. The
191 // read moves the whole ring out of `this`, which is never dropped, so there is one owner
192 // before and after.
193 Ok(unsafe {
194 std::ptr::read(std::ptr::from_ref::<Self>(&this).cast::<IoRing<T, X, Owned>>())
195 })
196 }
197}