src/corosio/src/detail/epoll/sockets.hpp

100.0% Lines (17/17) 100.0% Functions (7/7)
src/corosio/src/detail/epoll/sockets.hpp
Line Hits Source Code
1 //
2 // Copyright (c) 2026 Steve Gerbino
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_DETAIL_EPOLL_SOCKETS_HPP
11 #define BOOST_COROSIO_DETAIL_EPOLL_SOCKETS_HPP
12
13 #include <boost/corosio/detail/platform.hpp>
14
15 #if BOOST_COROSIO_HAS_EPOLL
16
17 #include <boost/corosio/detail/config.hpp>
18 #include <boost/corosio/tcp_socket.hpp>
19 #include <boost/capy/ex/executor_ref.hpp>
20 #include <boost/capy/ex/execution_context.hpp>
21 #include "src/detail/intrusive.hpp"
22 #include "src/detail/socket_service.hpp"
23
24 #include "src/detail/epoll/op.hpp"
25 #include "src/detail/epoll/scheduler.hpp"
26
27 #include <coroutine>
28 #include <memory>
29 #include <mutex>
30 #include <unordered_map>
31
32 /*
33 epoll Socket Implementation
34 ===========================
35
36 Each I/O operation follows the same pattern:
37 1. Try the syscall immediately (non-blocking socket)
38 2. If it succeeds or fails with a real error, post to completion queue
39 3. If EAGAIN/EWOULDBLOCK, register with epoll and wait
40
41 This "try first" approach avoids unnecessary epoll round-trips for
42 operations that can complete immediately (common for small reads/writes
43 on fast local connections).
44
45 One-Shot Registration
46 ---------------------
47 We use one-shot epoll registration: each operation registers, waits for
48 one event, then unregisters. This simplifies the state machine since we
49 don't need to track whether an fd is currently registered or handle
50 re-arming. The tradeoff is slightly more epoll_ctl calls, but the
51 simplicity is worth it.
52
53 Cancellation
54 ------------
55 See op.hpp for the completion/cancellation race handling via the
56 `registered` atomic. cancel() must complete pending operations (post
57 them with cancelled flag) so coroutines waiting on them can resume.
58 close_socket() calls cancel() first to ensure this.
59
60 Impl Lifetime with shared_ptr
61 -----------------------------
62 Socket impls use enable_shared_from_this. The service owns impls via
63 shared_ptr maps (socket_ptrs_) keyed by raw pointer for O(1) lookup and
64 removal. When a user calls close(), we call cancel() which posts pending
65 ops to the scheduler.
66
67 CRITICAL: The posted ops must keep the impl alive until they complete.
68 Otherwise the scheduler would process a freed op (use-after-free). The
69 cancel() method captures shared_from_this() into op.impl_ptr before
70 posting. When the op completes, impl_ptr is cleared, allowing the impl
71 to be destroyed if no other references exist.
72
73 Service Ownership
74 -----------------
75 epoll_socket_service owns all socket impls. destroy_impl() removes the
76 shared_ptr from the map, but the impl may survive if ops still hold
77 impl_ptr refs. shutdown() closes all sockets and clears the map; any
78 in-flight ops will complete and release their refs.
79 */
80
81 namespace boost::corosio::detail {
82
83 class epoll_socket_service;
84 class epoll_socket_impl;
85
86 /// Socket implementation for epoll backend.
87 class epoll_socket_impl final
88 : public tcp_socket::implementation
89 , public std::enable_shared_from_this<epoll_socket_impl>
90 , public intrusive_list<epoll_socket_impl>::node
91 {
92 friend class epoll_socket_service;
93
94 public:
95 explicit epoll_socket_impl(epoll_socket_service& svc) noexcept;
96 ~epoll_socket_impl() override;
97
98 std::coroutine_handle<> connect(
99 std::coroutine_handle<>,
100 capy::executor_ref,
101 endpoint,
102 std::stop_token,
103 std::error_code*) override;
104
105 std::coroutine_handle<> read_some(
106 std::coroutine_handle<>,
107 capy::executor_ref,
108 io_buffer_param,
109 std::stop_token,
110 std::error_code*,
111 std::size_t*) override;
112
113 std::coroutine_handle<> write_some(
114 std::coroutine_handle<>,
115 capy::executor_ref,
116 io_buffer_param,
117 std::stop_token,
118 std::error_code*,
119 std::size_t*) override;
120
121 std::error_code shutdown(tcp_socket::shutdown_type what) noexcept override;
122
123 29258 native_handle_type native_handle() const noexcept override
124 {
125 29258 return fd_;
126 }
127
128 // Socket options
129 std::error_code set_no_delay(bool value) noexcept override;
130 bool no_delay(std::error_code& ec) const noexcept override;
131
132 std::error_code set_keep_alive(bool value) noexcept override;
133 bool keep_alive(std::error_code& ec) const noexcept override;
134
135 std::error_code set_receive_buffer_size(int size) noexcept override;
136 int receive_buffer_size(std::error_code& ec) const noexcept override;
137
138 std::error_code set_send_buffer_size(int size) noexcept override;
139 int send_buffer_size(std::error_code& ec) const noexcept override;
140
141 std::error_code set_linger(bool enabled, int timeout) noexcept override;
142 tcp_socket::linger_options
143 linger(std::error_code& ec) const noexcept override;
144
145 16 endpoint local_endpoint() const noexcept override
146 {
147 16 return local_endpoint_;
148 }
149 16 endpoint remote_endpoint() const noexcept override
150 {
151 16 return remote_endpoint_;
152 }
153 bool is_open() const noexcept
154 {
155 return fd_ >= 0;
156 }
157 void cancel() noexcept override;
158 void cancel_single_op(epoll_op& op) noexcept;
159 void close_socket() noexcept;
160 4814 void set_socket(int fd) noexcept
161 {
162 4814 fd_ = fd;
163 4814 }
164 9628 void set_endpoints(endpoint local, endpoint remote) noexcept
165 {
166 9628 local_endpoint_ = local;
167 9628 remote_endpoint_ = remote;
168 9628 }
169
170 epoll_connect_op conn_;
171 epoll_read_op rd_;
172 epoll_write_op wr_;
173
174 /// Per-descriptor state for persistent epoll registration
175 descriptor_state desc_state_;
176
177 private:
178 epoll_socket_service& svc_;
179 int fd_ = -1;
180 endpoint local_endpoint_;
181 endpoint remote_endpoint_;
182
183 void register_op(
184 epoll_op& op,
185 epoll_op*& desc_slot,
186 bool& ready_flag,
187 bool& cancel_flag) noexcept;
188
189 friend struct epoll_op;
190 friend struct epoll_connect_op;
191 };
192
193 /** State for epoll socket service. */
194 class epoll_socket_state
195 {
196 public:
197 203 explicit epoll_socket_state(epoll_scheduler& sched) noexcept : sched_(sched)
198 {
199 203 }
200
201 epoll_scheduler& sched_;
202 std::mutex mutex_;
203 intrusive_list<epoll_socket_impl> socket_list_;
204 std::unordered_map<epoll_socket_impl*, std::shared_ptr<epoll_socket_impl>>
205 socket_ptrs_;
206 };
207
208 /** epoll socket service implementation.
209
210 Inherits from socket_service to enable runtime polymorphism.
211 Uses key_type = socket_service for service lookup.
212 */
213 class epoll_socket_service final : public socket_service
214 {
215 public:
216 explicit epoll_socket_service(capy::execution_context& ctx);
217 ~epoll_socket_service() override;
218
219 epoll_socket_service(epoll_socket_service const&) = delete;
220 epoll_socket_service& operator=(epoll_socket_service const&) = delete;
221
222 void shutdown() override;
223
224 io_object::implementation* construct() override;
225 void destroy(io_object::implementation*) override;
226 void close(io_object::handle&) override;
227 std::error_code open_socket(tcp_socket::implementation& impl) override;
228
229 512870 epoll_scheduler& scheduler() const noexcept
230 {
231 512870 return state_->sched_;
232 }
233 void post(epoll_op* op);
234 void work_started() noexcept;
235 void work_finished() noexcept;
236
237 private:
238 std::unique_ptr<epoll_socket_state> state_;
239 };
240
241 } // namespace boost::corosio::detail
242
243 #endif // BOOST_COROSIO_HAS_EPOLL
244
245 #endif // BOOST_COROSIO_DETAIL_EPOLL_SOCKETS_HPP
246