include/boost/corosio/win_random_access_handle.hpp

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