windows_overlapped_io_sys/started.rs
1// Copyright (c) 2026 Mike Grier
2//! The outcome of a buffer-owning adapter's submission.
3//!
4//! An adapter (`fs::read`, `device::ioctl`, `socket::send`, ...) owns the buffers
5//! an operation reads into or writes from, so it can report one of exactly two
6//! things once the native call returns: the operation is in flight and its
7//! result must be claimed from a completion later, or it is already finished and
8//! the buffers are back in hand. [`Started`] is that pair.
9//!
10//! # Why the synchronous case is visible rather than hidden
11//!
12//! It exists because of `FILE_SKIP_COMPLETION_PORT_ON_SUCCESS` (see
13//! [`crate::Issued::Pending`] for what that mode changes). Without the mode,
14//! every operation on an IOCP-associated endpoint produces a completion packet
15//! -- even one that succeeded immediately -- so an adapter can always hand back
16//! a claim-later token. With the mode, a synchronously-successful operation
17//! produces no packet at all, and the token would name a completion that is
18//! never coming.
19//!
20//! An adapter therefore cannot paper over the difference, because the two cases
21//! do not merely differ in timing: they differ in *who owns the payload*. A
22//! caller that ignored the distinction would either wait forever for a packet
23//! that will not arrive, or drop a result that was already delivered. Making
24//! both arms explicit costs a `match` and removes that whole class of mistake.
25//!
26//! A caller that never enables the mode will only ever observe
27//! [`Started::Pending`], and can say so with [`Started::expect_pending`].
28
29/// What became of an adapter submission that did not fail immediately.
30///
31/// This is [`crate::Submitted`] as an adapter reports it: the `Failed` arm is
32/// folded into the enclosing `io::Result`'s `Err`, and the operation storage of
33/// a synchronous completion is already reduced to the payload the matching
34/// token's `claim` would have yielded, so the two arms report the same shape.
35#[derive(Debug)]
36pub enum Started<T, P> {
37 /// The operation is in flight and a completion will arrive for it. Claim its
38 /// result with the token, which also carries the operation's identity for
39 /// cancellation and matching.
40 Pending(T),
41 /// The operation finished synchronously and no completion will arrive, so
42 /// there is nothing to claim and its payload is returned directly.
43 ///
44 /// Only reachable on an endpoint in `FILE_SKIP_COMPLETION_PORT_ON_SUCCESS`
45 /// mode; an endpoint left in the default mode always reports
46 /// [`Started::Pending`], because the I/O Manager queues a packet even for an
47 /// immediate success.
48 Completed {
49 /// The buffers the operation owned -- the same payload the token's
50 /// `claim` yields on the pending path.
51 payload: P,
52 /// The byte count the native call reported.
53 bytes_transferred: usize,
54 },
55}
56
57impl<T, P> Started<T, P> {
58 /// Whether a completion will arrive for this operation.
59 #[must_use]
60 pub fn is_pending(&self) -> bool {
61 matches!(self, Started::Pending(_))
62 }
63
64 /// Whether the operation already finished with no completion to come.
65 #[must_use]
66 pub fn is_completed(&self) -> bool {
67 matches!(self, Started::Completed { .. })
68 }
69
70 /// The token, if a completion will arrive.
71 #[must_use]
72 pub fn pending(self) -> Option<T> {
73 match self {
74 Started::Pending(token) => Some(token),
75 Started::Completed { .. } => None,
76 }
77 }
78
79 /// The payload and byte count, if the operation already finished.
80 #[must_use]
81 pub fn completed(self) -> Option<(P, usize)> {
82 match self {
83 Started::Completed {
84 payload,
85 bytes_transferred,
86 } => Some((payload, bytes_transferred)),
87 Started::Pending(_) => None,
88 }
89 }
90
91 /// The token, panicking if the operation completed synchronously.
92 ///
93 /// For a caller that never puts its endpoints in
94 /// `FILE_SKIP_COMPLETION_PORT_ON_SUCCESS` mode, where the synchronous arm is
95 /// unreachable and matching on it is noise.
96 ///
97 /// # Panics
98 ///
99 /// Panics with `message` if the operation completed synchronously, which
100 /// means the endpoint was in skip-on-success mode after all.
101 #[must_use]
102 #[track_caller]
103 pub fn expect_pending(self, message: &str) -> T {
104 match self {
105 Started::Pending(token) => token,
106 Started::Completed { .. } => panic!("{message}"),
107 }
108 }
109}
110
111#[cfg(test)]
112mod tests;