windows_ioring_sys/token.rs
1// Copyright (c) 2026 Mike Grier
2//! `Token<T>`: an owned value bound to one in-flight operation (M2.2, M2.3).
3
4use std::mem::ManuallyDrop;
5
6use crate::ring::{Completion, RingId};
7
8/// An owned value, plus the identity of the operation the kernel may still
9/// be reading or writing through it.
10///
11/// Usually `T` is an [`crate::IoBuf`] the operation reads or writes, but
12/// nothing about this type depends on that: M5's buffer registration reuses
13/// the same forget-unless-claimed mechanism to track a registration's own
14/// in-flight uses, where `T` carries no bytes at all (see
15/// `RegisteredUse` in `batch.rs`).
16///
17/// `Token` intentionally holds no reference back to the [`crate::IoRing`]
18/// that minted its identity: outstanding-operation accounting belongs to the
19/// ring (D-4 in `DESIGN-NOTES.md`), driven by whoever actually pops an
20/// `IORING_CQE`, not by a `Token` merely being dropped. A dropped, unclaimed
21/// `Token` means the *caller* has given up on this handle; it says nothing
22/// about whether the kernel has finished the operation, which is exactly why
23/// dropping it forgets the value instead of freeing it -- see
24/// [`Token`]'s `Drop` impl.
25pub struct Token<T: Send + 'static> {
26 id: usize,
27 ring_id: RingId,
28 value: ManuallyDrop<T>,
29}
30
31impl<T: Send + 'static> Token<T> {
32 /// Wrap `value` under a fresh identity reserved from `ring`.
33 ///
34 /// # Errors
35 ///
36 /// Returns any error from [`crate::IoRing::reserve_user_data`] (in
37 /// practice, only if the identity space is exhausted).
38 pub(crate) fn new(ring: &mut crate::IoRing, value: T) -> std::io::Result<Self> {
39 let id = ring.reserve_user_data()?;
40 Ok(Self {
41 id,
42 ring_id: ring.ring_id(),
43 value: ManuallyDrop::new(value),
44 })
45 }
46
47 /// This token's `UserData` identity.
48 #[must_use]
49 pub fn id(&self) -> usize {
50 self.id
51 }
52
53 /// Consume this token and recover its value unconditionally.
54 ///
55 /// Only for a caller who already knows the operation completed --
56 /// [`Token::claim_if`] on a match, or the crate's own abort path for a
57 /// push that never queued at all (`Batch::finish_push`). Not `pub`:
58 /// calling this without that knowledge is exactly the use-after-free
59 /// this type exists to prevent.
60 pub(crate) fn claim(mut self) -> T {
61 // SAFETY: `self` is not used again after this -- it is dropped
62 // normally by the caller's scope immediately after, and `Token`'s own
63 // `Drop` never reads `value` (see that impl), so taking it here
64 // leaves nothing for `Drop` to double-free.
65 unsafe { ManuallyDrop::take(&mut self.value) }
66 }
67
68 /// Claim this token's value if `completion` names it, or hand it back
69 /// unchanged otherwise (D-4).
70 ///
71 /// Takes a popped [`Completion`], not a bare `usize`: `Completion` has no
72 /// public constructor, so the only way to produce one is
73 /// [`crate::IoRing::try_pop`] actually observing a real `IORING_CQE`.
74 /// Accepting a caller-supplied integer here instead -- for example
75 /// `token.claim_if(token.id())` -- would let safe code reclaim (and then
76 /// drop, freeing) a buffer the kernel might still be reading or writing,
77 /// which is exactly the use-after-free this type exists to prevent.
78 /// Unlike `windows-overlapped-io-sys`'s `OperationId`, there is no
79 /// storage address to also check on its own -- `UserData` is a value
80 /// this crate chose -- but two different rings each hand out `UserData`
81 /// from their own counter starting at zero, so the same value can
82 /// legitimately occur on both. Requiring `completion`'s ring identity to
83 /// match this token's own (PR #20 review response) is what rules that
84 /// out: a token can only ever be claimed by a completion popped from the
85 /// exact ring that minted it.
86 pub fn claim_if(self, completion: &Completion) -> Result<T, Self> {
87 if self.id == completion.user_data() && self.ring_id == completion.ring_id() {
88 Ok(self.claim())
89 } else {
90 Err(self)
91 }
92 }
93}
94
95impl<T: Send + 'static> Drop for Token<T> {
96 fn drop(&mut self) {
97 // Deliberately empty. `value` is a `ManuallyDrop<T>`, which already
98 // never runs `T`'s destructor on its own; declaring this impl (rather
99 // than omitting it and relying on that) makes the leak-not-free
100 // choice a visible, intentional part of this type rather than an
101 // accident of its field types (D-4).
102 }
103}
104
105impl<T: Send + 'static> std::fmt::Debug for Token<T> {
106 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
107 // Not derived: deriving would require `T: Debug`, and a caller's
108 // value type need not implement it. The id is the only part of a
109 // token useful to print anyway.
110 f.debug_struct("Token")
111 .field("id", &self.id)
112 .finish_non_exhaustive()
113 }
114}
115
116#[cfg(test)]
117mod tests;