TLA Line data 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 : @code
56 : io_context ioc;
57 : random_access_file f(ioc);
58 : f.open("data.bin", file_base::read_only);
59 :
60 : char buf[4096];
61 : auto [ec, n] = co_await f.read_some_at(
62 : 0, capy::mutable_buffer(buf, sizeof(buf)));
63 : @endcode
64 : */
65 : class BOOST_COROSIO_DECL random_access_file : public io_object
66 : {
67 : public:
68 : /** Platform-specific random-access file implementation interface.
69 :
70 : Backends derive from this to provide offset-based file I/O.
71 : */
72 : struct implementation : io_object::implementation
73 : {
74 : /** Initiate a read at the given offset.
75 :
76 : @param offset Byte offset into the file.
77 : @param h Coroutine handle to resume on completion.
78 : @param ex Executor for dispatching the completion.
79 : @param buf The buffer to read into.
80 : @param token Stop token for cancellation.
81 : @param ec Output error code.
82 : @param bytes_out Output bytes transferred.
83 : @return Coroutine handle to resume immediately.
84 : */
85 : virtual std::coroutine_handle<> read_some_at(
86 : std::uint64_t offset,
87 : std::coroutine_handle<> h,
88 : capy::executor_ref ex,
89 : buffer_param buf,
90 : std::stop_token token,
91 : std::error_code* ec,
92 : std::size_t* bytes_out) = 0;
93 :
94 : /** Initiate a write at the given offset.
95 :
96 : @param offset Byte offset into the file.
97 : @param h Coroutine handle to resume on completion.
98 : @param ex Executor for dispatching the completion.
99 : @param buf The buffer to write from.
100 : @param token Stop token for cancellation.
101 : @param ec Output error code.
102 : @param bytes_out Output bytes transferred.
103 : @return Coroutine handle to resume immediately.
104 : */
105 : virtual std::coroutine_handle<> write_some_at(
106 : std::uint64_t offset,
107 : std::coroutine_handle<> h,
108 : capy::executor_ref ex,
109 : buffer_param buf,
110 : std::stop_token token,
111 : std::error_code* ec,
112 : std::size_t* bytes_out) = 0;
113 :
114 : /// Return the platform file descriptor or handle.
115 : virtual native_handle_type native_handle() const noexcept = 0;
116 :
117 : /// Cancel pending asynchronous operations.
118 : virtual void cancel() noexcept = 0;
119 :
120 : /// Return the file size in bytes.
121 : virtual std::uint64_t size() const = 0;
122 :
123 : /// Resize the file to @p new_size bytes.
124 : virtual void resize(std::uint64_t new_size) = 0;
125 :
126 : /// Synchronize file data to stable storage.
127 : virtual void sync_data() = 0;
128 :
129 : /// Synchronize file data and metadata to stable storage.
130 : virtual void sync_all() = 0;
131 :
132 : /// Release ownership of the native handle.
133 : virtual native_handle_type release() = 0;
134 :
135 : /// Adopt an existing native handle.
136 : virtual void assign(native_handle_type handle) = 0;
137 : };
138 :
139 : /** Awaitable for async read-at operations. */
140 : template<class MutableBufferSequence>
141 : struct read_some_at_awaitable
142 : {
143 : random_access_file& f_;
144 : std::uint64_t offset_;
145 : MutableBufferSequence buffers_;
146 : std::stop_token token_;
147 : mutable std::error_code ec_;
148 : mutable std::size_t bytes_ = 0;
149 :
150 HIT 116 : read_some_at_awaitable(
151 : random_access_file& f,
152 : std::uint64_t offset,
153 : MutableBufferSequence buffers)
154 : noexcept(std::is_nothrow_move_constructible_v<MutableBufferSequence>)
155 116 : : f_(f)
156 116 : , offset_(offset)
157 116 : , buffers_(std::move(buffers))
158 : {
159 116 : }
160 :
161 116 : bool await_ready() const noexcept
162 : {
163 116 : return false;
164 : }
165 :
166 116 : capy::io_result<std::size_t> await_resume() const noexcept
167 : {
168 116 : return {ec_, bytes_};
169 : }
170 :
171 116 : auto await_suspend(std::coroutine_handle<> h, capy::io_env const* env)
172 : -> std::coroutine_handle<>
173 : {
174 116 : token_ = env->stop_token;
175 348 : return f_.get().read_some_at(
176 348 : offset_, h, env->executor, buffers_, token_, &ec_, &bytes_);
177 : }
178 : };
179 :
180 : /** Awaitable for async write-at operations. */
181 : template<class ConstBufferSequence>
182 : struct write_some_at_awaitable
183 : {
184 : random_access_file& f_;
185 : std::uint64_t offset_;
186 : ConstBufferSequence buffers_;
187 : std::stop_token token_;
188 : mutable std::error_code ec_;
189 : mutable std::size_t bytes_ = 0;
190 :
191 10 : write_some_at_awaitable(
192 : random_access_file& f,
193 : std::uint64_t offset,
194 : ConstBufferSequence buffers)
195 : noexcept(std::is_nothrow_move_constructible_v<ConstBufferSequence>)
196 10 : : f_(f)
197 10 : , offset_(offset)
198 10 : , buffers_(std::move(buffers))
199 : {
200 10 : }
201 :
202 10 : bool await_ready() const noexcept
203 : {
204 10 : return false;
205 : }
206 :
207 10 : capy::io_result<std::size_t> await_resume() const noexcept
208 : {
209 10 : return {ec_, bytes_};
210 : }
211 :
212 10 : auto await_suspend(std::coroutine_handle<> h, capy::io_env const* env)
213 : -> std::coroutine_handle<>
214 : {
215 10 : token_ = env->stop_token;
216 30 : return f_.get().write_some_at(
217 30 : 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 1 : explicit random_access_file(Ex const& ex) : random_access_file(ex.context())
242 : {
243 1 : }
244 :
245 : /** Move constructor. */
246 1 : random_access_file(random_access_file&& other) noexcept
247 1 : : io_object(std::move(other))
248 : {
249 1 : }
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 : @param path The filesystem path to open.
268 : @param mode Bitmask of @ref file_base::flags specifying
269 : access mode and creation behavior.
270 :
271 : @throws std::system_error on failure.
272 : */
273 : void open(
274 : std::filesystem::path const& path,
275 : file_base::flags mode = file_base::read_only);
276 :
277 : /** Close the file.
278 :
279 : Releases file resources. Any pending operations complete
280 : with `errc::operation_canceled`.
281 : */
282 : void close();
283 :
284 : /** Check if the file is open. */
285 195 : bool is_open() const noexcept
286 : {
287 : #if BOOST_COROSIO_HAS_IOCP && !defined(BOOST_COROSIO_MRDOCS)
288 : return h_ && get().native_handle() != ~native_handle_type(0);
289 : #else
290 195 : return h_ && get().native_handle() >= 0;
291 : #endif
292 : }
293 :
294 : /** Read data at the given offset.
295 :
296 : @param offset Byte offset into the file.
297 : @param buffers The buffer sequence to read into.
298 :
299 : @return An awaitable yielding `(error_code, std::size_t)`.
300 :
301 : @throws std::logic_error if the file is not open.
302 : */
303 : template<capy::MutableBufferSequence MB>
304 116 : auto read_some_at(std::uint64_t offset, MB const& buffers)
305 : {
306 116 : if (!is_open())
307 MIS 0 : detail::throw_logic_error("read_some_at: file not open");
308 HIT 116 : return read_some_at_awaitable<MB>(*this, offset, buffers);
309 : }
310 :
311 : /** Write data at the given offset.
312 :
313 : @param offset Byte offset into the file.
314 : @param buffers The buffer sequence to write from.
315 :
316 : @return An awaitable yielding `(error_code, std::size_t)`.
317 :
318 : @throws std::logic_error if the file is not open.
319 : */
320 : template<capy::ConstBufferSequence CB>
321 10 : auto write_some_at(std::uint64_t offset, CB const& buffers)
322 : {
323 10 : if (!is_open())
324 MIS 0 : detail::throw_logic_error("write_some_at: file not open");
325 HIT 10 : return write_some_at_awaitable<CB>(*this, offset, buffers);
326 : }
327 :
328 : /** Cancel pending asynchronous operations. */
329 : void cancel();
330 :
331 : /** Get the native file descriptor or handle. */
332 : native_handle_type native_handle() const noexcept;
333 :
334 : /** Return the file size in bytes. */
335 : std::uint64_t size() const;
336 :
337 : /** Resize the file. */
338 : void resize(std::uint64_t new_size);
339 :
340 : /** Synchronize file data to stable storage. */
341 : void sync_data();
342 :
343 : /** Synchronize file data and metadata to stable storage. */
344 : void sync_all();
345 :
346 : /** Release ownership of the native handle.
347 :
348 : The file object becomes not-open. The caller is
349 : responsible for closing the returned handle.
350 :
351 : @return The native file descriptor or handle.
352 : */
353 : native_handle_type release();
354 :
355 : /** Adopt an existing native handle.
356 :
357 : Closes any currently open file before adopting.
358 : The file object takes ownership of the handle.
359 :
360 : @param handle The native file descriptor or handle.
361 : */
362 : void assign(native_handle_type handle);
363 :
364 : private:
365 348 : inline implementation& get() const noexcept
366 : {
367 348 : return *static_cast<implementation*>(h_.get());
368 : }
369 : };
370 :
371 : } // namespace boost::corosio
372 :
373 : #endif // BOOST_COROSIO_RANDOM_ACCESS_FILE_HPP
|