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