windows_overlapped_io_sys/blocking.rs
1// Copyright (c) 2026 Mike Grier
2//! Blocking backend: complete one overlapped operation at a time by waiting on
3//! the handle with `GetOverlappedResult`.
4//!
5//! This is the backend for overlapped endpoints that are not associated with a
6//! completion port. Because it waits on the handle itself to signal completion,
7//! it supports at most one outstanding operation at a time and completes it
8//! synchronously, so it needs neither ownership transfer nor rundown.
9
10use std::io;
11use std::os::windows::io::{AsHandle, AsRawHandle, BorrowedHandle, OwnedHandle};
12
13use windows_sys::Win32::Foundation::HANDLE;
14use windows_sys::Win32::System::IO::{GetOverlappedResult, OVERLAPPED};
15
16use crate::{Operation, OperationState, UnassociatedEndpoint};
17
18#[cfg(test)]
19mod tests;
20
21/// An overlapped endpoint that completes operations synchronously, one at a
22/// time, via `GetOverlappedResult`.
23///
24/// "One at a time" is enforced, not merely documented: every safe adapter on
25/// this type takes `&mut self`, so a second operation while one is in flight is
26/// a borrow-check error. That matters because `GetOverlappedResult` waits on the
27/// *handle*, which is signalled by whichever operation completes -- with two
28/// outstanding, a call could return the other one's result and hand back buffers
29/// the kernel is still writing into.
30///
31/// The type is still `Send + Sync`, so an endpoint can be moved between threads
32/// or shared behind a `Mutex`; what it cannot do is have two operations in
33/// flight, which is what the mutual exclusion buys.
34///
35/// One owner issuing operations in sequence is the supported shape; sharing one
36/// endpoint across threads and operating from both is rejected at compile time
37/// rather than corrupting a result at run time, since every operation method
38/// takes `&mut self` while an `Arc` hands out only `&BlockingEndpoint`. See the
39/// `read` method (available with the `fs` feature) for runnable examples of
40/// both -- the examples live there because they call `read`, which the `fs`
41/// feature provides, so they compile in every configuration that has it.
42#[derive(Debug)]
43pub struct BlockingEndpoint {
44 handle: OwnedHandle,
45}
46
47impl BlockingEndpoint {
48 /// Take ownership of an overlapped endpoint for synchronous completion.
49 ///
50 /// # Errors
51 ///
52 /// Returns [`TryFromEndpointError`], recoverable back into `endpoint` via
53 /// [`TryFromEndpointError::into_endpoint`], if `endpoint` has
54 /// [`NotificationModes::skip_set_event_on_handle`](crate::NotificationModes::skip_set_event_on_handle)
55 /// set (PR #20 review response). `run` below waits on the handle's own
56 /// internal event via `GetOverlappedResult`, which is exactly the
57 /// notification that mode suppresses -- constructing a `BlockingEndpoint`
58 /// from such an endpoint would have no wakeup source for a genuinely
59 /// pending (`ERROR_IO_PENDING`) operation and could block forever. Win32
60 /// offers no way to clear the mode once set (see
61 /// [`UnassociatedEndpoint::into_handle`]), so this is the one place the
62 /// incompatibility can be caught, and it is checked here rather than left
63 /// as a documentation-only warning.
64 pub fn new(endpoint: UnassociatedEndpoint) -> Result<Self, TryFromEndpointError> {
65 if endpoint.notification_modes().skip_set_event_on_handle {
66 return Err(TryFromEndpointError { endpoint });
67 }
68 Ok(Self {
69 handle: endpoint.into_handle(),
70 })
71 }
72
73 /// Borrow the underlying handle for issuing native operations.
74 #[must_use]
75 pub fn handle(&self) -> BorrowedHandle<'_> {
76 self.handle.as_handle()
77 }
78
79 /// Issue one overlapped operation and block until it completes, returning the
80 /// number of bytes transferred.
81 ///
82 /// `issue` performs the native call with the operation's `OVERLAPPED`
83 /// pointer, returning `Ok` when the operation was accepted (native success or
84 /// `ERROR_IO_PENDING`) and `Err` on an immediate failure.
85 ///
86 /// # Safety
87 ///
88 /// `issue` must start exactly one overlapped operation using the provided
89 /// `OVERLAPPED` pointer and no other storage, and no other operation may be
90 /// outstanding on this endpoint until this call returns. Any buffers the
91 /// operation reads or writes must stay valid for the duration of the call.
92 ///
93 /// This takes `&self` rather than `&mut self` so a caller driving the raw
94 /// seam can hold other borrows of the endpoint; the exclusivity requirement
95 /// is theirs to uphold, which is what makes this `unsafe`. The safe adapters
96 /// built on it take `&mut self` instead, so they cannot violate it.
97 pub unsafe fn run<P, F>(&self, operation: &mut Operation<P>, issue: F) -> io::Result<usize>
98 where
99 F: FnOnce(BorrowedHandle<'_>, *mut OVERLAPPED) -> io::Result<()>,
100 {
101 operation.set_state(OperationState::Submitted);
102 let overlapped = operation.overlapped_ptr();
103 issue(self.handle(), overlapped)?;
104 operation.set_state(OperationState::Pending);
105
106 let mut transferred: u32 = 0;
107 // SAFETY: the handle and overlapped are valid; a non-zero `wait` blocks
108 // on the handle until this single operation completes.
109 let ok = unsafe { GetOverlappedResult(self.raw_handle(), overlapped, &mut transferred, 1) };
110 if ok == 0 {
111 return Err(io::Error::last_os_error());
112 }
113 operation.set_state(OperationState::Completed);
114 Ok(transferred as usize)
115 }
116
117 fn raw_handle(&self) -> HANDLE {
118 self.handle.as_raw_handle()
119 }
120}
121
122/// [`BlockingEndpoint::new`]'s rejection: `endpoint` has
123/// [`crate::NotificationModes::skip_set_event_on_handle`] set, which is
124/// incompatible with the blocking backend (see `new`'s docs). Carries the
125/// endpoint back so a caller that constructed it in error loses nothing.
126#[derive(Debug)]
127pub struct TryFromEndpointError {
128 endpoint: UnassociatedEndpoint,
129}
130
131impl TryFromEndpointError {
132 /// Recover the endpoint this rejection carries.
133 #[must_use]
134 pub fn into_endpoint(self) -> UnassociatedEndpoint {
135 self.endpoint
136 }
137}
138
139impl std::fmt::Display for TryFromEndpointError {
140 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
141 write!(
142 f,
143 "cannot construct a BlockingEndpoint from an endpoint with \
144 skip_set_event_on_handle set: GetOverlappedResult's wait relies \
145 on exactly the notification that mode suppresses"
146 )
147 }
148}
149
150impl std::error::Error for TryFromEndpointError {}
151
152impl From<TryFromEndpointError> for io::Error {
153 fn from(error: TryFromEndpointError) -> Self {
154 io::Error::new(io::ErrorKind::InvalidInput, error)
155 }
156}