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