windows_overlapped_io_sys/iocp.rs
1// Copyright (c) 2026 Mike Grier
2//! Raw I/O completion port backend: port ownership, association, and dequeue.
3//!
4//! A [`CompletionPort`] owns a completion-port handle and can service many
5//! endpoints, each associated with a caller-chosen completion key. Association
6//! is the consuming transition that binds an [`UnassociatedEndpoint`] to this
7//! backend. The port does not create worker threads; the owner decides where
8//! [`CompletionPort::get`] runs. Submission of real overlapped operations, and
9//! the reclamation that follows their completion, are built on top of this
10//! module.
11
12use std::cell::Cell;
13use std::collections::HashMap;
14use std::fmt;
15use std::io;
16use std::os::windows::io::{AsHandle, AsRawHandle, BorrowedHandle, FromRawHandle, OwnedHandle};
17use std::panic::Location;
18use std::sync::atomic::{AtomicUsize, Ordering};
19use std::sync::{Arc, Mutex, MutexGuard};
20
21use windows_sys::Win32::Foundation::{HANDLE, INVALID_HANDLE_VALUE, WAIT_TIMEOUT};
22use windows_sys::Win32::System::IO::{
23 CancelIoEx, CreateIoCompletionPort, GetQueuedCompletionStatus, OVERLAPPED,
24 PostQueuedCompletionStatus,
25};
26
27use crate::identity::{OperationId, OperationRegistry};
28use crate::{Operation, OperationState, UnassociatedEndpoint};
29
30/// Bound on each `GetQueuedCompletionStatus` wait inside [`CompletionPort::run_down`].
31///
32/// `run_down` cannot wait without timeout: the port is shareable and
33/// [`CompletionPort::get`] takes `&self`, so a concurrent consumer can dequeue
34/// the last packet -- and clear the registry entry -- after `run_down` has
35/// observed a nonzero outstanding count but before its own wait begins. An
36/// unbounded wait would then block forever on a packet that is no longer coming.
37/// A bounded wait wakes periodically so the loop can recheck the live count and
38/// return once it reaches zero. The interval only bounds that recheck latency; a
39/// packet genuinely destined for `run_down` is still returned the instant it
40/// arrives, so this is not a busy-poll of a live operation.
41const RUN_DOWN_POLL_MS: u32 = 5;
42
43/// Optional per-operation source information, recorded only while source
44/// tracking is enabled.
45struct Track {
46 location: &'static Location<'static>,
47 #[cfg(feature = "operation-backtrace")]
48 backtrace: std::backtrace::Backtrace,
49}
50
51/// State shared between a port, its completions, and the drain path.
52///
53/// `live` is the registry of operations submitted through this port whose
54/// completion packet has not yet been dequeued. It governs rundown (its length
55/// is the outstanding count) and answers whether an [`OperationId`] still names
56/// an operation a packet is still coming for, which is what keeps a retained
57/// identity from cancelling an operation that merely recycled its address.
58/// Registration ends at dequeue, not at reclamation: a held [`Completion`] owns
59/// its operation's storage but is no longer awaiting anything, and counting it
60/// would make rundown wait for a packet it had already received.
61/// `tracked` is consulted only when source tracking is enabled.
62struct PortState {
63 live: OperationRegistry,
64 tracked: Mutex<HashMap<usize, Track>>,
65 /// Each associated endpoint's own outstanding-operation count, keyed by
66 /// its completion key (M1, PR #20 review response via `windows-ioring-sys`'s
67 /// M8). `live` above answers "is a packet still coming for this address",
68 /// port-wide; this answers the same question scoped to one endpoint, which
69 /// is what `AssociatedEndpoint`'s own `Drop` needs -- `CompletionPort::run_down`
70 /// is the wrong scope for it, since it blocks on every endpoint's operations,
71 /// not just one's.
72 endpoint_outstanding: Mutex<HashMap<usize, Arc<AtomicUsize>>>,
73}
74
75impl PortState {
76 fn new() -> Self {
77 Self {
78 live: OperationRegistry::new(),
79 tracked: Mutex::new(HashMap::new()),
80 endpoint_outstanding: Mutex::new(HashMap::new()),
81 }
82 }
83}
84
85/// Lock a mutex, recovering the guard even if a previous holder panicked.
86fn lock<T>(mutex: &Mutex<T>) -> MutexGuard<'_, T> {
87 mutex.lock().unwrap_or_else(|poison| poison.into_inner())
88}
89
90/// An owned I/O completion port.
91pub struct CompletionPort {
92 handle: OwnedHandle,
93 state: Arc<PortState>,
94}
95
96impl CompletionPort {
97 /// Create a new completion port.
98 ///
99 /// `concurrency` is the maximum number of threads the system lets run
100 /// completions for this port concurrently; zero means one per processor.
101 pub fn new(concurrency: u32) -> io::Result<Self> {
102 // SAFETY: creating a fresh port with no associated file handle.
103 let handle = unsafe {
104 CreateIoCompletionPort(INVALID_HANDLE_VALUE, std::ptr::null_mut(), 0, concurrency)
105 };
106 if handle.is_null() {
107 return Err(io::Error::last_os_error());
108 }
109 // SAFETY: the call returned a fresh, exclusively owned port handle.
110 let handle = unsafe { OwnedHandle::from_raw_handle(handle) };
111 Ok(Self {
112 handle,
113 state: Arc::new(PortState::new()),
114 })
115 }
116
117 /// Associate an overlapped endpoint with this port under `key`.
118 ///
119 /// Completions for operations issued on the endpoint are delivered to this
120 /// port and tagged with `key`. The association is permanent for the life of
121 /// the handle, so the returned endpoint borrows the port.
122 ///
123 /// # Errors
124 ///
125 /// Returns [`io::ErrorKind::InvalidInput`] if `key` is already associated
126 /// with a live endpoint on this port (PR #20 review response): a
127 /// completion key is a caller-defined tag, not a unique endpoint identity,
128 /// and `deregister_dequeued` finds an endpoint's outstanding-operation
129 /// counter by looking it up under `key` alone. Associating a second
130 /// endpoint under a key already in use would silently replace the first
131 /// endpoint's counter in `endpoint_outstanding`; completions for the
132 /// first endpoint would then decrement the second's counter (which can
133 /// underflow) while the first's own counter never reaches zero, blocking
134 /// its `Drop` forever. Rejecting the duplicate before native association
135 /// keeps every key's counter unambiguous instead. Also returns the error
136 /// from `CreateIoCompletionPort`.
137 pub fn associate(
138 &self,
139 endpoint: UnassociatedEndpoint,
140 key: usize,
141 ) -> io::Result<AssociatedEndpoint<'_>> {
142 // Read before the handle is taken out, so the mode travels with the
143 // endpoint into association rather than being lost at the boundary.
144 let modes = endpoint.notification_modes();
145 let handle = endpoint.into_handle();
146 let outstanding = Arc::new(AtomicUsize::new(0));
147 {
148 // Checked and reserved under one lock acquisition, so no other
149 // `associate` call can race into the same key between the check
150 // and the insert.
151 let mut endpoint_outstanding = lock(&self.state.endpoint_outstanding);
152 if endpoint_outstanding.contains_key(&key) {
153 return Err(io::Error::new(
154 io::ErrorKind::InvalidInput,
155 format!(
156 "windows-overlapped-io-sys: completion key {key} is already \
157 associated with a live endpoint on this port; each endpoint must \
158 use a distinct key"
159 ),
160 ));
161 }
162 endpoint_outstanding.insert(key, Arc::clone(&outstanding));
163 }
164 // SAFETY: associating a valid handle with a valid port; the concurrency
165 // argument is ignored when an existing port is supplied.
166 let result = unsafe { CreateIoCompletionPort(handle.as_raw_handle(), self.raw(), key, 0) };
167 if result.is_null() {
168 // Roll back the reservation above; nothing else can have used it,
169 // since the endpoint was never associated to receive completions.
170 lock(&self.state.endpoint_outstanding).remove(&key);
171 return Err(io::Error::last_os_error());
172 }
173 Ok(AssociatedEndpoint {
174 port: self,
175 handle,
176 key,
177 modes,
178 outstanding,
179 })
180 }
181
182 /// Post a user-defined wakeup packet to this port.
183 ///
184 /// The packet carries `key` and `bytes_transferred` with a null `OVERLAPPED`,
185 /// which keeps it distinguishable from operation completions; identify it by
186 /// its `key`.
187 pub fn post(&self, key: usize, bytes_transferred: u32) -> io::Result<()> {
188 // SAFETY: the port handle is valid; a null overlapped marks a user packet.
189 let ok = unsafe {
190 PostQueuedCompletionStatus(self.raw(), bytes_transferred, key, std::ptr::null())
191 };
192 if ok == 0 {
193 return Err(io::Error::last_os_error());
194 }
195 Ok(())
196 }
197
198 #[cfg(test)]
199 pub(crate) fn post_raw(
200 &self,
201 key: usize,
202 bytes_transferred: u32,
203 overlapped: *mut OVERLAPPED,
204 ) -> io::Result<()> {
205 // SAFETY: tests use this to simulate an operation completion for a live
206 // operation's OVERLAPPED pointer.
207 let ok = unsafe {
208 PostQueuedCompletionStatus(self.raw(), bytes_transferred, key, overlapped.cast_const())
209 };
210 if ok == 0 {
211 return Err(io::Error::last_os_error());
212 }
213 Ok(())
214 }
215
216 /// Dequeue one completion packet, waiting up to `timeout_ms` milliseconds.
217 ///
218 /// Returns `Ok(None)` when the wait times out with no packet. A packet is
219 /// returned even when its operation failed; the failure is reported through
220 /// [`Completion::error`].
221 pub fn get(&self, timeout_ms: u32) -> io::Result<Option<Completion>> {
222 let mut bytes_transferred: u32 = 0;
223 let mut key: usize = 0;
224 let mut overlapped: *mut OVERLAPPED = std::ptr::null_mut();
225 // SAFETY: all out-parameters are valid for the duration of the call.
226 let ok = unsafe {
227 GetQueuedCompletionStatus(
228 self.raw(),
229 &mut bytes_transferred,
230 &mut key,
231 &mut overlapped,
232 timeout_ms,
233 )
234 };
235 if ok != 0 {
236 return Ok(Some(Completion {
237 key,
238 bytes_transferred,
239 overlapped,
240 error: None,
241 // Deregister as the packet leaves the queue, recovering the
242 // identity in the same step. See `deregister_dequeued`.
243 id: self.deregister_dequeued(key, overlapped),
244 claimed: Cell::new(false),
245 }));
246 }
247
248 let error = io::Error::last_os_error();
249 if overlapped.is_null() {
250 if error.raw_os_error() == Some(WAIT_TIMEOUT as i32) {
251 return Ok(None);
252 }
253 return Err(error);
254 }
255 // A packet for a failed operation was dequeued.
256 Ok(Some(Completion {
257 key,
258 bytes_transferred,
259 overlapped,
260 error: Some(error),
261 id: self.deregister_dequeued(key, overlapped),
262 claimed: Cell::new(false),
263 }))
264 }
265
266 /// Deregister an operation whose packet has just been dequeued, returning the
267 /// identity the registry recorded for it.
268 ///
269 /// Registration ends at *dequeue*, not at reclamation, because the registry
270 /// answers "is a packet still coming for this operation?" -- which is what
271 /// [`run_down`](Self::run_down) waits on and what
272 /// [`cancel`](AssociatedEndpoint::cancel) must not act against. Once the
273 /// packet is off the queue neither is true any longer, and no further packet
274 /// will ever arrive for it.
275 ///
276 /// Keeping the entry until the [`Completion`] was dropped instead made a
277 /// held completion indistinguishable from an undelivered packet, so dropping
278 /// the port while one was held blocked forever in an unbounded `get` waiting
279 /// for a packet that had already been delivered. The completion still owns the
280 /// operation's storage and still frees it on drop; that ownership is simply
281 /// no longer expressed through the registry.
282 ///
283 /// A null pointer (a user packet) and an address this port never registered
284 /// both return `None`, since `remove` reports only what it held. Also
285 /// decrements `key`'s endpoint-scoped outstanding count, but only when a
286 /// real registered operation was actually removed -- a user-posted packet
287 /// (`CompletionPort::post`) never incremented one, so it must not decrement
288 /// one either, even if it happens to carry a key that collides with a live
289 /// endpoint's.
290 fn deregister_dequeued(&self, key: usize, overlapped: *mut OVERLAPPED) -> Option<OperationId> {
291 let id = self.state.live.remove(overlapped);
292 if id.is_some() {
293 if crate::source_tracking_enabled() {
294 lock(&self.state.tracked).remove(&(overlapped as usize));
295 }
296 if let Some(outstanding) = lock(&self.state.endpoint_outstanding).get(&key) {
297 outstanding.fetch_sub(1, Ordering::SeqCst);
298 }
299 }
300 id
301 }
302
303 pub(crate) fn raw(&self) -> HANDLE {
304 self.handle.as_raw_handle()
305 }
306
307 /// The registry of operations submitted through this port whose completion
308 /// packet has not yet been dequeued, for backends that must validate an
309 /// identity before cancelling.
310 ///
311 /// Only the socket backend reaches for this; the file backend cancels
312 /// through `AssociatedEndpoint`, which already holds the port.
313 #[cfg(feature = "socket")]
314 pub(crate) fn live_operations(&self) -> &OperationRegistry {
315 &self.state.live
316 }
317
318 /// The number of operations submitted through this port whose completion
319 /// packet has not yet been dequeued.
320 ///
321 /// A packet that has been dequeued is *not* counted, even if the
322 /// [`Completion`] is still held and its storage not yet released. The count
323 /// measures what the port is still waiting to deliver, which is what
324 /// [`run_down`](Self::run_down) blocks on.
325 #[must_use]
326 pub fn outstanding(&self) -> usize {
327 self.state.live.len()
328 }
329
330 /// Block until a completion packet has been dequeued for every outstanding
331 /// operation.
332 ///
333 /// Every outstanding operation must already be cancelled or otherwise
334 /// destined to complete -- which closing or cancelling the endpoints
335 /// guarantees -- or this waits indefinitely. Each packet dequeued here is
336 /// reclaimed immediately, since the [`Completion`] this creates is dropped
337 /// within the loop.
338 ///
339 /// A [`Completion`] held elsewhere does not keep this waiting: its packet has
340 /// already been delivered, so it is not outstanding. It still owns the
341 /// operation's storage, and still frees it when dropped, which may be after
342 /// this returns and after the port itself is gone.
343 ///
344 /// The port is shareable, so another thread may be consuming completions at
345 /// the same time. Each wait here is therefore bounded (`RUN_DOWN_POLL_MS`)
346 /// and the live count is rechecked after it: a concurrent consumer can
347 /// dequeue the last packet -- and clear its registry entry -- in the window
348 /// between this loop observing a nonzero count and beginning its own wait,
349 /// and an unbounded wait would then block forever on a packet no longer
350 /// coming. Removing a registry entry does not wake a `GetQueuedCompletionStatus`
351 /// already in progress, so the recheck, not a wakeup, is what ends the wait.
352 pub fn run_down(&self) -> io::Result<()> {
353 while self.outstanding() > 0 {
354 self.get(RUN_DOWN_POLL_MS)?;
355 }
356 Ok(())
357 }
358
359 /// Submit an owned operation on this port, running the shared outstanding-
360 /// operation accounting around a caller-supplied native call.
361 ///
362 /// This is the accounting core shared by every endpoint kind: `issue`
363 /// receives only the stable `OVERLAPPED` pointer and performs the single
364 /// native overlapped call (the endpoint supplies its own handle or socket by
365 /// capture). The counting, source tracking, and reclamation on the
366 /// synchronous and failure paths are identical regardless of endpoint kind.
367 ///
368 /// # Safety
369 ///
370 /// `issue` must start exactly one overlapped operation using the provided
371 /// `OVERLAPPED` pointer and no other storage, and must classify the outcome
372 /// correctly: [`Issued::Pending`] only when a completion packet will be
373 /// delivered to this port, [`Issued::Completed`] only when the operation is
374 /// already complete and no packet will arrive, and `Err` only when the
375 /// submission failed and no completion will arrive.
376 ///
377 /// `issue` must not unwind. The operation is registered before it runs and
378 /// deregistered only on the paths below; a panic out of `issue` skips that
379 /// and -- because a panic before starting the I/O is indistinguishable from
380 /// one after -- rundown could then wait forever for a packet that will never
381 /// arrive. A closure that might panic must catch it and return `Err`.
382 #[track_caller]
383 pub(crate) unsafe fn submit_with<P, F>(&self, operation: Operation<P>, issue: F) -> Submitted<P>
384 where
385 P: Send + 'static,
386 F: FnOnce(*mut OVERLAPPED) -> io::Result<Issued>,
387 {
388 // Transfer the operation's storage out; the caller (kernel) owns it until
389 // it is reclaimed. `into_overlapped` arms the type-erased reclaim thunk.
390 let overlapped = operation.into_overlapped();
391 let identity = overlapped as usize;
392 // Stamp this submission with a fresh generation, so the identity names
393 // this operation and not whatever later operation may reuse the address.
394 let id = OperationId::mint(overlapped);
395
396 // Register before issuing so a completion cannot race ahead of the count.
397 let state = &self.state;
398 state.live.insert(id);
399 let tracking = crate::source_tracking_enabled();
400 if tracking {
401 lock(&state.tracked).insert(
402 identity,
403 Track {
404 location: Location::caller(),
405 #[cfg(feature = "operation-backtrace")]
406 backtrace: std::backtrace::Backtrace::capture(),
407 },
408 );
409 }
410
411 match issue(overlapped) {
412 Ok(Issued::Pending) => Submitted::Pending(id),
413 Ok(Issued::Completed { bytes_transferred }) => {
414 // Synchronous completion with no packet to arrive (the
415 // skip-on-success case): balance the count and reclaim inline.
416 state.live.remove(overlapped);
417 if tracking {
418 lock(&state.tracked).remove(&identity);
419 }
420 // SAFETY: the operation completed synchronously and no packet
421 // will arrive, so the kernel is done with the storage; reclaim
422 // the box we just leaked, exactly once.
423 let mut operation = unsafe { Operation::<P>::from_overlapped(overlapped) };
424 operation.set_state(OperationState::Completed);
425 Submitted::Completed {
426 operation,
427 bytes_transferred,
428 }
429 }
430 Err(error) => {
431 state.live.remove(overlapped);
432 if tracking {
433 lock(&state.tracked).remove(&identity);
434 }
435 // SAFETY: no completion will arrive, so reclaim the operation we
436 // just leaked, exactly once.
437 let mut operation = unsafe { Operation::<P>::from_overlapped(overlapped) };
438 operation.set_state(OperationState::Idle);
439 Submitted::Failed { operation, error }
440 }
441 }
442 }
443
444 fn report_outstanding_at_drop(&self, count: usize) {
445 let tracked = lock(&self.state.tracked);
446 let mut message = format!(
447 "windows-overlapped-io-sys: CompletionPort dropped with {count} operation(s) still \
448 outstanding; call run_down() before dropping to control when this blocks."
449 );
450 if tracked.is_empty() {
451 message.push_str(
452 " Enable source tracking (WINDOWS_OVERLAPPED_IO_SYS_TRACK=1, or \
453 set_source_tracking) to identify the submit sites.",
454 );
455 } else {
456 message.push_str(" Sources:");
457 for track in tracked.values() {
458 message.push_str("\n - ");
459 message.push_str(&track.location.to_string());
460 #[cfg(feature = "operation-backtrace")]
461 {
462 message.push_str("\n backtrace:\n");
463 message.push_str(&track.backtrace.to_string());
464 }
465 }
466 }
467 eprintln!("{message}");
468 }
469}
470
471impl fmt::Debug for CompletionPort {
472 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
473 f.debug_struct("CompletionPort")
474 .field("outstanding", &self.outstanding())
475 .finish_non_exhaustive()
476 }
477}
478
479impl Drop for CompletionPort {
480 fn drop(&mut self) {
481 let count = self.outstanding();
482 if count == 0 {
483 return;
484 }
485 // A blocking Drop signals that run_down() was skipped; name the sources.
486 self.report_outstanding_at_drop(count);
487 // Block until the kernel is done with every operation's storage.
488 let _ = self.run_down();
489 }
490}
491
492/// An overlapped endpoint bound to exactly one [`CompletionPort`].
493///
494/// The endpoint owns its handle and borrows the port it is associated with, so
495/// the port cannot be dropped while any endpoint still routes completions to it.
496/// It is intentionally not `Clone`.
497#[derive(Debug)]
498pub struct AssociatedEndpoint<'port> {
499 port: &'port CompletionPort,
500 handle: OwnedHandle,
501 key: usize,
502 modes: crate::NotificationModes,
503 /// This endpoint's own outstanding-operation count (M1, PR #20 review
504 /// response), incremented in `submit` and decremented in
505 /// `CompletionPort::deregister_dequeued`. Shared with the port's
506 /// `endpoint_outstanding` map under this endpoint's key, so `Drop` can
507 /// block on it without needing the port to know about endpoints at all.
508 outstanding: Arc<AtomicUsize>,
509}
510
511impl<'port> AssociatedEndpoint<'port> {
512 /// Borrow the underlying handle for issuing native operations.
513 #[must_use]
514 pub fn handle(&self) -> BorrowedHandle<'_> {
515 self.handle.as_handle()
516 }
517
518 /// The completion key packets from this endpoint are tagged with.
519 #[must_use]
520 pub fn key(&self) -> usize {
521 self.key
522 }
523
524 /// The completion-notification modes this endpoint carries, as declared
525 /// before it was associated.
526 ///
527 /// The adapters read this to classify a synchronous native success: with
528 /// [`crate::NotificationModes::skip_completion_port_on_success`] set, no
529 /// packet will arrive for one, so it is an [`Issued::Completed`] rather than
530 /// an [`Issued::Pending`].
531 #[must_use]
532 pub fn notification_modes(&self) -> crate::NotificationModes {
533 self.modes
534 }
535
536 /// The completion port this endpoint is associated with.
537 #[must_use]
538 pub fn port(&self) -> &'port CompletionPort {
539 self.port
540 }
541
542 /// How many operations submitted on this endpoint have not yet had their
543 /// completion packet dequeued.
544 ///
545 /// Unlike [`CompletionPort::outstanding`], this is scoped to this endpoint
546 /// alone -- what [`AssociatedEndpoint`]'s own blocking `Drop` waits on.
547 #[must_use]
548 pub fn outstanding(&self) -> usize {
549 self.outstanding.load(Ordering::SeqCst)
550 }
551
552 /// Submit an owned operation on this endpoint.
553 ///
554 /// `issue` performs the single native overlapped call using the endpoint's
555 /// handle and the operation's stable `OVERLAPPED` pointer. It classifies the
556 /// outcome as an [`Issued`]: [`Issued::Pending`] when a completion packet
557 /// will be delivered, or [`Issued::Completed`] when the call finished
558 /// synchronously and no packet will arrive -- the state a handle in
559 /// `FILE_SKIP_COMPLETION_PORT_ON_SUCCESS` mode reports on synchronous
560 /// success. It returns `Err` for an immediate failure that yields no
561 /// completion.
562 ///
563 /// On the pending path the operation's storage is transferred to the kernel
564 /// and recovered later with [`Completion::claim`]. On the synchronous and
565 /// failure paths the operation is returned intact through [`Submitted`] so
566 /// its storage can be reused or inspected.
567 ///
568 /// # Panics
569 ///
570 /// Panics if this port already has a live operation registered at the new
571 /// operation's storage address. That cannot happen through ordinary use --
572 /// `operation` owns freshly boxed storage -- and indicates a defect in this
573 /// crate's own bookkeeping rather than in the calling code. See
574 /// [`OperationRegistry::insert`] for the invariant involved.
575 ///
576 /// # Safety
577 ///
578 /// `issue` must start exactly one overlapped operation using the provided
579 /// `OVERLAPPED` pointer and no other storage, and must classify the outcome
580 /// correctly: [`Issued::Pending`] only when a completion packet will be
581 /// delivered to this endpoint's port, [`Issued::Completed`] only when the
582 /// operation is already complete and no packet will arrive, and `Err` only
583 /// when the submission failed and no completion will arrive.
584 ///
585 /// `issue` must not unwind: a panic out of it can leave an operation
586 /// registered with no completion coming, which makes rundown wait forever. A
587 /// closure that might panic must catch it and return `Err`.
588 ///
589 /// `P: 'static` because submitting leaks the operation's storage, to be
590 /// freed later through a thunk carrying no lifetime -- see
591 /// [`Operation::into_overlapped`].
592 #[track_caller]
593 pub unsafe fn submit<P, F>(&self, operation: Operation<P>, issue: F) -> Submitted<P>
594 where
595 P: Send + 'static,
596 F: FnOnce(BorrowedHandle<'_>, *mut OVERLAPPED) -> io::Result<Issued>,
597 {
598 let handle = self.handle();
599 // Incremented before the native call, not after `Submitted::Pending` is
600 // observed: `CompletionPort::get` can run on another thread and dequeue
601 // this same operation's packet before this thread would otherwise have
602 // recorded it, which would underflow the count on decrement. Reversed
603 // below on every path that turns out not to be pending, mirroring
604 // `submit_with`'s own register-before-issue discipline.
605 self.outstanding.fetch_add(1, Ordering::SeqCst);
606 // SAFETY: `issue`'s safety contract (restated on this method) is exactly
607 // what the shared core requires; the endpoint only supplies its handle.
608 let result = unsafe {
609 self.port
610 .submit_with(operation, move |overlapped| issue(handle, overlapped))
611 };
612 if !matches!(result, Submitted::Pending(_)) {
613 self.outstanding.fetch_sub(1, Ordering::SeqCst);
614 }
615 result
616 }
617
618 /// Request cancellation of a single outstanding operation.
619 ///
620 /// Cancellation is only a request: the operation still completes, typically
621 /// with `ERROR_OPERATION_ABORTED`, and that completion remains the point at
622 /// which its storage is reclaimed with [`Completion::claim`].
623 ///
624 /// The identity is checked against this port's live operations first. An
625 /// identity whose operation has already completed is rejected with
626 /// [`io::ErrorKind::NotFound`] and no native call is made, even if another
627 /// operation has since been given the same storage address -- so retaining
628 /// an identity too long can never cancel an unrelated operation.
629 ///
630 /// # Errors
631 ///
632 /// Returns [`io::ErrorKind::NotFound`] if `id` no longer names a live
633 /// operation, or the error from `CancelIoEx` if the native request fails.
634 pub fn cancel(&self, id: OperationId) -> io::Result<()> {
635 // The liveness check and the native call happen under one registry
636 // guard; splitting them would let the address be recycled in between.
637 self.port.state.live.cancel_if_live(id, || {
638 // SAFETY: cancelling by a valid handle and an OVERLAPPED identity
639 // the registry has confirmed still names a live operation, and which
640 // cannot be reclaimed and reissued while the guard is held.
641 let ok = unsafe { CancelIoEx(self.raw_handle(), id.as_ptr()) };
642 if ok == 0 {
643 return Err(io::Error::last_os_error());
644 }
645 Ok(())
646 })
647 }
648
649 /// Request cancellation of every outstanding operation on this endpoint.
650 pub fn cancel_all(&self) -> io::Result<()> {
651 // SAFETY: a null OVERLAPPED cancels all operations on the handle.
652 let ok = unsafe { CancelIoEx(self.raw_handle(), std::ptr::null()) };
653 if ok == 0 {
654 return Err(io::Error::last_os_error());
655 }
656 Ok(())
657 }
658
659 fn raw_handle(&self) -> HANDLE {
660 self.handle.as_raw_handle()
661 }
662}
663
664impl Drop for AssociatedEndpoint<'_> {
665 fn drop(&mut self) {
666 // `self.handle` is not actually closed until *after* this function
667 // returns (Rust drops struct fields in declaration order once the
668 // custom `Drop::drop` body finishes), so relying on close-cancels-
669 // pending-I/O would deadlock here: nothing has told the kernel to
670 // finish anything yet. Cancel explicitly first, while the handle is
671 // still open and the call is valid.
672 if self.outstanding() > 0 {
673 let _ = self.cancel_all();
674 }
675 // Mirrors `CompletionPort::run_down`: bounded waits, rechecking the
676 // live count after each, because a concurrent consumer can dequeue
677 // this endpoint's last packet between the check and the wait. Every
678 // packet dequeued here is for *some* endpoint on this port, not
679 // necessarily this one; whichever it is for still updates that
680 // endpoint's own count via `deregister_dequeued`; only this loop's own
681 // exit condition cares which one just reached zero.
682 while self.outstanding() > 0 {
683 let _ = self.port.get(RUN_DOWN_POLL_MS);
684 }
685 // Safe to drop the shared counter's port-side entry only now: nothing
686 // will ever decrement it again, since no packet can still be coming
687 // for an operation this endpoint submitted.
688 lock(&self.port.state.endpoint_outstanding).remove(&self.key);
689 }
690}
691
692/// How the native call in [`AssociatedEndpoint::submit`] accepted an operation.
693///
694/// `issue` returns this to tell the backend whether a completion packet will be
695/// delivered, so the port's outstanding-operation accounting stays correct.
696///
697/// This asks **"will a completion packet arrive?"**, *not* "did the native call
698/// finish synchronously?". Those come apart precisely because Windows queues a
699/// packet for a synchronously-successful overlapped request too -- see
700/// [`Issued::Pending`], which is where that distinction is spelled out.
701#[derive(Debug, Clone, Copy)]
702pub enum Issued {
703 /// A completion packet will be delivered to the port; the operation's
704 /// storage stays with the kernel until [`Completion::claim`] recovers it.
705 ///
706 /// This covers **both** of the native call's success shapes, and it is the
707 /// right answer for both for the same reason -- a packet is coming either
708 /// way:
709 ///
710 /// - `ERROR_IO_PENDING`: the request has not finished; its packet is queued
711 /// when it does.
712 /// - **Native success returned immediately** (`TRUE`, or `0` from Winsock):
713 /// the request has *already* finished, and its packet is *already*
714 /// queued.
715 ///
716 /// The second is the counter-intuitive one, so it is worth stating why it
717 /// holds. For a handle opened for asynchronous I/O and associated with a
718 /// completion port, the I/O Manager queues a completion packet for every
719 /// request it completes, including one that succeeds immediately without
720 /// returning `ERROR_IO_PENDING`. The single documented exception is
721 /// `FILE_SKIP_COMPLETION_PORT_ON_SUCCESS`, and that flag's own definition
722 /// is what establishes the general rule: it says the I/O Manager "does not
723 /// queue a completion entry to the port, *when it would ordinarily do so*"
724 /// for a request that "returns success immediately without returning
725 /// ERROR_PENDING". Ordinarily -- that is, without the flag -- it does.
726 ///
727 /// So an immediate `TRUE` tells a caller that the *I/O* is done. It says
728 /// nothing about whether the *packet* is still coming, which is the only
729 /// thing this enum is about.
730 Pending,
731 /// The operation finished synchronously and no completion packet will
732 /// arrive -- the outcome a handle in `FILE_SKIP_COMPLETION_PORT_ON_SUCCESS`
733 /// mode reports on synchronous success. `bytes_transferred` is the count the
734 /// native call reported; the operation's storage is reclaimed inline and
735 /// returned through [`Submitted::Completed`].
736 ///
737 /// Reporting this when a packet *will* in fact arrive is a memory-safety
738 /// bug, not a bookkeeping one: `submit_with` treats it as license to drop
739 /// the operation from the port's outstanding set and reclaim its boxed
740 /// storage inline. The packet that was nevertheless queued then arrives
741 /// carrying a dangling `OVERLAPPED`, and claiming it frees that box a
742 /// second time -- while [`CompletionPort::run_down`] has already been told
743 /// there is nothing left to wait for. This is why every adapter that does
744 /// not enable skip-on-success mode reports [`Issued::Pending`] on immediate
745 /// native success.
746 Completed {
747 /// The number of bytes the synchronous call transferred.
748 bytes_transferred: u32,
749 },
750}
751
752/// The outcome of [`AssociatedEndpoint::submit`].
753#[derive(Debug)]
754pub enum Submitted<P> {
755 /// A completion will arrive; the storage was transferred to the kernel and
756 /// is recovered later with [`Completion::claim`]. The [`OperationId`]
757 /// identifies the in-flight operation for cancellation and matching.
758 Pending(OperationId),
759 /// The operation completed synchronously with no completion packet (the
760 /// skip-on-success case); the operation is returned already reclaimed, with
761 /// the bytes the native call transferred.
762 Completed {
763 /// The operation whose synchronous completion was observed inline.
764 operation: Operation<P>,
765 /// The number of bytes the synchronous call transferred.
766 bytes_transferred: u32,
767 },
768 /// Submission failed immediately with no completion; the operation is
769 /// returned so its storage can be reused or dropped.
770 Failed {
771 /// The operation whose submission failed.
772 operation: Operation<P>,
773 /// The immediate failure reported by the native call.
774 error: io::Error,
775 },
776}
777
778/// A completion packet dequeued from a [`CompletionPort`].
779///
780/// Dequeuing removes the operation from the port's outstanding set, so holding a
781/// completion never blocks [`CompletionPort::run_down`] or the port's `Drop`. The
782/// completion still owns the operation's storage until it is dropped or
783/// [`claim`](Self::claim)ed, and may outlive the port it came from.
784pub struct Completion {
785 key: usize,
786 bytes_transferred: u32,
787 overlapped: *mut OVERLAPPED,
788 error: Option<io::Error>,
789 /// The identity of the operation this packet completes, recovered from the
790 /// registry at dequeue time. `None` for a user packet, which has no
791 /// operation. Stored whole rather than as a bare generation, so nothing here
792 /// ever re-pairs an address with a generation.
793 id: Option<OperationId>,
794 claimed: Cell<bool>,
795}
796
797impl fmt::Debug for Completion {
798 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
799 f.debug_struct("Completion")
800 .field("key", &self.key)
801 .field("bytes_transferred", &self.bytes_transferred)
802 .field("overlapped", &self.overlapped)
803 .field("id", &self.id)
804 .field("error", &self.error)
805 .finish_non_exhaustive()
806 }
807}
808
809impl Drop for Completion {
810 fn drop(&mut self) {
811 // Claimed completions handed ownership to the caller; user packets carry
812 // a null overlapped and own nothing.
813 if self.claimed.get() || self.overlapped.is_null() {
814 return;
815 }
816 // The registry entry is already gone -- dequeue removed it -- so this
817 // only has to release the storage the completion still owns.
818 // SAFETY: the completion arrived, so the kernel is done with the storage;
819 // the operation's armed reclaim thunk frees the box exactly once.
820 unsafe { crate::operation::reclaim_from_overlapped(self.overlapped) };
821 }
822}
823
824impl Completion {
825 /// The completion key the packet was tagged with.
826 #[must_use]
827 pub fn key(&self) -> usize {
828 self.key
829 }
830
831 /// The number of bytes transferred by the operation.
832 #[must_use]
833 pub fn bytes_transferred(&self) -> u32 {
834 self.bytes_transferred
835 }
836
837 /// The `OVERLAPPED` pointer identifying the completed operation.
838 ///
839 /// For a user packet this is whatever value was passed to
840 /// [`CompletionPort::post`].
841 #[must_use]
842 pub fn overlapped_ptr(&self) -> *mut OVERLAPPED {
843 self.overlapped
844 }
845
846 /// The identity of the operation this packet completes.
847 ///
848 /// It matches the [`OperationId`] that [`AssociatedEndpoint::submit`]
849 /// returned for the operation, so a caller holding submission-time
850 /// identities can match a completion against them directly. Returns `None`
851 /// for a user packet from [`CompletionPort::post`], which completes no
852 /// operation.
853 #[must_use]
854 pub fn id(&self) -> Option<OperationId> {
855 self.id
856 }
857
858 /// The failure of the completed operation, if it did not succeed.
859 #[must_use]
860 pub fn error(&self) -> Option<&io::Error> {
861 self.error.as_ref()
862 }
863
864 /// Recover the owned operation whose completion this is.
865 ///
866 /// # Safety
867 ///
868 /// This completion must have been produced by submitting an `Operation<P>`
869 /// of this exact type through [`AssociatedEndpoint::submit`], and it must be
870 /// claimed exactly once.
871 pub unsafe fn claim<P>(&self) -> Operation<P> {
872 // Mark claimed so this completion's own drop will not also reclaim it.
873 // The registry entry was already removed at dequeue.
874 self.claimed.set(true);
875 // SAFETY: by this function's contract, the identity is a matching leaked
876 // Operation<P>, reclaimed exactly once here.
877 let mut operation = unsafe { Operation::<P>::from_overlapped(self.overlapped) };
878 operation.set_state(OperationState::Completed);
879 operation
880 }
881}
882
883#[cfg(test)]
884mod tests;