100.00% Lines (46/46) 100.00% Functions (15/15)
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 + if (auto ec = f.open("data.bin", file_base::read_only))
  59 + co_return; // report the error
59   60  
60   char buf[4096]; 61   char buf[4096];
61   auto [ec, n] = co_await f.read_some_at( 62   auto [ec, n] = co_await f.read_some_at(
62   0, capy::mutable_buffer(buf, sizeof(buf))); 63   0, capy::mutable_buffer(buf, sizeof(buf)));
63   @endcode 64   @endcode
64   */ 65   */
65   class BOOST_COROSIO_DECL random_access_file : public io_object 66   class BOOST_COROSIO_DECL random_access_file : public io_object
66   { 67   {
67   public: 68   public:
68   /** Platform-specific random-access file implementation interface. 69   /** Platform-specific random-access file implementation interface.
69   70  
70   Backends derive from this to provide offset-based file I/O. 71   Backends derive from this to provide offset-based file I/O.
71   */ 72   */
72   struct implementation : io_object::implementation 73   struct implementation : io_object::implementation
73   { 74   {
74   /** Initiate a read at the given offset. 75   /** Initiate a read at the given offset.
75   76  
76   @param offset Byte offset into the file. 77   @param offset Byte offset into the file.
77   @param h Coroutine handle to resume on completion. 78   @param h Coroutine handle to resume on completion.
78   @param ex Executor for dispatching the completion. 79   @param ex Executor for dispatching the completion.
79   @param buf The buffer to read into. 80   @param buf The buffer to read into.
80   @param token Stop token for cancellation. 81   @param token Stop token for cancellation.
81   @param ec Output error code. 82   @param ec Output error code.
82   @param bytes_out Output bytes transferred. 83   @param bytes_out Output bytes transferred.
83   @return Coroutine handle to resume immediately. 84   @return Coroutine handle to resume immediately.
84   */ 85   */
85   virtual std::coroutine_handle<> read_some_at( 86   virtual std::coroutine_handle<> read_some_at(
86   std::uint64_t offset, 87   std::uint64_t offset,
87   std::coroutine_handle<> h, 88   std::coroutine_handle<> h,
88   capy::executor_ref ex, 89   capy::executor_ref ex,
89   buffer_param buf, 90   buffer_param buf,
90   std::stop_token token, 91   std::stop_token token,
91   std::error_code* ec, 92   std::error_code* ec,
92   std::size_t* bytes_out) = 0; 93   std::size_t* bytes_out) = 0;
93   94  
94   /** Initiate a write at the given offset. 95   /** Initiate a write at the given offset.
95   96  
96   @param offset Byte offset into the file. 97   @param offset Byte offset into the file.
97   @param h Coroutine handle to resume on completion. 98   @param h Coroutine handle to resume on completion.
98   @param ex Executor for dispatching the completion. 99   @param ex Executor for dispatching the completion.
99   @param buf The buffer to write from. 100   @param buf The buffer to write from.
100   @param token Stop token for cancellation. 101   @param token Stop token for cancellation.
101   @param ec Output error code. 102   @param ec Output error code.
102   @param bytes_out Output bytes transferred. 103   @param bytes_out Output bytes transferred.
103   @return Coroutine handle to resume immediately. 104   @return Coroutine handle to resume immediately.
104   */ 105   */
105   virtual std::coroutine_handle<> write_some_at( 106   virtual std::coroutine_handle<> write_some_at(
106   std::uint64_t offset, 107   std::uint64_t offset,
107   std::coroutine_handle<> h, 108   std::coroutine_handle<> h,
108   capy::executor_ref ex, 109   capy::executor_ref ex,
109   buffer_param buf, 110   buffer_param buf,
110   std::stop_token token, 111   std::stop_token token,
111   std::error_code* ec, 112   std::error_code* ec,
112   std::size_t* bytes_out) = 0; 113   std::size_t* bytes_out) = 0;
113   114  
114   /// Return the platform file descriptor or handle. 115   /// Return the platform file descriptor or handle.
115   virtual native_handle_type native_handle() const noexcept = 0; 116   virtual native_handle_type native_handle() const noexcept = 0;
116   117  
117   /// Cancel pending asynchronous operations. 118   /// Cancel pending asynchronous operations.
118   virtual void cancel() noexcept = 0; 119   virtual void cancel() noexcept = 0;
119   120  
120   /// Return the file size in bytes. 121   /// Return the file size in bytes.
121   virtual std::uint64_t size() const = 0; 122   virtual std::uint64_t size() const = 0;
122   123  
123   /// Resize the file to @p new_size bytes. 124   /// Resize the file to @p new_size bytes.
124 - virtual void resize(std::uint64_t new_size) = 0; 125 + virtual std::error_code resize(std::uint64_t new_size) noexcept = 0;
125   126  
126   /// Synchronize file data to stable storage. 127   /// Synchronize file data to stable storage.
127 - virtual void sync_data() = 0; 128 + virtual std::error_code sync_data() noexcept = 0;
128   129  
129   /// Synchronize file data and metadata to stable storage. 130   /// Synchronize file data and metadata to stable storage.
130 - virtual void sync_all() = 0; 131 + virtual std::error_code sync_all() noexcept = 0;
131   132  
132   /// Release ownership of the native handle. 133   /// Release ownership of the native handle.
133   virtual native_handle_type release() = 0; 134   virtual native_handle_type release() = 0;
134   135  
135   /// Adopt an existing native handle. 136   /// Adopt an existing native handle.
136 - virtual void assign(native_handle_type handle) = 0; 137 + virtual std::error_code assign(native_handle_type handle) noexcept = 0;
137   }; 138   };
138   139  
139   /** Awaitable for async read-at operations. */ 140   /** Awaitable for async read-at operations. */
140   template<class MutableBufferSequence> 141   template<class MutableBufferSequence>
141   struct read_some_at_awaitable 142   struct read_some_at_awaitable
142   { 143   {
143   random_access_file& f_; 144   random_access_file& f_;
144   std::uint64_t offset_; 145   std::uint64_t offset_;
145   MutableBufferSequence buffers_; 146   MutableBufferSequence buffers_;
146   std::stop_token token_; 147   std::stop_token token_;
147   mutable std::error_code ec_; 148   mutable std::error_code ec_;
148   mutable std::size_t bytes_ = 0; 149   mutable std::size_t bytes_ = 0;
149   150  
HITCBC 150   271 read_some_at_awaitable( 151   277 read_some_at_awaitable(
151   random_access_file& f, 152   random_access_file& f,
152   std::uint64_t offset, 153   std::uint64_t offset,
153   MutableBufferSequence buffers) 154   MutableBufferSequence buffers)
154   noexcept(std::is_nothrow_move_constructible_v<MutableBufferSequence>) 155   noexcept(std::is_nothrow_move_constructible_v<MutableBufferSequence>)
HITCBC 155   271 : f_(f) 156   277 : f_(f)
HITCBC 156   271 , offset_(offset) 157   277 , offset_(offset)
HITCBC 157   271 , buffers_(std::move(buffers)) 158   277 , buffers_(std::move(buffers))
158   { 159   {
HITCBC 159   271 } 160   277 }
160   161  
HITCBC 161   271 bool await_ready() const noexcept 162   277 bool await_ready() const noexcept
162   { 163   {
ECB 163 - 271 return false; 164 + // A pre-set ec_ means the initiator failed before
  165 + // dispatch (e.g. a closed object).
HITGNC   166 + 277 return static_cast<bool>(ec_);
164   } 167   }
165   168  
HITCBC 166   271 [[nodiscard]] capy::io_result<std::size_t> await_resume() const noexcept 169   277 [[nodiscard]] capy::io_result<std::size_t> await_resume() const noexcept
167   { 170   {
HITCBC 168   271 return {ec_, bytes_}; 171   277 return {ec_, bytes_};
169   } 172   }
170   173  
HITCBC 171   271 auto await_suspend(std::coroutine_handle<> h, capy::io_env const* env) 174   275 auto await_suspend(std::coroutine_handle<> h, capy::io_env const* env)
172   -> std::coroutine_handle<> 175   -> std::coroutine_handle<>
173   { 176   {
HITCBC 174   271 token_ = env->stop_token; 177   275 token_ = env->stop_token;
HITCBC 175   813 return f_.get().read_some_at( 178   825 return f_.get().read_some_at(
HITCBC 176   813 offset_, h, env->executor, buffers_, token_, &ec_, &bytes_); 179   825 offset_, h, env->executor, buffers_, token_, &ec_, &bytes_);
177   } 180   }
178   }; 181   };
179   182  
180   /** Awaitable for async write-at operations. */ 183   /** Awaitable for async write-at operations. */
181   template<class ConstBufferSequence> 184   template<class ConstBufferSequence>
182   struct write_some_at_awaitable 185   struct write_some_at_awaitable
183   { 186   {
184   random_access_file& f_; 187   random_access_file& f_;
185   std::uint64_t offset_; 188   std::uint64_t offset_;
186   ConstBufferSequence buffers_; 189   ConstBufferSequence buffers_;
187   std::stop_token token_; 190   std::stop_token token_;
188   mutable std::error_code ec_; 191   mutable std::error_code ec_;
189   mutable std::size_t bytes_ = 0; 192   mutable std::size_t bytes_ = 0;
190   193  
HITCBC 191   25 write_some_at_awaitable( 194   29 write_some_at_awaitable(
192   random_access_file& f, 195   random_access_file& f,
193   std::uint64_t offset, 196   std::uint64_t offset,
194   ConstBufferSequence buffers) 197   ConstBufferSequence buffers)
195   noexcept(std::is_nothrow_move_constructible_v<ConstBufferSequence>) 198   noexcept(std::is_nothrow_move_constructible_v<ConstBufferSequence>)
HITCBC 196   25 : f_(f) 199   29 : f_(f)
HITCBC 197   25 , offset_(offset) 200   29 , offset_(offset)
HITCBC 198   25 , buffers_(std::move(buffers)) 201   29 , buffers_(std::move(buffers))
199   { 202   {
HITCBC 200   25 } 203   29 }
201   204  
HITCBC 202   25 bool await_ready() const noexcept 205   29 bool await_ready() const noexcept
203   { 206   {
ECB 204 - 25 return false; 207 + // A pre-set ec_ means the initiator failed before
  208 + // dispatch (e.g. a closed object).
HITGNC   209 + 29 return static_cast<bool>(ec_);
205   } 210   }
206   211  
HITCBC 207   25 [[nodiscard]] capy::io_result<std::size_t> await_resume() const noexcept 212   29 [[nodiscard]] capy::io_result<std::size_t> await_resume() const noexcept
208   { 213   {
HITCBC 209   25 return {ec_, bytes_}; 214   29 return {ec_, bytes_};
210   } 215   }
211   216  
HITCBC 212   25 auto await_suspend(std::coroutine_handle<> h, capy::io_env const* env) 217   27 auto await_suspend(std::coroutine_handle<> h, capy::io_env const* env)
213   -> std::coroutine_handle<> 218   -> std::coroutine_handle<>
214   { 219   {
HITCBC 215   25 token_ = env->stop_token; 220   27 token_ = env->stop_token;
HITCBC 216   75 return f_.get().write_some_at( 221   81 return f_.get().write_some_at(
HITCBC 217   75 offset_, h, env->executor, buffers_, token_, &ec_, &bytes_); 222   81 offset_, h, env->executor, buffers_, token_, &ec_, &bytes_);
218   } 223   }
219   }; 224   };
220   225  
221   public: 226   public:
222   /** Destructor. 227   /** Destructor.
223   228  
224   Closes the file if open, cancelling any pending operations. 229   Closes the file if open, cancelling any pending operations.
225   */ 230   */
226   ~random_access_file() override; 231   ~random_access_file() override;
227   232  
228   /** Construct from an execution context. 233   /** Construct from an execution context.
229   234  
230   @param ctx The execution context that will own this file. 235   @param ctx The execution context that will own this file.
231   */ 236   */
232   explicit random_access_file(capy::execution_context& ctx); 237   explicit random_access_file(capy::execution_context& ctx);
233   238  
234   /** Construct from an executor. 239   /** Construct from an executor.
235   240  
236   @param ex The executor whose context will own this file. 241   @param ex The executor whose context will own this file.
237   */ 242   */
238   template<class Ex> 243   template<class Ex>
239   requires(!std::same_as<std::remove_cvref_t<Ex>, random_access_file>) && 244   requires(!std::same_as<std::remove_cvref_t<Ex>, random_access_file>) &&
240   capy::Executor<Ex> 245   capy::Executor<Ex>
HITCBC 241   2 explicit random_access_file(Ex const& ex) : random_access_file(ex.context()) 246   2 explicit random_access_file(Ex const& ex) : random_access_file(ex.context())
242   { 247   {
HITCBC 243   2 } 248   2 }
244   249  
245   /** Move constructor. */ 250   /** Move constructor. */
HITCBC 246   2 random_access_file(random_access_file&& other) noexcept 251   2 random_access_file(random_access_file&& other) noexcept
HITCBC 247   2 : io_object(std::move(other)) 252   2 : io_object(std::move(other))
248   { 253   {
HITCBC 249   2 } 254   2 }
250   255  
251   /** Move assignment operator. */ 256   /** Move assignment operator. */
252   random_access_file& operator=(random_access_file&& other) noexcept 257   random_access_file& operator=(random_access_file&& other) noexcept
253   { 258   {
254   if (this != &other) 259   if (this != &other)
255   { 260   {
256   close(); 261   close();
257   h_ = std::move(other.h_); 262   h_ = std::move(other.h_);
258   } 263   }
259   return *this; 264   return *this;
260   } 265   }
261   266  
262   random_access_file(random_access_file const&) = delete; 267   random_access_file(random_access_file const&) = delete;
263   random_access_file& operator=(random_access_file const&) = delete; 268   random_access_file& operator=(random_access_file const&) = delete;
264   269  
265   /** Open a file. 270   /** Open a file.
266   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 +
267   @param path The filesystem path to open. 277   @param path The filesystem path to open.
268   @param mode Bitmask of @ref file_base::flags specifying 278   @param mode Bitmask of @ref file_base::flags specifying
269   access mode and creation behavior. 279   access mode and creation behavior.
270   280  
271 - @throws std::system_error on failure. 281 + @return The error code, empty on success.
272   */ 282   */
273 - void open( 283 + [[nodiscard]] std::error_code open(
274   std::filesystem::path const& path, 284   std::filesystem::path const& path,
275 - file_base::flags mode = file_base::read_only); 285 + file_base::flags mode = file_base::read_only) noexcept;
276   286  
277   /** Close the file. 287   /** Close the file.
278   288  
279   Releases file resources. Any pending operations complete 289   Releases file resources. Any pending operations complete
280   with `errc::operation_canceled`. 290   with `errc::operation_canceled`.
281   */ 291   */
282 - void close(); 292 + void close() noexcept;
283   293  
284   /** Check if the file is open. */ 294   /** Check if the file is open. */
HITCBC 285   532 bool is_open() const noexcept 295   590 bool is_open() const noexcept
286   { 296   {
287   #if BOOST_COROSIO_HAS_IOCP && !defined(BOOST_COROSIO_MRDOCS) 297   #if BOOST_COROSIO_HAS_IOCP && !defined(BOOST_COROSIO_MRDOCS)
288   return h_ && get().native_handle() != ~native_handle_type(0); 298   return h_ && get().native_handle() != ~native_handle_type(0);
289   #else 299   #else
HITCBC 290   532 return h_ && get().native_handle() >= 0; 300   590 return h_ && get().native_handle() >= 0;
291   #endif 301   #endif
292   } 302   }
293   303  
294   /** Read data at the given offset. 304   /** Read data at the given offset.
295   305  
296   @param offset Byte offset into the file. 306   @param offset Byte offset into the file.
297   @param buffers The buffer sequence to read into. 307   @param buffers The buffer sequence to read into.
298   308  
299   @return An awaitable yielding `(error_code, std::size_t)`. 309   @return An awaitable yielding `(error_code, std::size_t)`.
300   310  
301 - @throws std::logic_error if the file is not open. 311 + A closed file reports `errc::bad_file_descriptor`.
302   */ 312   */
303   template<capy::MutableBufferSequence MB> 313   template<capy::MutableBufferSequence MB>
HITCBC 304 - 271 auto read_some_at(std::uint64_t offset, MB const& buffers) 314 + 277 [[nodiscard]] auto read_some_at(std::uint64_t offset, MB const& buffers)
305   { 315   {
HITGNC   316 + 277 read_some_at_awaitable<MB> aw(*this, offset, buffers);
HITCBC 306   271 if (!is_open()) 317   277 if (!is_open())
HITGBC 307 - detail::throw_logic_error("read_some_at: file not open"); 318 + 2 aw.ec_ = make_error_code(std::errc::bad_file_descriptor);
HITCBC 308 - 271 return read_some_at_awaitable<MB>(*this, offset, buffers); 319 + 277 return aw;
309   } 320   }
310   321  
311   /** Write data at the given offset. 322   /** Write data at the given offset.
312   323  
313   @param offset Byte offset into the file. 324   @param offset Byte offset into the file.
314   @param buffers The buffer sequence to write from. 325   @param buffers The buffer sequence to write from.
315   326  
316   @return An awaitable yielding `(error_code, std::size_t)`. 327   @return An awaitable yielding `(error_code, std::size_t)`.
317   328  
318 - @throws std::logic_error if the file is not open. 329 + A closed file reports `errc::bad_file_descriptor`.
319   */ 330   */
320   template<capy::ConstBufferSequence CB> 331   template<capy::ConstBufferSequence CB>
HITCBC 321 - 25 auto write_some_at(std::uint64_t offset, CB const& buffers) 332 + 29 [[nodiscard]] auto write_some_at(std::uint64_t offset, CB const& buffers)
322   { 333   {
HITGNC   334 + 29 write_some_at_awaitable<CB> aw(*this, offset, buffers);
HITCBC 323   25 if (!is_open()) 335   29 if (!is_open())
HITGBC 324 - detail::throw_logic_error("write_some_at: file not open"); 336 + 2 aw.ec_ = make_error_code(std::errc::bad_file_descriptor);
HITCBC 325 - 25 return write_some_at_awaitable<CB>(*this, offset, buffers); 337 + 29 return aw;
326   } 338   }
327   339  
328   /** Cancel pending asynchronous operations. */ 340   /** Cancel pending asynchronous operations. */
329 - void cancel(); 341 + void cancel() noexcept;
330   342  
331   /** Get the native file descriptor or handle. */ 343   /** Get the native file descriptor or handle. */
332   native_handle_type native_handle() const noexcept; 344   native_handle_type native_handle() const noexcept;
333   345  
334 - /** Return the file size in bytes. */ 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 + */
335   std::uint64_t size() const; 351   std::uint64_t size() const;
336   352  
337 - /** Resize the file. */ 353 + /** Resize the file to @p new_size bytes.
338 - void resize(std::uint64_t new_size);  
339   354  
340 - /** Synchronize file data to stable storage. */ 355 + Failures such as insufficient disk space are reported
341 - void sync_data(); 356 + through the returned error code. A closed file reports
  357 + `errc::bad_file_descriptor`.
342   358  
343 - /** Synchronize file data and metadata to stable storage. */ 359 + @param new_size The new file size.
344 - void sync_all(); 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;
345   384  
346   /** Release ownership of the native handle. 385   /** Release ownership of the native handle.
347   386  
348   The file object becomes not-open. The caller is 387   The file object becomes not-open. The caller is
349   responsible for closing the returned handle. 388   responsible for closing the returned handle.
350   389  
351   @return The native file descriptor or handle. 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.
352   */ 394   */
353   native_handle_type release(); 395   native_handle_type release();
354   396  
355   /** Adopt an existing native handle. 397   /** Adopt an existing native handle.
356   398  
357   Closes any currently open file before adopting. 399   Closes any currently open file before adopting.
358 - The file object takes ownership of the handle. 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.
359   403  
360   @param handle The native file descriptor or handle. 404   @param handle The native file descriptor or handle.
  405 +
  406 + @return The error code, empty on success.
361   */ 407   */
362 - void assign(native_handle_type handle); 408 + [[nodiscard]] std::error_code assign(native_handle_type handle) noexcept;
363   409  
364   protected: 410   protected:
365   /// Construct from a pre-built handle (for native_random_access_file). 411   /// Construct from a pre-built handle (for native_random_access_file).
HITCBC 366   10 explicit random_access_file(handle h) noexcept : io_object(std::move(h)) {} 412   12 explicit random_access_file(handle h) noexcept : io_object(std::move(h)) {}
367   413  
368   private: 414   private:
HITCBC 369   919 inline implementation& get() const noexcept 415   1007 inline implementation& get() const noexcept
370   { 416   {
HITCBC 371   919 return *static_cast<implementation*>(h_.get()); 417   1007 return *static_cast<implementation*>(h_.get());
372   } 418   }
373   }; 419   };
374   420  
375   } // namespace boost::corosio 421   } // namespace boost::corosio
376   422  
377   #endif // BOOST_COROSIO_RANDOM_ACCESS_FILE_HPP 423   #endif // BOOST_COROSIO_RANDOM_ACCESS_FILE_HPP