Skip to main content

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}