include/boost/corosio/win_object_handle.hpp

100.0% Lines (14/14) 100.0% List of functions (7/7) 100.0% Branches (5/5)
win_object_handle.hpp
f(x) Functions (7)
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_OBJECT_HANDLE_HPP
11 #define BOOST_COROSIO_WIN_OBJECT_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/detail/op_base.hpp>
21 #include <boost/corosio/io/io_object.hpp>
22 #include <boost/capy/continuation.hpp>
23 #include <boost/capy/io_result.hpp>
24 #include <boost/capy/ex/executor_ref.hpp>
25 #include <boost/capy/ex/execution_context.hpp>
26 #include <boost/capy/ex/io_env.hpp>
27 #include <boost/capy/concept/executor.hpp>
28
29 #include <concepts>
30 #include <coroutine>
31 #include <stop_token>
32 #include <system_error>
33 #include <type_traits>
34
35 namespace boost::corosio {
36
37 /** Waits on an already-open Windows kernel object from an `io_context`.
38
39 Wraps a waitable handle: a process or thread, an event, a
40 semaphore, a waitable timer, or a job. A coroutine can await its
41 signaled state without blocking a thread. The handle must come
42 from the caller and must carry `SYNCHRONIZE` access.
43
44 Waits are carried by the Windows thread pool (one
45 `CreateThreadpoolWait` object per `win_object_handle`).
46 Completions are delivered through the `io_context`, so the
47 awaiting coroutine resumes on its executor as usual.
48
49 @par Rejected Handles
50 Only processes, threads, events, semaphores, waitable timers and
51 jobs are accepted; any other object type is rejected with
52 `errc::operation_not_supported`. Mutexes are excluded because a
53 satisfied mutex wait acquires the mutex on a pool thread the
54 resuming coroutine does not own. File objects are excluded
55 because one signals on any I/O completion. Console input and
56 directory change notification handles are file objects, so they
57 are rejected too. Pseudo-handles such as
58 `GetCurrentThread()` and handles without `SYNCHRONIZE` access
59 are rejected the same way. Every handle is rejected on an
60 `io_context` whose locking mode is `locking_mode::unsafe`,
61 because the wait completes from a thread-pool thread.
62
63 @par Ownership
64 `assign()` takes ownership and `close()` closes the handle.
65 `release()` hands it back.
66
67 @par Thread Safety
68 Distinct objects: Safe.@n
69 Shared objects: Unsafe. One wait may be pending at a time.
70
71 @see win_stream_handle, timeout
72 */
73 class BOOST_COROSIO_DECL win_object_handle : public io_object
74 {
75 public:
76 /** Define backend hooks for kernel object waits.
77
78 The IOCP backend derives from this to implement the wait.
79 */
80 struct implementation : io_object::implementation
81 {
82 /** Initiate an asynchronous wait for the object's signaled state.
83
84 @param cont Continuation to resume on completion; owned by
85 the awaitable.
86 @param ex Executor for dispatching the completion.
87 @param token Stop token for cancellation.
88 @param ec Output error code.
89 @return Coroutine handle to resume immediately.
90 */
91 virtual std::coroutine_handle<> wait(
92 capy::continuation& cont,
93 capy::executor_ref ex,
94 std::stop_token token,
95 std::error_code* ec) = 0;
96
97 /// Return the platform handle, or `INVALID_HANDLE_VALUE` when not open.
98 virtual native_handle_type native_handle() const noexcept = 0;
99
100 /** Release ownership of the native handle.
101
102 Cancels a pending wait without closing the handle. The
103 wait completes with a code that compares equal to
104 `capy::cond::canceled`, unless the kernel already
105 satisfied it, in which case it reports success. The
106 caller takes ownership.
107
108 @return The native handle.
109 */
110 virtual native_handle_type release_handle() noexcept = 0;
111
112 /** Request cancellation of a pending wait.
113
114 A wait the kernel has not yet satisfied completes with a
115 code that compares equal to `capy::cond::canceled`.
116 */
117 virtual void cancel() noexcept = 0;
118 };
119
120 /// Represent the awaitable returned by @ref wait.
121 struct wait_awaitable : detail::void_op_base<wait_awaitable>
122 {
123 private:
124 friend win_object_handle;
125
126 3221x explicit wait_awaitable(win_object_handle& o) noexcept : o_(o) {}
127
128 friend detail::void_op_base<wait_awaitable>;
129
130 win_object_handle& o_;
131 // Lives in the awaiting frame until resumption, so a completion
132 // posted through an executor never shares the object's state.
133 mutable capy::continuation cont_;
134
135 std::coroutine_handle<>
136 3221x dispatch(std::coroutine_handle<> h, capy::executor_ref ex) const
137 {
138 3221x cont_.h = h;
139
1/1
✓ Branch 4 → 5 taken 3221 times.
3221x return o_.get().wait(cont_, ex, token_, &ec_);
140 }
141 };
142
143 /** Closes the handle if open.
144
145 A pending wait completes with a code that compares equal to
146 `capy::cond::canceled`, unless the kernel already satisfied
147 it, in which case it reports success.
148 */
149 ~win_object_handle() override;
150
151 /** Construct from an execution context.
152
153 @param ctx The execution context that owns this object.
154 */
155 explicit win_object_handle(capy::execution_context& ctx);
156
157 /** Construct from an executor.
158
159 The overload excludes `win_object_handle` itself so that it
160 cannot displace the move constructor.
161
162 @tparam Ex A type satisfying `capy::Executor`.
163 @param ex The executor whose context owns this object.
164 */
165 template<class Ex>
166 requires(!std::same_as<std::remove_cvref_t<Ex>, win_object_handle>) &&
167 capy::Executor<Ex>
168 explicit win_object_handle(Ex const& ex) : win_object_handle(ex.context())
169 {
170 }
171
172 /** Transfer ownership of the handle from @p other.
173
174 After the move, @p other is in a moved-from state and may only
175 be destroyed or assigned to.
176
177 @param other The object to move from.
178 @pre No awaitables returned by @p other's methods exist.
179 */
180 1x win_object_handle(win_object_handle&& other) noexcept
181 1x : io_object(std::move(other))
182 {
183 1x }
184
185 /** Close any held handle and transfer ownership from @p other.
186
187 After the move, @p other is in a moved-from state and may only
188 be destroyed or assigned to.
189
190 @param other The object to move from.
191 @return `*this`.
192 @pre No awaitables returned by either object's methods exist.
193 */
194 win_object_handle& operator=(win_object_handle&& other) noexcept
195 {
196 io_object::operator=(std::move(other));
197 return *this;
198 }
199
200 /// Copy construction is disabled; the handle is uniquely owned.
201 win_object_handle(win_object_handle const&) = delete;
202 /// Copy assignment is disabled; the handle is uniquely owned.
203 win_object_handle& operator=(win_object_handle const&) = delete;
204
205 /** Adopt an existing waitable handle.
206
207 No wait is performed on @p h; its signal state is unchanged.
208
209 @param h The native handle to adopt.
210
211 @return `error::already_open` if this object is open.
212 `errc::bad_file_descriptor` when @p h is null, invalid,
213 closed, or `GetCurrentProcess()` (which equals
214 `INVALID_HANDLE_VALUE`). `errc::operation_not_supported`
215 when @p h is any other pseudo-handle, lacks `SYNCHRONIZE`
216 access, or is not a process, thread, event, semaphore,
217 waitable timer, or job. The same when this `io_context`
218 uses `locking_mode::unsafe`.
219 Otherwise an empty code.
220
221 @par Exception Safety
222 Throws nothing. On failure the object is unchanged and @p h
223 stays with the caller.
224
225 @see release
226 */
227 [[nodiscard]] std::error_code assign(native_handle_type h) noexcept;
228
229 /** Release ownership of the native handle.
230
231 The object becomes not-open. A pending wait completes with a
232 code that compares equal to `capy::cond::canceled`, unless the
233 kernel already satisfied it, in which case it reports success.
234 The caller is responsible for closing the result.
235
236 @return The native handle.
237
238 @throws std::system_error `errc::bad_file_descriptor` if the
239 object is not open.
240
241 @post `is_open() == false`
242 */
243 native_handle_type release();
244
245 /** Close the handle.
246
247 A pending wait completes with a code that compares equal to
248 `capy::cond::canceled`, unless the kernel already satisfied
249 it, in which case it reports success. Does nothing when not
250 open.
251 */
252 void close() noexcept;
253
254 /** Check whether a handle is held.
255
256 @return `true` if a handle is held.
257 */
258 8259x bool is_open() const noexcept
259 {
260
4/4
✓ Branch 3 → 4 taken 8258 times.
✓ Branch 3 → 8 taken 1 time.
✓ Branch 6 → 7 taken 5022 times.
✓ Branch 6 → 8 taken 3236 times.
8259x return h_ && get().native_handle() != ~native_handle_type{};
261 }
262
263 /** Get the native handle.
264
265 @return The native handle, or `INVALID_HANDLE_VALUE` when not open.
266 */
267 native_handle_type native_handle() const noexcept;
268
269 /** Cancel a pending wait.
270
271 A wait the kernel has not yet satisfied completes with a code
272 that compares equal to `capy::cond::canceled`.
273 */
274 void cancel() noexcept;
275
276 /** Wait for the object to become signaled.
277
278 Completes when the kernel satisfies the wait. Some objects
279 change state when a wait is satisfied: an auto-reset event,
280 a semaphore, or a waitable timer without manual reset. For
281 these, the change has happened by the time this completes.
282 A wait the kernel satisfied is reported as success even when
283 it raced `cancel()`, `close()` or `release()`.
284
285 Only one wait may be pending. While the first is pending, a
286 second `wait()` completes with `errc::operation_in_progress`
287 and does not disturb it. Calling `wait()` concurrently on a
288 shared object is unsafe. There is no timeout parameter;
289 compose with @ref timeout or a stop token.
290
291 @return An awaitable yielding `capy::io_result<>`. Yields
292 `errc::bad_file_descriptor` when not open and a code
293 comparing equal to `capy::cond::canceled` when cancelled
294 before the kernel satisfied the wait.
295
296 @par Example
297 @par !example wait
298
299 @see timeout
300 */
301 3221x [[nodiscard]] wait_awaitable wait()
302 {
303 3221x return wait_awaitable(*this);
304 }
305
306 protected:
307 /// Default-construct (for derived types that initialize `io_object` directly).
308 win_object_handle() noexcept = default;
309
310 /** Construct from a handle.
311
312 @param h The handle this object takes ownership of.
313 */
314 1x explicit win_object_handle(handle h) noexcept : io_object(std::move(h)) {}
315
316 private:
317 /// Return the implementation downcast to this type's interface.
318 16708x implementation& get() const noexcept
319 {
320 16708x return *static_cast<implementation*>(h_.get());
321 }
322 };
323
324 } // namespace boost::corosio
325
326 #endif // BOOST_COROSIO_HAS_IOCP || BOOST_COROSIO_MRDOCS
327
328 #endif
329