include/boost/corosio/random_access_file.hpp

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