include/boost/corosio/win_stream_handle.hpp

100.0% Lines (5/5) 100.0% List of functions (3/3) 75.0% Branches (3/4)
win_stream_handle.hpp
f(x) Functions (3)
Line Branch TLA Hits Source Code
1 //
2 // Copyright (c) 2026 Michael Vandeberg
3 //
4 // Distributed under the Boost Software License, Version 1.0. (See accompanying
5 // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
6 //
7 // Official repository: https://github.com/cppalliance/corosio
8 //
9
10 #ifndef BOOST_COROSIO_WIN_STREAM_HANDLE_HPP
11 #define BOOST_COROSIO_WIN_STREAM_HANDLE_HPP
12
13 #include <boost/corosio/detail/config.hpp>
14 #include <boost/corosio/detail/platform.hpp>
15
16 #if BOOST_COROSIO_HAS_IOCP || defined(BOOST_COROSIO_MRDOCS)
17
18 #include <boost/corosio/detail/except.hpp>
19 #include <boost/corosio/detail/native_handle.hpp>
20 #include <boost/corosio/io/io_stream.hpp>
21 #include <boost/capy/ex/executor_ref.hpp>
22 #include <boost/capy/ex/execution_context.hpp>
23 #include <boost/capy/concept/executor.hpp>
24
25 #include <concepts>
26 #include <coroutine>
27 #include <stop_token>
28 #include <system_error>
29 #include <type_traits>
30
31 namespace boost::corosio {
32 /** Drives an already-open overlapped Windows handle from an `io_context`.
33
34 Wraps a handle with an implicit position: the server or client
35 end of a named pipe opened with `FILE_FLAG_OVERLAPPED`, a COM
36 port, or a mailslot. The handle must come from the caller; this
37 type never creates one.
38
39 The type name is deliberately platform-qualified. Portability
40 comes from the interfaces it implements, not from the name. A
41 `win_stream_handle` is an @ref io_stream, so `capy::read`,
42 `capy::write`, other `capy::Stream`-constrained algorithms and
43 TLS layering work on it exactly as they do on a socket.
44
45 There is no `wait()`. IOCP has no readiness primitive for
46 arbitrary handles, which is the reason this type is not a
47 portable one.
48
49 @par Ownership
50 `assign()` takes ownership and `close()` closes the handle.
51 `release()` detaches it from this context's completion port and
52 hands it back. It throws and keeps the handle while an operation
53 is still in flight, or if Windows refuses the detach.
54
55 While the handle is bound to a completion port, every overlapped
56 call on it queues a packet to that port. Do not issue your own
57 overlapped I/O on it, such as `ConnectNamedPipe`, `WaitCommEvent`,
58 or `DeviceIoControl`. The exception is a call whose `OVERLAPPED`
59 has the low-order bit of `hEvent` set, which suppresses the
60 packet. Connect a pipe server before `assign()`.
61
62 @par Rejected Handles
63 Console handles, sockets, disk files, and directories are rejected
64 with `errc::operation_not_supported`. So are handles opened without
65 `FILE_FLAG_OVERLAPPED`, including both ends of an anonymous
66 `CreatePipe` pipe, and handles already in
67 skip-completion-port-on-success mode. A named pipe created with
68 `FILE_FLAG_OVERLAPPED` is the substitute for `CreatePipe`;
69 @ref stream_file adopts disk files. A handle already bound to
70 another completion port fails with `errc::invalid_argument`.
71
72 @par Transfers
73 Each `read_some()` and `write_some()` transfers at most the first
74 non-empty buffer of the sequence. On a message-mode pipe, a
75 message longer than the buffer is returned across successive
76 reads. A read that finds the peer closed completes with
77 `capy::error::eof`; a write completes with `errc::broken_pipe`.
78 On a message-mode pipe, a zero-length message reads as end of file, and
79 a zero-length write sends nothing.
80
81 @par Thread Safety
82 Distinct objects: Safe.@n
83 Shared objects: Unsafe. One read and one write may be in flight
84 simultaneously; two reads (or two writes) may not.
85
86 @par Example
87 @par !example win_stream_handle
88
89 @see io_stream, stream_file, win_random_access_handle
90 */
91 class BOOST_COROSIO_DECL win_stream_handle : public io_stream
92 {
93 public:
94 /** Define backend hooks for handle operations.
95
96 The IOCP backend derives from this to implement handle I/O.
97 */
98 struct implementation : io_stream::implementation
99 {
100 /// Return the platform handle, or `INVALID_HANDLE_VALUE` when not open.
101 virtual native_handle_type native_handle() const noexcept = 0;
102
103 /** Release ownership of the native handle.
104
105 Cancels pending operations and detaches the handle from
106 the completion port without closing it. The caller takes
107 ownership.
108
109 @return The native handle.
110
111 @throws std::system_error `errc::device_or_resource_busy` if
112 an operation is still in flight, or
113 `errc::operation_not_supported` if the handle cannot be
114 detached. The handle stays owned on throw.
115 */
116 virtual native_handle_type release_handle() = 0;
117
118 /** Request cancellation of pending asynchronous operations.
119
120 All outstanding operations complete with a code that
121 compares equal to `capy::cond::canceled`.
122 */
123 virtual void cancel() noexcept = 0;
124 };
125
126 /** Closes the handle if open, cancelling pending operations. */
127 ~win_stream_handle() override;
128
129 /** Construct from an execution context.
130
131 @param ctx The execution context that owns this object.
132 */
133 explicit win_stream_handle(capy::execution_context& ctx);
134
135 /** Construct from an executor.
136
137 The overload excludes `win_stream_handle` itself so that it
138 cannot displace the move constructor.
139
140 @tparam Ex A type satisfying `capy::Executor`.
141 @param ex The executor whose context owns this object.
142 */
143 template<class Ex>
144 requires(!std::same_as<std::remove_cvref_t<Ex>, win_stream_handle>) &&
145 capy::Executor<Ex>
146 explicit win_stream_handle(Ex const& ex) : win_stream_handle(ex.context())
147 {
148 }
149
150 /** Transfer ownership of the handle from @p other.
151
152 After the move, @p other is in a moved-from state and may only
153 be destroyed or assigned to.
154
155 @param other The object to move from.
156 @pre No awaitables returned by @p other's methods exist.
157 */
158 win_stream_handle(win_stream_handle&& other) noexcept
159 : io_object(std::move(other))
160 {
161 }
162
163 /** Close any held handle and transfer ownership from @p other.
164
165 After the move, @p other is in a moved-from state and may only
166 be destroyed or assigned to.
167
168 @param other The object to move from.
169 @return `*this`.
170 @pre No awaitables returned by either object's methods exist.
171 */
172 win_stream_handle& operator=(win_stream_handle&& other) noexcept
173 {
174 io_object::operator=(std::move(other));
175 return *this;
176 }
177
178 /// Copy construction is disabled; the handle is uniquely owned.
179 win_stream_handle(win_stream_handle const&) = delete;
180 /// Copy assignment is disabled; the handle is uniquely owned.
181 win_stream_handle& operator=(win_stream_handle const&) = delete;
182
183 /** Adopt an existing overlapped handle.
184
185 @param h The native handle to adopt.
186
187 @return `error::already_open` if this object is open.
188 `errc::invalid_argument` when @p h is bound to another
189 completion port. `errc::bad_file_descriptor` when @p h is
190 null, invalid, or closed. `errc::operation_not_supported`
191 when @p h is a console, a socket, a synchronous-mode
192 handle, a handle in skip-completion-port-on-success mode,
193 a disk file, or a directory. It is also returned when the
194 handle's I/O mode cannot be queried. Otherwise the error
195 the system reported, or an empty code.
196
197 @par Exception Safety
198 Throws nothing. On failure the object is unchanged and @p h
199 stays with the caller.
200
201 @see release
202 */
203 [[nodiscard]] std::error_code assign(native_handle_type h) noexcept;
204
205 /** Release ownership of the native handle.
206
207 Pending operations are cancelled first. If one is still in
208 flight, the object keeps the handle and this throws. The same
209 happens when Windows refuses to detach the handle from the
210 execution context's completion port. Call `release()` again
211 once the cancelled operations have completed. Detaching
212 requires Windows 8.1 or later. On success the object becomes
213 not-open and the caller is responsible for closing the
214 result.
215
216 @return The native handle.
217
218 @throws std::system_error `errc::bad_file_descriptor` if the
219 object is not open; `errc::device_or_resource_busy` if an
220 operation is still in flight; `errc::operation_not_supported`
221 if the handle cannot be detached.
222 */
223 native_handle_type release();
224
225 /** Close the handle.
226
227 Pending operations complete with a code that compares equal
228 to `capy::cond::canceled`. Does nothing when not open.
229 */
230 void close() noexcept;
231
232 /** Check whether a handle is held.
233
234 @return `true` if a handle is held.
235 */
236 85x bool is_open() const noexcept
237 {
238
3/4
✓ Branch 3 → 4 taken 85 times.
✗ Branch 3 → 8 not taken.
✓ Branch 6 → 7 taken 32 times.
✓ Branch 6 → 8 taken 53 times.
85x return h_ && get().native_handle() != ~native_handle_type{};
239 }
240
241 /** Get the native handle.
242
243 @return The native handle, or `INVALID_HANDLE_VALUE` when not open.
244 */
245 native_handle_type native_handle() const noexcept;
246
247 /** Cancel pending asynchronous operations.
248
249 Outstanding operations complete with a code that compares
250 equal to `capy::cond::canceled`.
251 */
252 void cancel() noexcept;
253
254 protected:
255 /// Default-construct (for derived types that initialize `io_object` directly).
256 1x win_stream_handle() noexcept = default;
257
258 /** Construct from a handle.
259
260 @param h The handle this object takes ownership of.
261 */
262 explicit win_stream_handle(handle h) noexcept : io_object(std::move(h)) {}
263
264 private:
265 /// Return the implementation downcast to this type's interface.
266 132x implementation& get() const noexcept
267 {
268 132x return *static_cast<implementation*>(h_.get());
269 }
270 };
271
272 } // namespace boost::corosio
273
274 #endif // BOOST_COROSIO_HAS_IOCP || BOOST_COROSIO_MRDOCS
275
276 #endif
277