include/boost/corosio/win_object_handle.hpp

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