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) :151 277x 100.0% 100.0% boost::corosio::random_access_file::read_some_at_awaitable<boost::capy::mutable_buffer>::await_ready() const :162 277x 100.0% 100.0% boost::corosio::random_access_file::read_some_at_awaitable<boost::capy::mutable_buffer>::await_resume() const :169 277x 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 275x 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) :194 29x 100.0% 100.0% boost::corosio::random_access_file::write_some_at_awaitable<boost::capy::const_buffer>::await_ready() const :205 29x 100.0% 100.0% boost::corosio::random_access_file::write_some_at_awaitable<boost::capy::const_buffer>::await_resume() const :212 29x 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 27x 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&) :246 2x 100.0% 100.0% boost::corosio::random_access_file::random_access_file(boost::corosio::random_access_file&&) :251 2x 100.0% 100.0% boost::corosio::random_access_file::is_open() const :295 590x 100.0% 100.0% auto boost::corosio::random_access_file::read_some_at<boost::capy::mutable_buffer>(unsigned long, boost::capy::mutable_buffer const&) :314 277x 100.0% 100.0% auto boost::corosio::random_access_file::write_some_at<boost::capy::const_buffer>(unsigned long, boost::capy::const_buffer const&) :332 29x 100.0% 100.0% boost::corosio::random_access_file::random_access_file(boost::corosio::io_object::handle) :412 12x 100.0% 100.0% boost::corosio::random_access_file::get() const :415 1007x 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 277x 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 277x : f_(f)
157 277x , offset_(offset)
158 277x , buffers_(std::move(buffers))
159 {
160 277x }
161
162 277x bool await_ready() const noexcept
163 {
164 // A pre-set ec_ means the initiator failed before
165 // dispatch (e.g. a closed object).
166 277x return static_cast<bool>(ec_);
167 }
168
169 277x [[nodiscard]] capy::io_result<std::size_t> await_resume() const noexcept
170 {
171 277x return {ec_, bytes_};
172 }
173
174 275x auto await_suspend(std::coroutine_handle<> h, capy::io_env const* env)
175 -> std::coroutine_handle<>
176 {
177 275x token_ = env->stop_token;
178 825x return f_.get().read_some_at(
179 825x 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 29x 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 29x : f_(f)
200 29x , offset_(offset)
201 29x , buffers_(std::move(buffers))
202 {
203 29x }
204
205 29x bool await_ready() const noexcept
206 {
207 // A pre-set ec_ means the initiator failed before
208 // dispatch (e.g. a closed object).
209 29x return static_cast<bool>(ec_);
210 }
211
212 29x [[nodiscard]] capy::io_result<std::size_t> await_resume() const noexcept
213 {
214 29x return {ec_, bytes_};
215 }
216
217 27x auto await_suspend(std::coroutine_handle<> h, capy::io_env const* env)
218 -> std::coroutine_handle<>
219 {
220 27x token_ = env->stop_token;
221 81x return f_.get().write_some_at(
222 81x 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 2x explicit random_access_file(Ex const& ex) : random_access_file(ex.context())
247 {
248 2x }
249
250 /** Move constructor. */
251 2x random_access_file(random_access_file&& other) noexcept
252 2x : io_object(std::move(other))
253 {
254 2x }
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 590x bool is_open() const noexcept
296 {
297 #if BOOST_COROSIO_HAS_IOCP && !defined(BOOST_COROSIO_MRDOCS)
298 return h_ && get().native_handle() != ~native_handle_type(0);
299 #else
300 590x 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 277x [[nodiscard]] auto read_some_at(std::uint64_t offset, MB const& buffers)
315 {
316 277x read_some_at_awaitable<MB> aw(*this, offset, buffers);
317 277x if (!is_open())
318 2x aw.ec_ = make_error_code(std::errc::bad_file_descriptor);
319 277x 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 29x [[nodiscard]] auto write_some_at(std::uint64_t offset, CB const& buffers)
333 {
334 29x write_some_at_awaitable<CB> aw(*this, offset, buffers);
335 29x if (!is_open())
336 2x aw.ec_ = make_error_code(std::errc::bad_file_descriptor);
337 29x 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 12x explicit random_access_file(handle h) noexcept : io_object(std::move(h)) {}
413
414 private:
415 1007x inline implementation& get() const noexcept
416 {
417 1007x return *static_cast<implementation*>(h_.get());
418 }
419 };
420
421 } // namespace boost::corosio
422
423 #endif // BOOST_COROSIO_RANDOM_ACCESS_FILE_HPP
424