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)
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 |