include/boost/corosio/random_access_file.hpp

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