include/boost/corosio/random_access_file.hpp

96.7% Lines (59/61) 100.0% List of functions (19/19) 81.8% Branches (18/22)
random_access_file.hpp
f(x) Functions (19)
Function Calls Lines Branches Blocks
boost::corosio::random_access_file::read_some_at_awaitable<boost::capy::mutable_buffer>::read_some_at_awaitable(boost::corosio::random_access_file::read_some_at_awaitable<boost::capy::mutable_buffer>&&) :133 1148x 100.0% 100.0% boost::corosio::random_access_file::read_some_at_awaitable<boost::capy::mutable_buffer>::~read_some_at_awaitable() :133 1722x 100.0% 100.0% boost::corosio::random_access_file::read_some_at_awaitable<boost::capy::mutable_buffer>::read_some_at_awaitable(boost::corosio::random_access_file&, unsigned long long, boost::capy::mutable_buffer) :142 574x 100.0% 100.0% boost::corosio::random_access_file::read_some_at_awaitable<boost::capy::mutable_buffer>::await_ready() const :155 287x 100.0% 100.0% 100.0% boost::corosio::random_access_file::read_some_at_awaitable<boost::capy::mutable_buffer>::await_resume() const :162 285x 100.0% 100.0% 100.0% boost::corosio::random_access_file::read_some_at_awaitable<boost::capy::mutable_buffer>::await_suspend(std::__1::coroutine_handle<void>, boost::capy::io_env const*) :169 285x 80.0% 50.0% 50.0% boost::corosio::random_access_file::write_some_at_awaitable<boost::capy::const_buffer>::write_some_at_awaitable(boost::corosio::random_access_file::write_some_at_awaitable<boost::capy::const_buffer>&&) :180 148x 100.0% 100.0% boost::corosio::random_access_file::write_some_at_awaitable<boost::capy::const_buffer>::~write_some_at_awaitable() :180 222x 100.0% 100.0% boost::corosio::random_access_file::write_some_at_awaitable<boost::capy::const_buffer>::write_some_at_awaitable(boost::corosio::random_access_file&, unsigned long long, boost::capy::const_buffer) :189 74x 100.0% 100.0% boost::corosio::random_access_file::write_some_at_awaitable<boost::capy::const_buffer>::await_ready() const :202 37x 100.0% 100.0% 100.0% boost::corosio::random_access_file::write_some_at_awaitable<boost::capy::const_buffer>::await_resume() const :209 37x 100.0% 100.0% 100.0% boost::corosio::random_access_file::write_some_at_awaitable<boost::capy::const_buffer>::await_suspend(std::__1::coroutine_handle<void>, boost::capy::io_env const*) :216 35x 80.0% 50.0% 50.0% boost::corosio::random_access_file::random_access_file<boost::corosio::io_context::executor_type>(boost::corosio::io_context::executor_type const&) :245 2x 100.0% 100.0% boost::corosio::random_access_file::random_access_file(boost::corosio::random_access_file&&) :250 4x 100.0% 100.0% boost::corosio::random_access_file::is_open() const :294 646x 100.0% 100.0% 100.0% auto boost::corosio::random_access_file::read_some_at<boost::capy::mutable_buffer>(unsigned long long, boost::capy::mutable_buffer const&) :313 287x 100.0% 100.0% 100.0% auto boost::corosio::random_access_file::write_some_at<boost::capy::const_buffer>(unsigned long long, boost::capy::const_buffer const&) :331 37x 100.0% 100.0% 100.0% boost::corosio::random_access_file::random_access_file(boost::corosio::io_object::handle) :411 16x 100.0% 100.0% boost::corosio::random_access_file::get() const :414 1105x 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_RANDOM_ACCESS_FILE_HPP
11 #define BOOST_COROSIO_RANDOM_ACCESS_FILE_HPP
12
13 #include <boost/corosio/detail/config.hpp>
14 #include <boost/corosio/detail/platform.hpp>
15 #include <boost/corosio/detail/except.hpp>
16 #include <boost/corosio/detail/native_handle.hpp>
17 #include <boost/corosio/detail/buffer_param.hpp>
18 #include <boost/corosio/file_base.hpp>
19 #include <boost/corosio/io/io_object.hpp>
20 #include <boost/capy/io_result.hpp>
21 #include <boost/capy/ex/executor_ref.hpp>
22 #include <boost/capy/ex/execution_context.hpp>
23 #include <boost/capy/ex/io_env.hpp>
24 #include <boost/capy/concept/executor.hpp>
25 #include <boost/capy/buffers.hpp>
26
27 #include <concepts>
28 #include <coroutine>
29 #include <cstddef>
30 #include <cstdint>
31 #include <type_traits>
32 #include <filesystem>
33 #include <stop_token>
34 #include <system_error>
35
36 namespace boost::corosio {
37
38 /** An asynchronous random-access file for coroutine I/O.
39
40 Provides asynchronous read and write operations at explicit
41 byte offsets, without maintaining an implicit file position.
42
43 On POSIX platforms, file I/O is dispatched to a thread pool
44 (blocking `preadv`/`pwritev`) with completion posted back to
45 the scheduler. On Windows, true overlapped I/O is used via IOCP.
46
47 @par Thread Safety
48 Distinct objects: Safe.@n
49 Shared objects: Unsafe. Multiple concurrent reads and writes
50 are supported from coroutines sharing the same file object,
51 but external synchronization is required for non-async
52 operations (open, close, size, resize, etc.).
53
54 @par Example
55 @par !example random_access_file
56 */
57 class BOOST_COROSIO_DECL random_access_file : public io_object
58 {
59 public:
60 /** Platform-specific random-access file implementation interface.
61
62 Backends derive from this to provide offset-based file I/O.
63 */
64 struct implementation : io_object::implementation
65 {
66 /** Initiate a read at the given offset.
67
68 @param offset Byte offset into the file.
69 @param h Coroutine handle to resume on completion.
70 @param ex Executor for dispatching the completion.
71 @param buf The buffer to read into.
72 @param token Stop token for cancellation.
73 @param ec Output error code.
74 @param bytes_out Output bytes transferred.
75 @return Coroutine handle to resume immediately.
76 */
77 virtual std::coroutine_handle<> read_some_at(
78 std::uint64_t offset,
79 std::coroutine_handle<> h,
80 capy::executor_ref ex,
81 buffer_param buf,
82 std::stop_token token,
83 std::error_code* ec,
84 std::size_t* bytes_out) = 0;
85
86 /** Initiate a write at the given offset.
87
88 @param offset Byte offset into the file.
89 @param h Coroutine handle to resume on completion.
90 @param ex Executor for dispatching the completion.
91 @param buf The buffer to write from.
92 @param token Stop token for cancellation.
93 @param ec Output error code.
94 @param bytes_out Output bytes transferred.
95 @return Coroutine handle to resume immediately.
96 */
97 virtual std::coroutine_handle<> write_some_at(
98 std::uint64_t offset,
99 std::coroutine_handle<> h,
100 capy::executor_ref ex,
101 buffer_param buf,
102 std::stop_token token,
103 std::error_code* ec,
104 std::size_t* bytes_out) = 0;
105
106 /// Return the platform file descriptor or handle.
107 virtual native_handle_type native_handle() const noexcept = 0;
108
109 /// Cancel pending asynchronous operations.
110 virtual void cancel() noexcept = 0;
111
112 /// Return the file size in bytes.
113 virtual std::uint64_t size() const = 0;
114
115 /// Resize the file to @p new_size bytes.
116 virtual std::error_code resize(std::uint64_t new_size) noexcept = 0;
117
118 /// Synchronize file data to stable storage.
119 virtual std::error_code sync_data() noexcept = 0;
120
121 /// Synchronize file data and metadata to stable storage.
122 virtual std::error_code sync_all() noexcept = 0;
123
124 /// Release ownership of the native handle.
125 virtual native_handle_type release() = 0;
126
127 /// Adopt an existing native handle.
128 virtual std::error_code assign(native_handle_type handle) noexcept = 0;
129 };
130
131 /** Awaitable for async read-at operations. */
132 template<class MutableBufferSequence>
133 struct read_some_at_awaitable
134 {
135 random_access_file& f_;
136 std::uint64_t offset_;
137 MutableBufferSequence buffers_;
138 std::stop_token token_;
139 mutable std::error_code ec_;
140 287x mutable std::size_t bytes_ = 0;
141
142 861x read_some_at_awaitable(
143 random_access_file& f,
144 std::uint64_t offset,
145 MutableBufferSequence
146 buffers) noexcept(std::
147 is_nothrow_move_constructible_v<
148 MutableBufferSequence>)
149 287x : f_(f)
150 287x , offset_(offset)
151 287x , buffers_(std::move(buffers))
152 287x {
153 574x }
154
155 287x bool await_ready() const noexcept
156 {
157 // A pre-set ec_ means the initiator failed before
158 // dispatch (e.g. a closed object).
159
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 281 times.
287x return static_cast<bool>(ec_) || token_.stop_requested();
160 }
161
162 285x [[nodiscard]] capy::io_result<std::size_t> await_resume() const noexcept
163 {
164
2/2
✓ Branch 0 taken 4 times.
✓ Branch 1 taken 281 times.
285x if (token_.stop_requested())
165 4x return {make_error_code(std::errc::operation_canceled), 0};
166 281x return {ec_, bytes_};
167 285x }
168
169 285x auto await_suspend(std::coroutine_handle<> h, capy::io_env const* env)
170 -> std::coroutine_handle<>
171 {
172 285x token_ = env->stop_token;
173
2/4
✓ Branch 0 taken 285 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 285 times.
✗ Branch 3 not taken.
570x return f_.get().read_some_at(
174 285x offset_, h, env->executor, buffers_, token_, &ec_, &bytes_);
175 }
176 };
177
178 /** Awaitable for async write-at operations. */
179 template<class ConstBufferSequence>
180 struct write_some_at_awaitable
181 {
182 random_access_file& f_;
183 std::uint64_t offset_;
184 ConstBufferSequence buffers_;
185 std::stop_token token_;
186 mutable std::error_code ec_;
187 37x mutable std::size_t bytes_ = 0;
188
189 111x write_some_at_awaitable(
190 random_access_file& f,
191 std::uint64_t offset,
192 ConstBufferSequence
193 buffers) noexcept(std::
194 is_nothrow_move_constructible_v<
195 ConstBufferSequence>)
196 37x : f_(f)
197 37x , offset_(offset)
198 37x , buffers_(std::move(buffers))
199 37x {
200 74x }
201
202 37x bool await_ready() const noexcept
203 {
204 // A pre-set ec_ means the initiator failed before
205 // dispatch (e.g. a closed object).
206
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 31 times.
37x return static_cast<bool>(ec_) || token_.stop_requested();
207 }
208
209 37x [[nodiscard]] capy::io_result<std::size_t> await_resume() const noexcept
210 {
211
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 35 times.
37x if (token_.stop_requested())
212 2x return {make_error_code(std::errc::operation_canceled), 0};
213 35x return {ec_, bytes_};
214 37x }
215
216 35x auto await_suspend(std::coroutine_handle<> h, capy::io_env const* env)
217 -> std::coroutine_handle<>
218 {
219 35x token_ = env->stop_token;
220
2/4
✓ Branch 0 taken 35 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 35 times.
✗ Branch 3 not taken.
70x return f_.get().write_some_at(
221 35x offset_, h, env->executor, buffers_, token_, &ec_, &bytes_);
222 }
223 };
224
225 public:
226 /** Destructor.
227
228 Closes the file if open, cancelling any pending operations.
229 */
230 ~random_access_file() override;
231
232 /** Construct from an execution context.
233
234 @param ctx The execution context that will own this file.
235 */
236 explicit random_access_file(capy::execution_context& ctx);
237
238 /** Construct from an executor.
239
240 @param ex The executor whose context will own this file.
241 */
242 template<class Ex>
243 requires(!std::same_as<std::remove_cvref_t<Ex>, random_access_file>) &&
244 capy::Executor<Ex>
245 2x explicit random_access_file(Ex const& ex) : random_access_file(ex.context())
246 {
247 2x }
248
249 /** Move constructor. */
250 4x random_access_file(random_access_file&& other) noexcept
251 2x : io_object(std::move(other))
252 4x {
253 4x }
254
255 /** Move assignment operator. */
256 random_access_file& operator=(random_access_file&& other) noexcept
257 {
258 if (this != &other)
259 {
260 close();
261 h_ = std::move(other.h_);
262 }
263 return *this;
264 }
265
266 random_access_file(random_access_file const&) = delete;
267 random_access_file& operator=(random_access_file const&) = delete;
268
269 /** Open a file.
270
271 Failures such as a missing file or insufficient permissions
272 are expected runtime conditions and are reported through the
273 returned error code. If the file is already open, it is
274 closed first.
275
276 @param path The filesystem path to open.
277 @param mode Bitmask of @ref file_base::flags specifying
278 access mode and creation behavior.
279
280 @return The error code, empty on success.
281 */
282 [[nodiscard]] std::error_code open(
283 std::filesystem::path const& path,
284 file_base::flags mode = file_base::read_only) noexcept;
285
286 /** Close the file.
287
288 Releases file resources. Any pending operations complete
289 with `errc::operation_canceled`.
290 */
291 void close() noexcept;
292
293 /** Check if the file is open. */
294 646x bool is_open() const noexcept
295 {
296 #if BOOST_COROSIO_HAS_IOCP && !defined(BOOST_COROSIO_MRDOCS)
297 return h_ && get().native_handle() != ~native_handle_type(0);
298 #else
299
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 620 times.
646x return h_ && get().native_handle() >= 0;
300 #endif
301 }
302
303 /** Read data at the given offset.
304
305 @param offset Byte offset into the file.
306 @param buffers The buffer sequence to read into.
307
308 @return An awaitable yielding `(error_code, std::size_t)`.
309
310 A closed file reports `errc::bad_file_descriptor`.
311 */
312 template<capy::MutableBufferSequence MB>
313 287x [[nodiscard]] auto read_some_at(std::uint64_t offset, MB const& buffers)
314 {
315 287x read_some_at_awaitable<MB> aw(*this, offset, buffers);
316
2/2
✓ Branch 0 taken 281 times.
✓ Branch 1 taken 2 times.
287x if (!is_open())
317 2x aw.ec_ = make_error_code(std::errc::bad_file_descriptor);
318 287x return aw;
319 287x }
320
321 /** Write data at the given offset.
322
323 @param offset Byte offset into the file.
324 @param buffers The buffer sequence to write from.
325
326 @return An awaitable yielding `(error_code, std::size_t)`.
327
328 A closed file reports `errc::bad_file_descriptor`.
329 */
330 template<capy::ConstBufferSequence CB>
331 37x [[nodiscard]] auto write_some_at(std::uint64_t offset, CB const& buffers)
332 {
333 37x write_some_at_awaitable<CB> aw(*this, offset, buffers);
334
2/2
✓ Branch 0 taken 31 times.
✓ Branch 1 taken 2 times.
37x if (!is_open())
335 2x aw.ec_ = make_error_code(std::errc::bad_file_descriptor);
336 37x return aw;
337 37x }
338
339 /** Cancel pending asynchronous operations. */
340 void cancel() noexcept;
341
342 /** Get the native file descriptor or handle. */
343 native_handle_type native_handle() const noexcept;
344
345 /** Return the file size in bytes.
346
347 @throws std::system_error If the file is not open, or if the
348 underlying size query fails.
349 */
350 std::uint64_t size() const;
351
352 /** Resize the file to @p new_size bytes.
353
354 Failures such as insufficient disk space are reported
355 through the returned error code. A closed file reports
356 `errc::bad_file_descriptor`.
357
358 @param new_size The new file size.
359
360 @return The error code, empty on success.
361 */
362 [[nodiscard]] std::error_code resize(std::uint64_t new_size) noexcept;
363
364 /** Synchronize file data to stable storage.
365
366 Write-back failures such as device I/O errors surface here
367 and are reported through the returned error code. A closed
368 file reports `errc::bad_file_descriptor`.
369
370 @return The error code, empty on success.
371 */
372 [[nodiscard]] std::error_code sync_data() noexcept;
373
374 /** Synchronize file data and metadata to stable storage.
375
376 Write-back failures such as device I/O errors surface here
377 and are reported through the returned error code. A closed
378 file reports `errc::bad_file_descriptor`.
379
380 @return The error code, empty on success.
381 */
382 [[nodiscard]] std::error_code sync_all() noexcept;
383
384 /** Release ownership of the native handle.
385
386 The file object becomes not-open. The caller is
387 responsible for closing the returned handle.
388
389 @return The native file descriptor or handle.
390
391 @throws std::system_error `errc::bad_file_descriptor` if the
392 file is not open.
393 */
394 native_handle_type release();
395
396 /** Adopt an existing native handle.
397
398 Closes any currently open file before adopting.
399 The file object takes ownership of the handle. Handles
400 created elsewhere may be unsuitable for asynchronous I/O;
401 such failures are reported through the returned error code.
402
403 @param handle The native file descriptor or handle.
404
405 @return The error code, empty on success.
406 */
407 [[nodiscard]] std::error_code assign(native_handle_type handle) noexcept;
408
409 protected:
410 /// Construct from a pre-built handle (for native_random_access_file).
411 16x explicit random_access_file(handle h) noexcept : io_object(std::move(h)) {}
412
413 private:
414 1105x inline implementation& get() const noexcept
415 {
416 1105x return *static_cast<implementation*>(h_.get());
417 }
418 };
419
420 } // namespace boost::corosio
421
422 #endif // BOOST_COROSIO_RANDOM_ACCESS_FILE_HPP
423