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_REACTOR_REACTOR_DESCRIPTOR_SERVICE_HPP
11 : #define BOOST_COROSIO_NATIVE_DETAIL_REACTOR_REACTOR_DESCRIPTOR_SERVICE_HPP
12 :
13 : #include <boost/corosio/detail/platform.hpp>
14 :
15 : #if BOOST_COROSIO_POSIX
16 :
17 : #include <boost/corosio/detail/descriptor_service.hpp>
18 : #include <boost/corosio/detail/scheduler_op.hpp>
19 : #include <boost/corosio/native/detail/validate_fd.hpp>
20 : #include <boost/corosio/native/detail/reactor/reactor_descriptor.hpp>
21 : #include <boost/corosio/native/detail/reactor/reactor_service_state.hpp>
22 : #include <boost/capy/ex/execution_context.hpp>
23 :
24 : #include <memory>
25 : #include <mutex>
26 : #include <system_error>
27 :
28 : /* Reactor-backed descriptor_service.
29 :
30 : assign_descriptor is the validate-before-mutate core the public
31 : assign() contract rests on, modelled on do_assign_fd in
32 : reactor_service_finals.hpp.
33 :
34 : PARALLEL COPY: construct, destroy, close and shutdown here mirror the
35 : same four members of reactor_socket_service.hpp, which this cannot
36 : reuse because it calls close_socket() by name. A fix to the service
37 : lifecycle -- the construct/destroy bookkeeping under state_->mutex_,
38 : or shutdown's deliberate retention of impl_ptrs_ so impls outlive the
39 : scheduler's drain -- belongs in both files.
40 : */
41 :
42 : namespace boost::corosio::detail {
43 :
44 : /** CRTP base for reactor-backed descriptor services.
45 :
46 : @tparam Derived The named final service type (CRTP self).
47 : @tparam Traits Backend traits (epoll_traits, kqueue_traits, ...).
48 : @tparam DescFinal The named final descriptor impl type.
49 : */
50 : template<class Derived, class Traits, class DescFinal>
51 : class reactor_descriptor_service : public descriptor_service
52 : {
53 : using scheduler_type = typename Traits::scheduler_type;
54 : using state_type = reactor_service_state<scheduler_type, DescFinal>;
55 :
56 : friend Derived;
57 :
58 : protected:
59 : // NOLINTNEXTLINE(bugprone-crtp-constructor-accessibility)
60 HIT 57 : explicit reactor_descriptor_service(capy::execution_context& ctx)
61 57 : : state_(
62 : std::make_unique<state_type>(
63 57 : ctx.template use_service<scheduler_type>()))
64 : {
65 57 : }
66 :
67 : public:
68 : /// True when a parked write-direction op must wake the reactor.
69 : static constexpr bool needs_write_notification =
70 : Traits::needs_write_notification;
71 :
72 57 : ~reactor_descriptor_service() override = default;
73 :
74 : std::error_code assign_descriptor(
75 : posix_descriptor::implementation& impl, native_handle_type fd) override;
76 :
77 57 : void shutdown() override
78 : {
79 57 : std::lock_guard lock(state_->mutex_);
80 :
81 57 : while (auto* impl = state_->impl_list_.pop_front())
82 MIS 0 : impl->close_descriptor();
83 :
84 : // Don't clear impl_ptrs_ here: the scheduler shuts down after us
85 : // and drains completed_ops_, so every impl must outlive that.
86 HIT 57 : }
87 :
88 59 : io_object::implementation* construct() override
89 : {
90 59 : auto impl = std::make_shared<DescFinal>(static_cast<Derived&>(*this));
91 59 : auto* raw = impl.get();
92 :
93 : {
94 59 : std::lock_guard lock(state_->mutex_);
95 59 : state_->impl_ptrs_.emplace(raw, std::move(impl));
96 59 : state_->impl_list_.push_back(raw);
97 59 : }
98 :
99 59 : return raw;
100 59 : }
101 :
102 59 : void destroy(io_object::implementation* impl) override
103 : {
104 59 : auto* typed = static_cast<DescFinal*>(impl);
105 59 : typed->close_descriptor();
106 59 : std::lock_guard lock(state_->mutex_);
107 59 : state_->impl_list_.remove(typed);
108 59 : state_->impl_ptrs_.erase(typed);
109 59 : }
110 :
111 102 : void close(io_object::handle& h) override
112 : {
113 102 : static_cast<DescFinal*>(h.get())->close_descriptor();
114 102 : }
115 :
116 125 : scheduler_type& scheduler() const noexcept
117 : {
118 125 : return state_->sched_;
119 : }
120 :
121 30 : void post(scheduler_op* op)
122 : {
123 30 : state_->sched_.post(op);
124 30 : }
125 :
126 16 : void work_started() noexcept
127 : {
128 16 : state_->sched_.work_started();
129 16 : }
130 :
131 5 : void work_finished() noexcept
132 : {
133 5 : state_->sched_.work_finished();
134 5 : }
135 :
136 : protected:
137 : std::unique_ptr<state_type> state_;
138 :
139 : private:
140 : reactor_descriptor_service(reactor_descriptor_service const&) = delete;
141 : reactor_descriptor_service&
142 : operator=(reactor_descriptor_service const&) = delete;
143 : };
144 :
145 : template<class Derived, class Traits, class DescFinal>
146 : std::error_code
147 55 : reactor_descriptor_service<Derived, Traits, DescFinal>::assign_descriptor(
148 : posix_descriptor::implementation& impl_base, native_handle_type fd)
149 : {
150 55 : auto* impl = static_cast<DescFinal*>(&impl_base);
151 :
152 : // fd >= 0 guard: an unset impl reports native_handle() == -1, and a
153 : // caller-supplied -1 must fail as a bad fd, not a self-assign.
154 55 : if (fd >= 0 && fd == impl->native_handle())
155 2 : return std::make_error_code(std::errc::invalid_argument);
156 :
157 : // Validate before touching the held descriptor: a failed assign
158 : // must leave the object unchanged and the caller owning the fd.
159 53 : if (auto ec = validate_descriptor_fd(fd))
160 6 : return ec;
161 :
162 47 : impl->close_descriptor();
163 :
164 47 : return impl->init_and_register(fd);
165 : }
166 :
167 : } // namespace boost::corosio::detail
168 :
169 : #endif // BOOST_COROSIO_POSIX
170 :
171 : #endif // BOOST_COROSIO_NATIVE_DETAIL_REACTOR_REACTOR_DESCRIPTOR_SERVICE_HPP
|