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

100.0% Lines (12/12) 100.0% Functions (7/7) -% Branches (0/0)
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
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();
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 28568 native_handle_type native_handle() const noexcept override { return fd_; }
124
125 // Socket options
126 std::error_code set_no_delay(bool value) noexcept override;
127 bool no_delay(std::error_code& ec) const noexcept override;
128
129 std::error_code set_keep_alive(bool value) noexcept override;
130 bool keep_alive(std::error_code& ec) const noexcept override;
131
132 std::error_code set_receive_buffer_size(int size) noexcept override;
133 int receive_buffer_size(std::error_code& ec) const noexcept override;
134
135 std::error_code set_send_buffer_size(int size) noexcept override;
136 int send_buffer_size(std::error_code& ec) const noexcept override;
137
138 std::error_code set_linger(bool enabled, int timeout) noexcept override;
139 tcp_socket::linger_options linger(std::error_code& ec) const noexcept override;
140
141 16 endpoint local_endpoint() const noexcept override { return local_endpoint_; }
142 16 endpoint remote_endpoint() const noexcept override { return remote_endpoint_; }
143 bool is_open() const noexcept { return fd_ >= 0; }
144 void cancel() noexcept override;
145 void cancel_single_op(epoll_op& op) noexcept;
146 void close_socket() noexcept;
147 4699 void set_socket(int fd) noexcept { fd_ = fd; }
148 9398 void set_endpoints(endpoint local, endpoint remote) noexcept
149 {
150 9398 local_endpoint_ = local;
151 9398 remote_endpoint_ = remote;
152 9398 }
153
154 epoll_connect_op conn_;
155 epoll_read_op rd_;
156 epoll_write_op wr_;
157
158 /// Per-descriptor state for persistent epoll registration
159 descriptor_state desc_state_;
160
161 private:
162 epoll_socket_service& svc_;
163 int fd_ = -1;
164 endpoint local_endpoint_;
165 endpoint remote_endpoint_;
166
167 void register_op(
168 epoll_op& op,
169 epoll_op*& desc_slot,
170 bool& ready_flag,
171 bool& cancel_flag) noexcept;
172
173 friend struct epoll_op;
174 friend struct epoll_connect_op;
175 };
176
177 /** State for epoll socket service. */
178 class epoll_socket_state
179 {
180 public:
181 203 explicit epoll_socket_state(epoll_scheduler& sched) noexcept
182 203 : sched_(sched)
183 {
184 203 }
185
186 epoll_scheduler& sched_;
187 std::mutex mutex_;
188 intrusive_list<epoll_socket_impl> socket_list_;
189 std::unordered_map<epoll_socket_impl*, std::shared_ptr<epoll_socket_impl>> socket_ptrs_;
190 };
191
192 /** epoll socket service implementation.
193
194 Inherits from socket_service to enable runtime polymorphism.
195 Uses key_type = socket_service for service lookup.
196 */
197 class epoll_socket_service : public socket_service
198 {
199 public:
200 explicit epoll_socket_service(capy::execution_context& ctx);
201 ~epoll_socket_service();
202
203 epoll_socket_service(epoll_socket_service const&) = delete;
204 epoll_socket_service& operator=(epoll_socket_service const&) = delete;
205
206 void shutdown() override;
207
208 io_object::implementation* construct() override;
209 void destroy(io_object::implementation*) override;
210 void close(io_object::handle&) override;
211 std::error_code open_socket(tcp_socket::implementation& impl) override;
212
213 358016 epoll_scheduler& scheduler() const noexcept { return state_->sched_; }
214 void post(epoll_op* op);
215 void work_started() noexcept;
216 void work_finished() noexcept;
217
218 private:
219 std::unique_ptr<epoll_socket_state> state_;
220 };
221
222 } // namespace boost::corosio::detail
223
224 #endif // BOOST_COROSIO_HAS_EPOLL
225
226 #endif // BOOST_COROSIO_DETAIL_EPOLL_SOCKETS_HPP
227