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