include/boost/corosio/win_random_access_handle.hpp

94.6% Lines (35/37) 100.0% List of functions (9/9) 70.0% Branches (7/10)
win_random_access_handle.hpp
f(x) Functions (9)
Function Calls Lines Branches Blocks
boost::corosio::win_random_access_handle::read_some_at_awaitable<boost::capy::mutable_buffer>::read_some_at_awaitable(boost::corosio::win_random_access_handle&, unsigned long long, boost::capy::mutable_buffer) :180 15x 100.0% – 100.0% boost::corosio::win_random_access_handle::read_some_at_awaitable<boost::capy::mutable_buffer>::dispatch(std::__n4861::coroutine_handle<void>, boost::capy::executor_ref) const :194 15x 100.0% 100.0% 75.0% boost::corosio::win_random_access_handle::write_some_at_awaitable<boost::capy::const_buffer>::write_some_at_awaitable(boost::corosio::win_random_access_handle&, unsigned long long, boost::capy::const_buffer) :218 35x 100.0% – 100.0% boost::corosio::win_random_access_handle::write_some_at_awaitable<boost::capy::const_buffer>::dispatch(std::__n4861::coroutine_handle<void>, boost::capy::executor_ref) const :232 35x 100.0% 100.0% 75.0% boost::corosio::win_random_access_handle::is_open() const :352 109x 100.0% 75.0% 100.0% auto boost::corosio::win_random_access_handle::read_some_at<boost::capy::mutable_buffer>(unsigned long long, boost::capy::mutable_buffer const&) :367 15x 80.0% 50.0% 83.3% auto boost::corosio::win_random_access_handle::write_some_at<boost::capy::const_buffer>(unsigned long long, boost::capy::const_buffer const&) :385 35x 80.0% 50.0% 83.3% boost::corosio::win_random_access_handle::win_random_access_handle(boost::corosio::io_object::handle) :414 1x 100.0% – 100.0% boost::corosio::win_random_access_handle::get() const :421 195x 100.0% – 100.0%
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_RANDOM_ACCESS_HANDLE_HPP
11 #define BOOST_COROSIO_WIN_RANDOM_ACCESS_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/buffer_param.hpp>
21 #include <boost/corosio/detail/op_base.hpp>
22 #include <boost/corosio/io/io_object.hpp>
23 #include <boost/capy/continuation.hpp>
24 #include <boost/capy/io_result.hpp>
25 #include <boost/capy/ex/executor_ref.hpp>
26 #include <boost/capy/ex/execution_context.hpp>
27 #include <boost/capy/ex/io_env.hpp>
28 #include <boost/capy/concept/executor.hpp>
29 #include <boost/capy/buffers.hpp>
30
31 #include <concepts>
32 #include <coroutine>
33 #include <cstddef>
34 #include <cstdint>
35 #include <stop_token>
36 #include <system_error>
37 #include <type_traits>
38
39 namespace boost::corosio {
40
41 /** Drives an already-open overlapped Windows handle with caller-chosen offsets.
42
43 Wraps an overlapped handle whose I/O is positional. Examples
44 are a volume such as `C:` or a physical drive such as
45 `PhysicalDrive0`, opened by device path with
46 `FILE_FLAG_OVERLAPPED`. Any other overlapped handle works too,
47 unless it is a console, a directory, a socket, a
48 synchronous-mode handle, or one already in
49 skip-completion-port-on-success mode. The handle must come
50 from the caller; this type never creates one.
51 For regular files prefer @ref random_access_file, which also
52 offers `size()`, `resize()` and the sync operations.
53
54 Overlapped pipes are accepted too. The kernel ignores the offset
55 for them, so each operation reads or writes the stream in order
56 of arrival.
57
58 @par Ownership
59 `assign()` takes ownership and `close()` closes the handle.
60 `release()` detaches it from this context's completion port and
61 hands it back. It throws and keeps the handle while an operation
62 is still in flight, or if Windows refuses the detach.
63
64 While the handle is bound to a completion port, every overlapped
65 call on it queues a packet to that port. Do not issue your own
66 overlapped I/O on it, such as `ConnectNamedPipe`, `WaitCommEvent`,
67 or `DeviceIoControl`. The exception is a call whose `OVERLAPPED`
68 has the low-order bit of `hEvent` set, which suppresses the
69 packet. Connect a pipe server before `assign()`.
70
71 @par Concurrency
72 Any number of `read_some_at()` and `write_some_at()` operations
73 may be in flight at once.
74
75 @par Transfers
76 Each operation transfers at most the first non-empty buffer of
77 the sequence. Volume and drive handles require sector-aligned
78 offsets, lengths and buffers; misaligned requests fail with the
79 kernel's error.
80
81 @par Thread Safety
82 Distinct objects: Safe.@n
83 Shared objects: Unsafe, except that concurrent positional
84 operations are supported.
85
86 @par Example
87 @par !example win_random_access_handle
88
89 @see random_access_file, win_stream_handle
90 */
91 class BOOST_COROSIO_DECL win_random_access_handle : public io_object
92 {
93 public:
94 /** Declares the offset-based operations the IOCP backend must implement. */
95 struct implementation : io_object::implementation
96 {
97 /** Initiate a read at the given offset.
98
99 @param offset Byte offset into the handle.
100 @param cont The awaiting coroutine's continuation. It must
101 stay valid until `cont.h` is resumed through @p ex.
102 @param ex Executor for dispatching the completion.
103 @param buf The buffer to read into.
104 @param token Stop token for cancellation.
105 @param ec Output error code.
106 @param bytes_out Output bytes transferred.
107 @return Coroutine handle to resume immediately.
108 */
109 virtual std::coroutine_handle<> read_some_at(
110 std::uint64_t offset,
111 capy::continuation& cont,
112 capy::executor_ref ex,
113 buffer_param buf,
114 std::stop_token token,
115 std::error_code* ec,
116 std::size_t* bytes_out) = 0;
117
118 /** Initiate a write at the given offset.
119
120 @param offset Byte offset into the handle.
121 @param cont The awaiting coroutine's continuation. It must
122 stay valid until `cont.h` is resumed through @p ex.
123 @param ex Executor for dispatching the completion.
124 @param buf The buffer to write from.
125 @param token Stop token for cancellation.
126 @param ec Output error code.
127 @param bytes_out Output bytes transferred.
128 @return Coroutine handle to resume immediately.
129 */
130 virtual std::coroutine_handle<> write_some_at(
131 std::uint64_t offset,
132 capy::continuation& cont,
133 capy::executor_ref ex,
134 buffer_param buf,
135 std::stop_token token,
136 std::error_code* ec,
137 std::size_t* bytes_out) = 0;
138
139 /// Return the platform handle, or `INVALID_HANDLE_VALUE` when not open.
140 virtual native_handle_type native_handle() const noexcept = 0;
141
142 /** Release ownership of the native handle.
143
144 Cancels pending operations and detaches the handle from
145 the completion port without closing it. The caller takes
146 ownership.
147
148 @return The native handle.
149
150 @throws std::system_error `errc::device_or_resource_busy` if
151 an operation is still in flight, or
152 `errc::operation_not_supported` if the handle cannot be
153 detached. The handle stays owned on throw.
154 */
155 virtual native_handle_type release_handle() = 0;
156
157 /** Request cancellation of pending asynchronous operations.
158
159 All outstanding operations complete with a code that
160 compares equal to `capy::cond::canceled`.
161 */
162 virtual void cancel() noexcept = 0;
163 };
164
165 /** Yields `(error_code, std::size_t)` once a positional read completes. */
166 template<class MutableBufferSequence>
167 struct read_some_at_awaitable
168 : detail::bytes_op_base<read_some_at_awaitable<MutableBufferSequence>>
169 {
170 private:
171 friend win_random_access_handle;
172 friend detail::bytes_op_base<
173 read_some_at_awaitable<MutableBufferSequence>>;
174
175 win_random_access_handle& f_;
176 std::uint64_t offset_;
177 MutableBufferSequence buffers_;
178 mutable capy::continuation cont_;
179
180 15x read_some_at_awaitable(
181 win_random_access_handle& f,
182 std::uint64_t offset,
183 MutableBufferSequence
184 buffers) noexcept(std::
185 is_nothrow_move_constructible_v<
186 MutableBufferSequence>)
187 15x : f_(f)
188 15x , offset_(offset)
189 15x , buffers_(std::move(buffers))
190 {
191 15x }
192
193 std::coroutine_handle<>
194 15x dispatch(std::coroutine_handle<> h, capy::executor_ref ex) const
195 {
196 15x cont_.h = h;
197 30x return f_.get().read_some_at(
198
1/1
✓ Branch 5 → 6 taken 15 times.
15x offset_, cont_, ex, buffers_, this->token_, &this->ec_,
199 30x &this->bytes_);
200 }
201 };
202
203 /** Yields `(error_code, std::size_t)` once a positional write completes. */
204 template<class ConstBufferSequence>
205 struct write_some_at_awaitable
206 : detail::bytes_op_base<write_some_at_awaitable<ConstBufferSequence>>
207 {
208 private:
209 friend win_random_access_handle;
210 friend detail::bytes_op_base<
211 write_some_at_awaitable<ConstBufferSequence>>;
212
213 win_random_access_handle& f_;
214 std::uint64_t offset_;
215 ConstBufferSequence buffers_;
216 mutable capy::continuation cont_;
217
218 35x write_some_at_awaitable(
219 win_random_access_handle& f,
220 std::uint64_t offset,
221 ConstBufferSequence
222 buffers) noexcept(std::
223 is_nothrow_move_constructible_v<
224 ConstBufferSequence>)
225 35x : f_(f)
226 35x , offset_(offset)
227 35x , buffers_(std::move(buffers))
228 {
229 35x }
230
231 std::coroutine_handle<>
232 35x dispatch(std::coroutine_handle<> h, capy::executor_ref ex) const
233 {
234 35x cont_.h = h;
235 70x return f_.get().write_some_at(
236
1/1
✓ Branch 5 → 6 taken 35 times.
35x offset_, cont_, ex, buffers_, this->token_, &this->ec_,
237 70x &this->bytes_);
238 }
239 };
240
241 /** Closes the handle if open, cancelling pending operations. */
242 ~win_random_access_handle() override;
243
244 /** Construct from an execution context.
245
246 @param ctx The execution context that owns this object.
247 */
248 explicit win_random_access_handle(capy::execution_context& ctx);
249
250 /** Construct from an executor.
251
252 The overload excludes `win_random_access_handle` itself so
253 that it cannot displace the move constructor.
254
255 @tparam Ex A type satisfying `capy::Executor`.
256 @param ex The executor whose context owns this object.
257 */
258 template<class Ex>
259 requires(!std::same_as<std::remove_cvref_t<Ex>, win_random_access_handle>) &&
260 capy::Executor<Ex>
261 explicit win_random_access_handle(Ex const& ex)
262 : win_random_access_handle(ex.context())
263 {
264 }
265
266 /** Transfer ownership of the handle from @p other.
267
268 After the move, @p other is in a moved-from state and may only
269 be destroyed or assigned to.
270
271 @param other The object to move from.
272 @pre No awaitables returned by @p other's methods exist.
273 */
274 win_random_access_handle(win_random_access_handle&& other) noexcept
275 : io_object(std::move(other))
276 {
277 }
278
279 /** Close any held handle and transfer ownership from @p other.
280
281 After the move, @p other is in a moved-from state and may only
282 be destroyed or assigned to.
283
284 @param other The object to move from.
285 @return `*this`.
286 @pre No awaitables returned by either object's methods exist.
287 */
288 win_random_access_handle& operator=(win_random_access_handle&& other) noexcept
289 {
290 io_object::operator=(std::move(other));
291 return *this;
292 }
293
294 /// Copy construction is disabled; the handle is uniquely owned.
295 win_random_access_handle(win_random_access_handle const&) = delete;
296 /// Copy assignment is disabled; the handle is uniquely owned.
297 win_random_access_handle& operator=(win_random_access_handle const&) = delete;
298
299 /** Adopt an existing overlapped handle.
300
301 @param h The native handle to adopt.
302
303 @return `error::already_open` if this object is open.
304 `errc::invalid_argument` when @p h is bound to another
305 completion port. `errc::bad_file_descriptor` when @p h is
306 null, invalid, or closed. `errc::operation_not_supported`
307 when @p h is a console, a socket, a directory, a
308 synchronous-mode handle, or one already in
309 skip-completion-port-on-success mode. It is also returned
310 when the handle's I/O mode cannot be queried. Otherwise
311 the error the system reported, or an empty code.
312
313 @par Exception Safety
314 Throws nothing. On failure the object is unchanged and @p h
315 stays with the caller.
316
317 @see release
318 */
319 [[nodiscard]] std::error_code assign(native_handle_type h) noexcept;
320
321 /** Release ownership of the native handle.
322
323 Pending operations are cancelled first. If one is still in
324 flight, the object keeps the handle and this throws. The same
325 happens when Windows refuses to detach the handle from the
326 execution context's completion port. Call `release()` again
327 once the cancelled operations have completed. Detaching
328 requires Windows 8.1 or later. On success the object becomes
329 not-open and the caller is responsible for closing the
330 result.
331
332 @return The native handle.
333
334 @throws std::system_error `errc::bad_file_descriptor` if the
335 object is not open; `errc::device_or_resource_busy` if an
336 operation is still in flight; `errc::operation_not_supported`
337 if the handle cannot be detached.
338 */
339 native_handle_type release();
340
341 /** Close the handle.
342
343 Pending operations complete with a code that compares equal
344 to `capy::cond::canceled`. Does nothing when not open.
345 */
346 void close() noexcept;
347
348 /** Check whether a handle is held.
349
350 @return `true` if a handle is held.
351 */
352 109x bool is_open() const noexcept
353 {
354
3/4
✓ Branch 3 → 4 taken 109 times.
✗ Branch 3 → 8 not taken.
✓ Branch 6 → 7 taken 74 times.
✓ Branch 6 → 8 taken 35 times.
109x return h_ && get().native_handle() != ~native_handle_type{};
355 }
356
357 /** Read data at the given offset.
358
359 @param offset Byte offset into the handle.
360 @param buffers The buffer sequence to read into.
361
362 @return An awaitable yielding `(error_code, std::size_t)`.
363
364 A closed handle reports `errc::bad_file_descriptor`.
365 */
366 template<capy::MutableBufferSequence MB>
367 15x [[nodiscard]] auto read_some_at(std::uint64_t offset, MB const& buffers)
368 {
369 15x read_some_at_awaitable<MB> aw(*this, offset, buffers);
370
1/2
✗ Branch 4 → 5 not taken.
✓ Branch 4 → 6 taken 15 times.
15x if (!is_open())
371 ✗ aw.ec_ = make_error_code(std::errc::bad_file_descriptor);
372 15x return aw;
373 }
374
375 /** Write data at the given offset.
376
377 @param offset Byte offset into the handle.
378 @param buffers The buffer sequence to write from.
379
380 @return An awaitable yielding `(error_code, std::size_t)`.
381
382 A closed handle reports `errc::bad_file_descriptor`.
383 */
384 template<capy::ConstBufferSequence CB>
385 35x [[nodiscard]] auto write_some_at(std::uint64_t offset, CB const& buffers)
386 {
387 35x write_some_at_awaitable<CB> aw(*this, offset, buffers);
388
1/2
✗ Branch 4 → 5 not taken.
✓ Branch 4 → 6 taken 35 times.
35x if (!is_open())
389 ✗ aw.ec_ = make_error_code(std::errc::bad_file_descriptor);
390 35x return aw;
391 }
392
393 /** Get the native handle.
394
395 @return The native handle, or `INVALID_HANDLE_VALUE` when not open.
396 */
397 native_handle_type native_handle() const noexcept;
398
399 /** Cancel pending asynchronous operations.
400
401 Outstanding operations complete with a code that compares
402 equal to `capy::cond::canceled`.
403 */
404 void cancel() noexcept;
405
406 protected:
407 /// Default-construct (for derived types that initialize `io_object` directly).
408 win_random_access_handle() noexcept = default;
409
410 /** Construct from a handle.
411
412 @param h The handle this object takes ownership of.
413 */
414 1x explicit win_random_access_handle(handle h) noexcept
415 1x : io_object(std::move(h))
416 {
417 1x }
418
419 private:
420 /// Return the implementation downcast to this type's interface.
421 195x implementation& get() const noexcept
422 {
423 195x return *static_cast<implementation*>(h_.get());
424 }
425 };
426
427 } // namespace boost::corosio
428
429 #endif // BOOST_COROSIO_HAS_IOCP || BOOST_COROSIO_MRDOCS
430
431 #endif
432