include/boost/corosio/random_access_file.hpp

100.0% Lines (50/0/50) 100.0% List of functions (15/0/15)
random_access_file.hpp
f(x) Functions (15)
Function Calls Lines 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, boost::capy::mutable_buffer) :151 293x 100.0% 100.0% boost::corosio::random_access_file::read_some_at_awaitable<boost::capy::mutable_buffer>::await_ready() const :162 293x 100.0% 100.0% boost::corosio::random_access_file::read_some_at_awaitable<boost::capy::mutable_buffer>::await_resume() const :169 291x 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*) :176 291x 100.0% 77.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, boost::capy::const_buffer) :196 43x 100.0% 100.0% boost::corosio::random_access_file::write_some_at_awaitable<boost::capy::const_buffer>::await_ready() const :207 43x 100.0% 100.0% boost::corosio::random_access_file::write_some_at_awaitable<boost::capy::const_buffer>::await_resume() const :214 43x 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*) :221 41x 100.0% 77.0% boost::corosio::random_access_file::random_access_file<boost::corosio::io_context::executor_type>(boost::corosio::io_context::executor_type const&) :250 2x 100.0% 100.0% boost::corosio::random_access_file::random_access_file(boost::corosio::random_access_file&&) :255 2x 100.0% 100.0% boost::corosio::random_access_file::is_open() const :299 682x 100.0% 100.0% auto boost::corosio::random_access_file::read_some_at<boost::capy::mutable_buffer>(unsigned long, boost::capy::mutable_buffer const&) :318 293x 100.0% 100.0% auto boost::corosio::random_access_file::write_some_at<boost::capy::const_buffer>(unsigned long, boost::capy::const_buffer const&) :336 43x 100.0% 100.0% boost::corosio::random_access_file::random_access_file(boost::corosio::io_object::handle) :416 16x 100.0% 100.0% boost::corosio::random_access_file::get() const :419 1171x 100.0% 100.0%
Line 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 293x 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 293x : f_(f)
157 293x , offset_(offset)
158 293x , buffers_(std::move(buffers))
159 {
160 293x }
161
162 293x bool await_ready() const noexcept
163 {
164 // A pre-set ec_ means the initiator failed before
165 // dispatch (e.g. a closed object).
166 293x return static_cast<bool>(ec_) || token_.stop_requested();
167 }
168
169 291x [[nodiscard]] capy::io_result<std::size_t> await_resume() const noexcept
170 {
171 291x if (token_.stop_requested())
172 4x return {make_error_code(std::errc::operation_canceled), 0};
173 287x return {ec_, bytes_};
174 }
175
176 291x auto await_suspend(std::coroutine_handle<> h, capy::io_env const* env)
177 -> std::coroutine_handle<>
178 {
179 291x token_ = env->stop_token;
180 873x return f_.get().read_some_at(
181 873x offset_, h, env->executor, buffers_, token_, &ec_, &bytes_);
182 }
183 };
184
185 /** Awaitable for async write-at operations. */
186 template<class ConstBufferSequence>
187 struct write_some_at_awaitable
188 {
189 random_access_file& f_;
190 std::uint64_t offset_;
191 ConstBufferSequence buffers_;
192 std::stop_token token_;
193 mutable std::error_code ec_;
194 mutable std::size_t bytes_ = 0;
195
196 43x write_some_at_awaitable(
197 random_access_file& f,
198 std::uint64_t offset,
199 ConstBufferSequence buffers)
200 noexcept(std::is_nothrow_move_constructible_v<ConstBufferSequence>)
201 43x : f_(f)
202 43x , offset_(offset)
203 43x , buffers_(std::move(buffers))
204 {
205 43x }
206
207 43x bool await_ready() const noexcept
208 {
209 // A pre-set ec_ means the initiator failed before
210 // dispatch (e.g. a closed object).
211 43x return static_cast<bool>(ec_) || token_.stop_requested();
212 }
213
214 43x [[nodiscard]] capy::io_result<std::size_t> await_resume() const noexcept
215 {
216 43x if (token_.stop_requested())
217 2x return {make_error_code(std::errc::operation_canceled), 0};
218 41x return {ec_, bytes_};
219 }
220
221 41x auto await_suspend(std::coroutine_handle<> h, capy::io_env const* env)
222 -> std::coroutine_handle<>
223 {
224 41x token_ = env->stop_token;
225 123x return f_.get().write_some_at(
226 123x offset_, h, env->executor, buffers_, token_, &ec_, &bytes_);
227 }
228 };
229
230 public:
231 /** Destructor.
232
233 Closes the file if open, cancelling any pending operations.
234 */
235 ~random_access_file() override;
236
237 /** Construct from an execution context.
238
239 @param ctx The execution context that will own this file.
240 */
241 explicit random_access_file(capy::execution_context& ctx);
242
243 /** Construct from an executor.
244
245 @param ex The executor whose context will own this file.
246 */
247 template<class Ex>
248 requires(!std::same_as<std::remove_cvref_t<Ex>, random_access_file>) &&
249 capy::Executor<Ex>
250 2x explicit random_access_file(Ex const& ex) : random_access_file(ex.context())
251 {
252 2x }
253
254 /** Move constructor. */
255 2x random_access_file(random_access_file&& other) noexcept
256 2x : io_object(std::move(other))
257 {
258 2x }
259
260 /** Move assignment operator. */
261 random_access_file& operator=(random_access_file&& other) noexcept
262 {
263 if (this != &other)
264 {
265 close();
266 h_ = std::move(other.h_);
267 }
268 return *this;
269 }
270
271 random_access_file(random_access_file const&) = delete;
272 random_access_file& operator=(random_access_file const&) = delete;
273
274 /** Open a file.
275
276 Failures such as a missing file or insufficient permissions
277 are expected runtime conditions and are reported through the
278 returned error code. If the file is already open, it is
279 closed first.
280
281 @param path The filesystem path to open.
282 @param mode Bitmask of @ref file_base::flags specifying
283 access mode and creation behavior.
284
285 @return The error code, empty on success.
286 */
287 [[nodiscard]] std::error_code open(
288 std::filesystem::path const& path,
289 file_base::flags mode = file_base::read_only) noexcept;
290
291 /** Close the file.
292
293 Releases file resources. Any pending operations complete
294 with `errc::operation_canceled`.
295 */
296 void close() noexcept;
297
298 /** Check if the file is open. */
299 682x bool is_open() const noexcept
300 {
301 #if BOOST_COROSIO_HAS_IOCP && !defined(BOOST_COROSIO_MRDOCS)
302 return h_ && get().native_handle() != ~native_handle_type(0);
303 #else
304 682x return h_ && get().native_handle() >= 0;
305 #endif
306 }
307
308 /** Read data at the given offset.
309
310 @param offset Byte offset into the file.
311 @param buffers The buffer sequence to read into.
312
313 @return An awaitable yielding `(error_code, std::size_t)`.
314
315 A closed file reports `errc::bad_file_descriptor`.
316 */
317 template<capy::MutableBufferSequence MB>
318 293x [[nodiscard]] auto read_some_at(std::uint64_t offset, MB const& buffers)
319 {
320 293x read_some_at_awaitable<MB> aw(*this, offset, buffers);
321 293x if (!is_open())
322 2x aw.ec_ = make_error_code(std::errc::bad_file_descriptor);
323 293x return aw;
324 }
325
326 /** Write data at the given offset.
327
328 @param offset Byte offset into the file.
329 @param buffers The buffer sequence to write from.
330
331 @return An awaitable yielding `(error_code, std::size_t)`.
332
333 A closed file reports `errc::bad_file_descriptor`.
334 */
335 template<capy::ConstBufferSequence CB>
336 43x [[nodiscard]] auto write_some_at(std::uint64_t offset, CB const& buffers)
337 {
338 43x write_some_at_awaitable<CB> aw(*this, offset, buffers);
339 43x if (!is_open())
340 2x aw.ec_ = make_error_code(std::errc::bad_file_descriptor);
341 43x return aw;
342 }
343
344 /** Cancel pending asynchronous operations. */
345 void cancel() noexcept;
346
347 /** Get the native file descriptor or handle. */
348 native_handle_type native_handle() const noexcept;
349
350 /** Return the file size in bytes.
351
352 @throws std::system_error If the file is not open, or if the
353 underlying size query fails.
354 */
355 std::uint64_t size() const;
356
357 /** Resize the file to @p new_size bytes.
358
359 Failures such as insufficient disk space are reported
360 through the returned error code. A closed file reports
361 `errc::bad_file_descriptor`.
362
363 @param new_size The new file size.
364
365 @return The error code, empty on success.
366 */
367 [[nodiscard]] std::error_code resize(std::uint64_t new_size) noexcept;
368
369 /** Synchronize file data to stable storage.
370
371 Write-back failures such as device I/O errors surface here
372 and are reported through the returned error code. A closed
373 file reports `errc::bad_file_descriptor`.
374
375 @return The error code, empty on success.
376 */
377 [[nodiscard]] std::error_code sync_data() noexcept;
378
379 /** Synchronize file data and metadata to stable storage.
380
381 Write-back failures such as device I/O errors surface here
382 and are reported through the returned error code. A closed
383 file reports `errc::bad_file_descriptor`.
384
385 @return The error code, empty on success.
386 */
387 [[nodiscard]] std::error_code sync_all() noexcept;
388
389 /** Release ownership of the native handle.
390
391 The file object becomes not-open. The caller is
392 responsible for closing the returned handle.
393
394 @return The native file descriptor or handle.
395
396 @throws std::system_error `errc::bad_file_descriptor` if the
397 file is not open.
398 */
399 native_handle_type release();
400
401 /** Adopt an existing native handle.
402
403 Closes any currently open file before adopting.
404 The file object takes ownership of the handle. Handles
405 created elsewhere may be unsuitable for asynchronous I/O;
406 such failures are reported through the returned error code.
407
408 @param handle The native file descriptor or handle.
409
410 @return The error code, empty on success.
411 */
412 [[nodiscard]] std::error_code assign(native_handle_type handle) noexcept;
413
414 protected:
415 /// Construct from a pre-built handle (for native_random_access_file).
416 16x explicit random_access_file(handle h) noexcept : io_object(std::move(h)) {}
417
418 private:
419 1171x inline implementation& get() const noexcept
420 {
421 1171x return *static_cast<implementation*>(h_.get());
422 }
423 };
424
425 } // namespace boost::corosio
426
427 #endif // BOOST_COROSIO_RANDOM_ACCESS_FILE_HPP
428