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_NATIVE_DETAIL_POSIX_POSIX_STREAM_FILE_SERVICE_HPP
11 : #define BOOST_COROSIO_NATIVE_DETAIL_POSIX_POSIX_STREAM_FILE_SERVICE_HPP
12 :
13 : #include <boost/corosio/detail/platform.hpp>
14 :
15 : #if BOOST_COROSIO_POSIX
16 :
17 : #include <boost/corosio/native/detail/posix/posix_stream_file.hpp>
18 : #include <boost/corosio/native/detail/reactor/reactor_scheduler.hpp>
19 : #include <boost/corosio/detail/file_service.hpp>
20 : #include <boost/corosio/detail/thread_pool.hpp>
21 :
22 : #include <mutex>
23 : #include <unordered_map>
24 :
25 : namespace boost::corosio::detail {
26 :
27 : /** Stream file service for POSIX backends.
28 :
29 : Owns all posix_stream_file instances. Thread lifecycle is
30 : managed by the thread_pool service (shared with resolver).
31 : */
32 : class BOOST_COROSIO_DECL posix_stream_file_service final
33 : : public file_service
34 : {
35 : public:
36 HIT 1790 : posix_stream_file_service(
37 : capy::execution_context& ctx, scheduler& sched)
38 3580 : : sched_(&sched)
39 1790 : , pool_(ctx)
40 : {
41 1790 : }
42 :
43 3580 : ~posix_stream_file_service() override = default;
44 :
45 : posix_stream_file_service(posix_stream_file_service const&) = delete;
46 : posix_stream_file_service& operator=(posix_stream_file_service const&) = delete;
47 :
48 114 : io_object::implementation* construct() override
49 : {
50 114 : auto ptr = std::make_shared<posix_stream_file>(*this);
51 114 : auto* impl = ptr.get();
52 :
53 : {
54 114 : std::lock_guard<std::mutex> lock(mutex_);
55 114 : file_list_.push_back(impl);
56 114 : file_ptrs_[impl] = std::move(ptr);
57 114 : }
58 :
59 114 : return impl;
60 114 : }
61 :
62 112 : void destroy(io_object::implementation* p) override
63 : {
64 112 : auto& impl = static_cast<posix_stream_file&>(*p);
65 112 : impl.cancel();
66 112 : impl.close_file();
67 112 : destroy_impl(impl);
68 112 : }
69 :
70 197 : void close(io_object::handle& h) override
71 : {
72 197 : if (h.get())
73 : {
74 197 : auto& impl = static_cast<posix_stream_file&>(*h.get());
75 197 : impl.cancel();
76 197 : impl.close_file();
77 : }
78 197 : }
79 :
80 99 : std::error_code open_file(
81 : stream_file::implementation& impl,
82 : std::filesystem::path const& path,
83 : file_base::flags mode) override
84 : {
85 : // Unavailable in the unsafe tier: the file thread pool completes
86 : // cross-thread, which the lockless scheduler cannot accept.
87 99 : if (sched_->scheduler_locking_disabled())
88 2 : return std::make_error_code(std::errc::operation_not_supported);
89 97 : return static_cast<posix_stream_file&>(impl).open_file(path, mode);
90 : }
91 :
92 1790 : void shutdown() override
93 : {
94 1790 : std::lock_guard<std::mutex> lock(mutex_);
95 1792 : for (auto* impl = file_list_.pop_front(); impl != nullptr;
96 2 : impl = file_list_.pop_front())
97 : {
98 2 : impl->cancel();
99 2 : impl->close_file();
100 : }
101 1790 : file_ptrs_.clear();
102 1790 : }
103 :
104 112 : void destroy_impl(posix_stream_file& impl)
105 : {
106 112 : std::lock_guard<std::mutex> lock(mutex_);
107 112 : file_list_.remove(&impl);
108 112 : file_ptrs_.erase(&impl);
109 112 : }
110 :
111 61 : void post(scheduler_op* op)
112 : {
113 61 : sched_->post(op);
114 61 : }
115 :
116 : void work_started() noexcept
117 : {
118 : sched_->work_started();
119 : }
120 :
121 : void work_finished() noexcept
122 : {
123 : sched_->work_finished();
124 : }
125 :
126 : /** Return the thread pool that runs this service's file work.
127 :
128 : The pool's service is created on first use, so this can fail
129 : where a plain accessor could not. Its workers start later, on
130 : the first post, and a thread the system refuses there is
131 : reported by that post rather than thrown here.
132 :
133 : @throws std::bad_alloc If the service cannot be allocated.
134 :
135 : @return The context's shared blocking-I/O pool.
136 :
137 : @see thread_pool_ref::get
138 : */
139 61 : thread_pool& pool()
140 : {
141 61 : return pool_.get();
142 : }
143 :
144 : private:
145 : scheduler* sched_;
146 : thread_pool_ref pool_;
147 : std::mutex mutex_;
148 : intrusive_list<posix_stream_file> file_list_;
149 : std::unordered_map<posix_stream_file*, std::shared_ptr<posix_stream_file>>
150 : file_ptrs_;
151 : };
152 :
153 : /** Get or create the stream file service for the given context. */
154 : inline posix_stream_file_service&
155 1790 : get_stream_file_service(capy::execution_context& ctx, scheduler& sched)
156 : {
157 1790 : return ctx.make_service<posix_stream_file_service>(sched);
158 : }
159 :
160 : // ---------------------------------------------------------------------------
161 : // posix_stream_file inline implementations (require complete service type)
162 : // ---------------------------------------------------------------------------
163 :
164 : inline std::coroutine_handle<>
165 40 : posix_stream_file::read_some(
166 : std::coroutine_handle<> h,
167 : capy::executor_ref ex,
168 : buffer_param param,
169 : std::stop_token token,
170 : std::error_code* ec,
171 : std::size_t* bytes_out)
172 : {
173 40 : auto& op = read_op_;
174 40 : op.reset();
175 40 : op.is_read = true;
176 :
177 : // Closed-object contract outranks the zero-length no-op.
178 40 : if (fd_ < 0)
179 : {
180 6 : *ec = make_error_code(std::errc::bad_file_descriptor);
181 6 : *bytes_out = 0;
182 6 : op.cont.h = h;
183 6 : return dispatch_coro(ex, op.cont);
184 : }
185 :
186 34 : capy::mutable_buffer bufs[max_buffers];
187 34 : op.iovec_count = static_cast<int>(param.copy_to(bufs, max_buffers));
188 :
189 34 : if (op.iovec_count == 0)
190 : {
191 2 : *ec = {};
192 2 : *bytes_out = 0;
193 2 : op.cont.h = h;
194 2 : return dispatch_coro(ex, op.cont);
195 : }
196 :
197 64 : for (int i = 0; i < op.iovec_count; ++i)
198 : {
199 32 : op.iovecs[i].iov_base = bufs[i].data();
200 32 : op.iovecs[i].iov_len = bufs[i].size();
201 : }
202 :
203 32 : op.h = h;
204 32 : op.ex = ex;
205 32 : op.ec_out = ec;
206 32 : op.bytes_out = bytes_out;
207 32 : op.start(token);
208 :
209 32 : op.ex.on_work_started();
210 :
211 32 : read_pool_op_.file_ = this;
212 32 : read_pool_op_.ref_ = this->shared_from_this();
213 32 : read_pool_op_.func_ = &posix_stream_file::do_read_work;
214 32 : if (auto pec = svc_.pool().post(&read_pool_op_))
215 : {
216 : // The pool is shutting down, or the system refused it a thread.
217 : // Nothing of this read went cross-thread, so it answers here
218 : // like the closed-descriptor and zero-length exits above rather
219 : // than through a completion the scheduler has to carry back.
220 MIS 0 : read_pool_op_.ref_.reset();
221 0 : op.stop_cb.reset();
222 0 : op.ex.on_work_finished();
223 0 : *ec = pec;
224 0 : *bytes_out = 0;
225 0 : op.cont.h = h;
226 0 : return dispatch_coro(ex, op.cont);
227 : }
228 HIT 32 : return std::noop_coroutine();
229 : }
230 :
231 : inline void
232 32 : posix_stream_file::do_read_work(pool_work_item* w) noexcept
233 : {
234 32 : auto* pw = static_cast<pool_op*>(w);
235 32 : auto* self = pw->file_;
236 32 : auto& op = self->read_op_;
237 :
238 32 : if (!op.cancelled.load(std::memory_order_acquire))
239 : {
240 : ssize_t n;
241 : do
242 : {
243 56 : n = ::preadv(self->fd_, op.iovecs, op.iovec_count,
244 28 : static_cast<off_t>(self->offset_));
245 : }
246 28 : while (n < 0 && errno == EINTR);
247 :
248 28 : if (n >= 0)
249 : {
250 21 : op.errn = 0;
251 21 : op.bytes_transferred = static_cast<std::size_t>(n);
252 21 : self->offset_ += static_cast<std::uint64_t>(n);
253 : }
254 : else
255 : {
256 7 : op.errn = errno;
257 7 : op.bytes_transferred = 0;
258 : }
259 : }
260 :
261 32 : op.impl_ptr = std::move(pw->ref_);
262 32 : self->svc_.post(&op);
263 32 : }
264 :
265 : inline std::coroutine_handle<>
266 37 : posix_stream_file::write_some(
267 : std::coroutine_handle<> h,
268 : capy::executor_ref ex,
269 : buffer_param param,
270 : std::stop_token token,
271 : std::error_code* ec,
272 : std::size_t* bytes_out)
273 : {
274 37 : auto& op = write_op_;
275 37 : op.reset();
276 37 : op.is_read = false;
277 :
278 : // Closed-object contract outranks the zero-length no-op.
279 37 : if (fd_ < 0)
280 : {
281 6 : *ec = make_error_code(std::errc::bad_file_descriptor);
282 6 : *bytes_out = 0;
283 6 : op.cont.h = h;
284 6 : return dispatch_coro(ex, op.cont);
285 : }
286 :
287 31 : capy::mutable_buffer bufs[max_buffers];
288 31 : op.iovec_count = static_cast<int>(param.copy_to(bufs, max_buffers));
289 :
290 31 : if (op.iovec_count == 0)
291 : {
292 2 : *ec = {};
293 2 : *bytes_out = 0;
294 2 : op.cont.h = h;
295 2 : return dispatch_coro(ex, op.cont);
296 : }
297 :
298 58 : for (int i = 0; i < op.iovec_count; ++i)
299 : {
300 29 : op.iovecs[i].iov_base = bufs[i].data();
301 29 : op.iovecs[i].iov_len = bufs[i].size();
302 : }
303 :
304 29 : op.h = h;
305 29 : op.ex = ex;
306 29 : op.ec_out = ec;
307 29 : op.bytes_out = bytes_out;
308 29 : op.start(token);
309 :
310 29 : op.ex.on_work_started();
311 :
312 29 : write_pool_op_.file_ = this;
313 29 : write_pool_op_.ref_ = this->shared_from_this();
314 29 : write_pool_op_.func_ = &posix_stream_file::do_write_work;
315 29 : if (auto pec = svc_.pool().post(&write_pool_op_))
316 : {
317 : // The pool is shutting down, or the system refused it a thread.
318 : // Nothing of this write went cross-thread, so it answers here
319 : // like the closed-descriptor and zero-length exits above rather
320 : // than through a completion the scheduler has to carry back.
321 MIS 0 : write_pool_op_.ref_.reset();
322 0 : op.stop_cb.reset();
323 0 : op.ex.on_work_finished();
324 0 : *ec = pec;
325 0 : *bytes_out = 0;
326 0 : op.cont.h = h;
327 0 : return dispatch_coro(ex, op.cont);
328 : }
329 HIT 29 : return std::noop_coroutine();
330 : }
331 :
332 : inline void
333 29 : posix_stream_file::do_write_work(pool_work_item* w) noexcept
334 : {
335 29 : auto* pw = static_cast<pool_op*>(w);
336 29 : auto* self = pw->file_;
337 29 : auto& op = self->write_op_;
338 :
339 29 : if (!op.cancelled.load(std::memory_order_acquire))
340 : {
341 : ssize_t n;
342 : do
343 : {
344 58 : n = ::pwritev(self->fd_, op.iovecs, op.iovec_count,
345 29 : static_cast<off_t>(self->offset_));
346 : }
347 29 : while (n < 0 && errno == EINTR);
348 :
349 29 : if (n >= 0)
350 : {
351 22 : op.errn = 0;
352 22 : op.bytes_transferred = static_cast<std::size_t>(n);
353 22 : self->offset_ += static_cast<std::uint64_t>(n);
354 : }
355 : else
356 : {
357 7 : op.errn = errno;
358 7 : op.bytes_transferred = 0;
359 : }
360 : }
361 :
362 29 : op.impl_ptr = std::move(pw->ref_);
363 29 : self->svc_.post(&op);
364 29 : }
365 :
366 : } // namespace boost::corosio::detail
367 :
368 : #endif // BOOST_COROSIO_POSIX
369 :
370 : #endif // BOOST_COROSIO_NATIVE_DETAIL_POSIX_POSIX_STREAM_FILE_SERVICE_HPP
|