include/boost/corosio/random_access_file.hpp

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