windows_overlapped_io_sys/operation.rs
1// Copyright (c) 2026 Mike Grier
2//! Pinned per-operation storage: an `OVERLAPPED` coupled to its payload.
3//!
4//! Each in-flight overlapped operation owns stable storage holding its
5//! `OVERLAPPED`, the caller's opaque payload, and an explicit lifecycle state.
6//! The address of the `OVERLAPPED` is the completion identity a backend uses to
7//! match a dequeued packet back to its operation, so the storage must not move
8//! or be reused while an operation is outstanding. This module models the
9//! storage and identity only; submission, completion, and reclamation belong to
10//! the individual completion backends.
11
12use std::cell::UnsafeCell;
13
14use windows_sys::Win32::System::IO::{OVERLAPPED, OVERLAPPED_0, OVERLAPPED_0_0};
15
16/// The lifecycle state of an overlapped operation's storage.
17#[derive(Debug, Clone, Copy, PartialEq, Eq)]
18pub enum OperationState {
19 /// Constructed but not yet submitted; the payload may still be prepared.
20 Idle,
21 /// Handed to a native submission call whose outcome is not yet known.
22 Submitted,
23 /// Accepted by the kernel and awaiting an asynchronous completion.
24 Pending,
25 /// A completion has been observed; the payload and result may be read.
26 Completed,
27 /// Cancellation was requested before completion was observed.
28 Cancelled,
29}
30
31/// Stable storage for one overlapped operation.
32///
33/// `P` is the caller's payload, for example a buffer or a descriptor array. The
34/// crate never interprets it. The `OVERLAPPED` lives in an [`UnsafeCell`] because
35/// the kernel writes to it through [`Operation::overlapped_ptr`] while the owner
36/// holds only a shared reference.
37// `repr(C)` with `overlapped` first keeps the operation pointer identical to its
38// `OVERLAPPED` pointer, so a completion can recover the operation from it. The
39// `reclaim` thunk sits before `payload`, so its offset is the same for every `P`
40// and can be read from the `OVERLAPPED` pointer alone during rundown, and
41// `sync_bytes` sits before `payload` for the same reason.
42#[derive(Debug)]
43#[repr(C)]
44pub struct Operation<P> {
45 overlapped: UnsafeCell<OVERLAPPED>,
46 // Read only through `reclaim_from_overlapped`, via its fixed offset.
47 #[allow(dead_code)]
48 reclaim: Option<unsafe fn(*mut OVERLAPPED)>,
49 state: OperationState,
50 // The `lpNumberOfBytesTransferred` / `lpBytesReturned` out-parameter an
51 // adapter hands to its native call. It lives here, in the pinned operation,
52 // rather than on the submitting stack frame because the kernel may write it
53 // *after* that call returns: `DeviceIoControl` documents the count as
54 // "meaningless until the overlapped operation has completed" when
55 // `lpOverlapped` is non-null, so a stack local would be a dangling write
56 // for any operation that goes asynchronous. Only read on the synchronous
57 // path, where the value is already there before the call returns.
58 sync_bytes: UnsafeCell<u32>,
59 payload: P,
60}
61
62/// Offset of the `reclaim` field, identical for every `P`.
63const RECLAIM_OFFSET: usize = core::mem::offset_of!(Operation<()>, reclaim);
64
65/// Offset of the `sync_bytes` cell, identical for every `P` because it sits
66/// before `payload`.
67#[cfg(any(feature = "fs", feature = "socket", feature = "device"))]
68const SYNC_BYTES_OFFSET: usize = core::mem::offset_of!(Operation<()>, sync_bytes);
69
70/// Drop a leaked `Box<Operation<P>>` given its `OVERLAPPED` pointer.
71///
72/// # Safety
73///
74/// `overlapped` must be the base of a live `Box<Operation<P>>` reclaimed exactly
75/// once.
76pub(crate) unsafe fn reclaim_operation<P>(overlapped: *mut OVERLAPPED) {
77 drop(unsafe { Box::from_raw(overlapped.cast::<Operation<P>>()) });
78}
79
80/// Run the reclaim thunk armed on the operation identified by `overlapped`.
81///
82/// # Safety
83///
84/// `overlapped` must be the identity pointer of a live armed operation reclaimed
85/// exactly once.
86pub(crate) unsafe fn reclaim_from_overlapped(overlapped: *mut OVERLAPPED) {
87 let slot = unsafe {
88 overlapped
89 .cast::<u8>()
90 .add(RECLAIM_OFFSET)
91 .cast::<Option<unsafe fn(*mut OVERLAPPED)>>()
92 };
93 if let Some(reclaim) = unsafe { *slot } {
94 unsafe { reclaim(overlapped) };
95 }
96}
97
98/// Recover a pointer to the payload of an operation from its `OVERLAPPED`
99/// identity.
100///
101/// The payload offset depends on `P`, so the caller must supply the exact `P`
102/// the operation was created with. It is used by a family adapter to reach the
103/// buffer it owns inside the pinned operation while issuing the native call.
104///
105/// # Safety
106///
107/// `overlapped` must be the identity pointer of a live `Operation<P>` of this
108/// exact type, and the returned pointer must be used only while that operation's
109/// storage stays put and nothing else accesses the payload concurrently.
110#[cfg(any(feature = "fs", feature = "socket"))]
111pub(crate) unsafe fn payload_ptr_from_overlapped<P>(overlapped: *mut OVERLAPPED) -> *mut P {
112 let offset = core::mem::offset_of!(Operation<P>, payload);
113 unsafe { overlapped.cast::<u8>().add(offset).cast::<P>() }
114}
115
116/// Recover a pointer to the synchronous byte-count cell of an operation from its
117/// `OVERLAPPED` identity.
118///
119/// This is the `lpNumberOfBytesTransferred` / `lpBytesReturned` out-parameter an
120/// adapter passes to its native call. Unlike the payload's, this offset does not
121/// depend on `P` -- the cell sits before `payload` precisely so it does not --
122/// so an adapter reaches it without naming the payload type.
123///
124/// Read the value only when the native call reported immediate success, which is
125/// the one moment it is guaranteed to be populated and no longer subject to a
126/// later kernel write.
127///
128/// # Safety
129///
130/// `overlapped` must be the identity pointer of a live `Operation<P>`, and the
131/// returned pointer must be used only while that operation's storage stays put.
132#[cfg(any(feature = "fs", feature = "socket", feature = "device"))]
133pub(crate) unsafe fn sync_bytes_ptr_from_overlapped(overlapped: *mut OVERLAPPED) -> *mut u32 {
134 unsafe { overlapped.cast::<u8>().add(SYNC_BYTES_OFFSET).cast::<u32>() }
135}
136
137/// Reclaim and drop an operation from its `OVERLAPPED` identity without knowing
138/// its payload type, using the thunk armed by [`Operation::into_overlapped`].
139///
140/// This lets a backend free operations of mixed payload types during rundown.
141///
142/// # Safety
143///
144/// `overlapped` must have been returned by [`Operation::into_overlapped`] and
145/// must be reclaimed exactly once.
146pub unsafe fn reclaim_overlapped(overlapped: *mut OVERLAPPED) {
147 unsafe { reclaim_from_overlapped(overlapped) };
148}
149
150impl<P> Operation<P> {
151 /// Create idle storage with a zeroed `OVERLAPPED` and the given payload.
152 #[must_use]
153 pub fn new(payload: P) -> Self {
154 let overlapped = OVERLAPPED {
155 Internal: 0,
156 InternalHigh: 0,
157 Anonymous: OVERLAPPED_0 {
158 Anonymous: OVERLAPPED_0_0 {
159 Offset: 0,
160 OffsetHigh: 0,
161 },
162 },
163 hEvent: std::ptr::null_mut(),
164 };
165 Self {
166 overlapped: UnsafeCell::new(overlapped),
167 reclaim: None,
168 state: OperationState::Idle,
169 sync_bytes: UnsafeCell::new(0),
170 payload,
171 }
172 }
173
174 /// Return the current lifecycle state.
175 #[must_use]
176 pub fn state(&self) -> OperationState {
177 self.state
178 }
179
180 /// Set the lifecycle state marker.
181 pub fn set_state(&mut self, state: OperationState) {
182 self.state = state;
183 }
184
185 /// Arm the reclaim thunk so rundown can free this operation generically.
186 pub(crate) fn arm(&mut self) {
187 self.reclaim = Some(reclaim_operation::<P>);
188 }
189
190 /// Consume the operation for submission, transferring ownership out and
191 /// returning its stable `OVERLAPPED` identity.
192 ///
193 /// The returned pointer identifies the operation and must be handed to
194 /// exactly one native overlapped call. Recover the operation afterward with
195 /// [`Operation::from_overlapped`] when the payload type is known (as in a
196 /// completion), or with [`reclaim_overlapped`] when it is not (as during
197 /// rundown). This is the submission seam shared by the completion-port and
198 /// thread-pool backends.
199 ///
200 /// `P: 'static` because this is the moment the storage is leaked. The box is
201 /// freed later through a type-erased thunk that carries no lifetime, by
202 /// whichever path reclaims it -- a completion, or rundown -- and nothing at
203 /// that point can prove a borrow inside `P` is still live. A payload holding
204 /// a `&'a T` would compile without this bound and could then have its `Drop`
205 /// run after `'a` ended. The bound sits here, rather than on `Operation`
206 /// itself, because it is the leak that requires it: the blocking backend
207 /// drives an operation through `&mut` without ever leaking it, and correctly
208 /// needs neither this nor `Send`.
209 ///
210 /// # Examples
211 ///
212 /// An owned payload submits fine:
213 ///
214 /// ```
215 /// use windows_overlapped_io_sys::Operation;
216 ///
217 /// let operation = Operation::new(vec![0_u8; 32]);
218 /// let overlapped = operation.into_overlapped();
219 /// // SAFETY: nothing was submitted against it, so this reclaims the
220 /// // storage exactly once and no completion can be outstanding.
221 /// unsafe { windows_overlapped_io_sys::reclaim_overlapped(overlapped) };
222 /// ```
223 ///
224 /// A payload borrowing from the caller's frame is rejected, rather than
225 /// having its `Drop` run later against an expired borrow:
226 ///
227 /// ```compile_fail
228 /// use windows_overlapped_io_sys::Operation;
229 ///
230 /// fn leak_a_borrow(bytes: &[u8]) -> *mut std::ffi::c_void {
231 /// let operation = Operation::new(bytes);
232 /// operation.into_overlapped().cast()
233 /// }
234 /// ```
235 #[must_use]
236 pub fn into_overlapped(mut self) -> *mut OVERLAPPED
237 where
238 P: 'static,
239 {
240 self.arm();
241 self.state = OperationState::Pending;
242 let boxed = Box::new(self);
243 let overlapped = boxed.overlapped_ptr();
244 // Ownership transfers to the caller until the operation is reclaimed.
245 let _ = Box::into_raw(boxed);
246 overlapped
247 }
248
249 /// Recover an operation previously submitted with [`Operation::into_overlapped`].
250 ///
251 /// # Safety
252 ///
253 /// `overlapped` must have been returned by [`Operation::into_overlapped`] on
254 /// an `Operation<P>` of this exact type, and must be reclaimed exactly once.
255 #[must_use]
256 pub unsafe fn from_overlapped(overlapped: *mut OVERLAPPED) -> Self {
257 unsafe { *Box::from_raw(overlapped.cast::<Operation<P>>()) }
258 }
259
260 /// Borrow the payload.
261 #[must_use]
262 pub fn payload(&self) -> &P {
263 &self.payload
264 }
265
266 /// Mutably borrow the payload.
267 ///
268 /// Exclusive access proves no operation is in flight, so preparing or
269 /// inspecting the payload here cannot race a kernel write.
270 #[must_use]
271 pub fn payload_mut(&mut self) -> &mut P {
272 &mut self.payload
273 }
274
275 /// Consume the storage and recover the payload.
276 #[must_use]
277 pub fn into_payload(self) -> P {
278 self.payload
279 }
280
281 /// Set the seek position for endpoints that use `OVERLAPPED` offsets.
282 ///
283 /// Non-seekable endpoints such as pipes and sockets must leave the offset at
284 /// its default of zero.
285 pub fn set_offset(&mut self, offset: u64) {
286 let overlapped = self.overlapped.get_mut();
287 // The offset fields share a union with the unused `Pointer` member.
288 overlapped.Anonymous.Anonymous.Offset = offset as u32;
289 overlapped.Anonymous.Anonymous.OffsetHigh = (offset >> 32) as u32;
290 }
291
292 /// Return the stable pointer that identifies this operation to a backend.
293 ///
294 /// The pointer is valid only while the storage stays put; a backend must pin
295 /// the operation before submitting and must not free the storage until the
296 /// matching completion has been observed.
297 #[must_use]
298 pub fn overlapped_ptr(&self) -> *mut OVERLAPPED {
299 self.overlapped.get()
300 }
301}
302
303#[cfg(test)]
304mod tests;