TLA Line data 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_NATIVE_DETAIL_POSIX_POSIX_RESOLVER_HPP
11 : #define BOOST_COROSIO_NATIVE_DETAIL_POSIX_POSIX_RESOLVER_HPP
12 :
13 : #include <boost/corosio/detail/platform.hpp>
14 :
15 : #if BOOST_COROSIO_POSIX
16 :
17 : #include <boost/corosio/detail/config.hpp>
18 : #include <boost/corosio/resolver.hpp>
19 : #include <boost/capy/ex/execution_context.hpp>
20 :
21 : #include <boost/corosio/native/detail/endpoint_convert.hpp>
22 : #include <boost/corosio/detail/intrusive.hpp>
23 : #include <boost/corosio/detail/dispatch_coro.hpp>
24 : #include <boost/corosio/detail/scheduler_op.hpp>
25 : #include <boost/corosio/detail/thread_pool.hpp>
26 :
27 : #include <boost/corosio/detail/scheduler.hpp>
28 : #include <boost/corosio/resolver_results.hpp>
29 : #include <boost/capy/ex/executor_ref.hpp>
30 : #include <coroutine>
31 : #include <boost/capy/error.hpp>
32 :
33 : #include <netdb.h>
34 : #include <netinet/in.h>
35 : #include <sys/socket.h>
36 :
37 : #include <atomic>
38 : #include <memory>
39 : #include <optional>
40 : #include <stop_token>
41 : #include <string>
42 :
43 : /*
44 : POSIX Resolver Service
45 : ======================
46 :
47 : POSIX getaddrinfo() is a blocking call that cannot be monitored with
48 : epoll/kqueue/io_uring. Blocking calls are dispatched to a shared
49 : resolver_thread_pool service which reuses threads across operations.
50 :
51 : Cancellation
52 : ------------
53 : getaddrinfo() cannot be interrupted mid-call. We use an atomic flag to
54 : indicate cancellation was requested. The worker thread checks this flag
55 : after getaddrinfo() returns and reports the appropriate error.
56 :
57 : Class Hierarchy
58 : ---------------
59 : - posix_resolver_service (execution_context service, one per context)
60 : - Owns all posix_resolver instances via shared_ptr
61 : - Stores scheduler* for posting completions
62 : - posix_resolver (one per resolver object)
63 : - Contains embedded resolve_op and reverse_resolve_op for reuse
64 : - Uses shared_from_this to prevent premature destruction
65 : - resolve_op (forward resolution state)
66 : - Uses getaddrinfo() to resolve host/service to endpoints
67 : - reverse_resolve_op (reverse resolution state)
68 : - Uses getnameinfo() to resolve endpoint to host/service
69 :
70 : Completion Flow
71 : ---------------
72 : Forward resolution:
73 : 1. resolve() sets up op_, posts work to the thread pool
74 : 2. Pool thread runs getaddrinfo() (blocking)
75 : 3. Pool thread stores results in op_.stored_results
76 : 4. Pool thread calls svc_.post(&op_) to queue completion
77 : 5. Scheduler invokes op_() which resumes the coroutine
78 :
79 : Reverse resolution follows the same pattern using getnameinfo().
80 :
81 : Single-Inflight Constraint
82 : --------------------------
83 : Each resolver has ONE embedded op_ for forward and ONE reverse_op_ for
84 : reverse resolution. Concurrent operations of the same type on the same
85 : resolver would corrupt state. Users must serialize operations per-resolver.
86 :
87 : Shutdown
88 : --------
89 : The resolver service cancels all resolvers and clears the impl map.
90 : The thread pool service shuts down separately via execution_context
91 : service ordering, joining all worker threads.
92 : */
93 :
94 : namespace boost::corosio::detail {
95 :
96 : struct scheduler;
97 :
98 : namespace posix_resolver_detail {
99 :
100 : // Convert resolve_flags to addrinfo ai_flags
101 : int flags_to_hints(resolve_flags flags);
102 :
103 : // Convert reverse_flags to getnameinfo NI_* flags
104 : int flags_to_ni_flags(reverse_flags flags);
105 :
106 : // Convert addrinfo results to resolver_results
107 : resolver_results convert_results(
108 : struct addrinfo* ai, std::string_view host, std::string_view service);
109 :
110 : // Convert getaddrinfo error codes to std::error_code
111 : std::error_code make_gai_error(int gai_err);
112 :
113 : } // namespace posix_resolver_detail
114 :
115 : class posix_resolver_service;
116 :
117 : /** Resolver implementation for POSIX backends.
118 :
119 : Each resolver instance contains a single embedded operation object (op_)
120 : that is reused for each resolve() call. This design avoids per-operation
121 : heap allocation but imposes a critical constraint:
122 :
123 : @par Single-Inflight Contract
124 :
125 : Only ONE resolve operation may be in progress at a time per resolver
126 : instance. Calling resolve() while a previous resolve() is still pending
127 : results in undefined behavior:
128 :
129 : - The new call overwrites op_ fields (host, service, coroutine handle)
130 : - The worker thread from the first call reads corrupted state
131 : - The wrong coroutine may be resumed, or resumed multiple times
132 : - Data races occur on non-atomic op_ members
133 :
134 : @par Safe Usage Patterns
135 :
136 : @code
137 : // CORRECT: Sequential resolves
138 : auto [ec1, r1] = co_await resolver.resolve("host1", "80");
139 : auto [ec2, r2] = co_await resolver.resolve("host2", "80");
140 :
141 : // CORRECT: Parallel resolves with separate resolver instances
142 : resolver r1(ctx), r2(ctx);
143 : auto [ec1, res1] = co_await r1.resolve("host1", "80"); // in one coroutine
144 : auto [ec2, res2] = co_await r2.resolve("host2", "80"); // in another
145 :
146 : // WRONG: Concurrent resolves on same resolver
147 : // These may run concurrently if launched in parallel - UNDEFINED BEHAVIOR
148 : auto f1 = resolver.resolve("host1", "80");
149 : auto f2 = resolver.resolve("host2", "80"); // BAD: overlaps with f1
150 : @endcode
151 :
152 : @par Thread Safety
153 : Distinct objects: Safe.
154 : Shared objects: Unsafe. See single-inflight contract above.
155 : */
156 : class posix_resolver final
157 : : public resolver::implementation
158 : , public std::enable_shared_from_this<posix_resolver>
159 : , public intrusive_list<posix_resolver>::node
160 : {
161 : friend class posix_resolver_service;
162 :
163 : public:
164 : // resolve_op - operation state for a single DNS resolution
165 :
166 : struct resolve_op : scheduler_op
167 : {
168 : struct canceller
169 : {
170 : resolve_op* op;
171 MIS 0 : void operator()() const noexcept
172 : {
173 0 : op->request_cancel();
174 0 : }
175 : };
176 :
177 : // Coroutine state
178 : std::coroutine_handle<> h;
179 : capy::executor_ref ex;
180 : posix_resolver* impl = nullptr;
181 :
182 : // Output parameters
183 : std::error_code* ec_out = nullptr;
184 : resolver_results* out = nullptr;
185 :
186 : // Input parameters (owned copies for thread safety)
187 : std::string host;
188 : std::string service;
189 : resolve_flags flags = resolve_flags::none;
190 :
191 : // Result storage (populated by worker thread)
192 : resolver_results stored_results;
193 : int gai_error = 0;
194 :
195 : // Thread coordination
196 : std::atomic<bool> cancelled{false};
197 : std::optional<std::stop_callback<canceller>> stop_cb;
198 :
199 HIT 29 : resolve_op() = default;
200 :
201 : void reset() noexcept;
202 : void operator()() override;
203 : void destroy() override;
204 : void request_cancel() noexcept;
205 : void start(std::stop_token const& token);
206 : };
207 :
208 : // reverse_resolve_op - operation state for reverse DNS resolution
209 :
210 : struct reverse_resolve_op : scheduler_op
211 : {
212 : struct canceller
213 : {
214 : reverse_resolve_op* op;
215 MIS 0 : void operator()() const noexcept
216 : {
217 0 : op->request_cancel();
218 0 : }
219 : };
220 :
221 : // Coroutine state
222 : std::coroutine_handle<> h;
223 : capy::executor_ref ex;
224 : posix_resolver* impl = nullptr;
225 :
226 : // Output parameters
227 : std::error_code* ec_out = nullptr;
228 : reverse_resolver_result* result_out = nullptr;
229 :
230 : // Input parameters
231 : endpoint ep;
232 : reverse_flags flags = reverse_flags::none;
233 :
234 : // Result storage (populated by worker thread)
235 : std::string stored_host;
236 : std::string stored_service;
237 : int gai_error = 0;
238 :
239 : // Thread coordination
240 : std::atomic<bool> cancelled{false};
241 : std::optional<std::stop_callback<canceller>> stop_cb;
242 :
243 HIT 29 : reverse_resolve_op() = default;
244 :
245 : void reset() noexcept;
246 : void operator()() override;
247 : void destroy() override;
248 : void request_cancel() noexcept;
249 : void start(std::stop_token const& token);
250 : };
251 :
252 : /// Embedded pool work item for thread pool dispatch.
253 : struct pool_op : pool_work_item
254 : {
255 : /// Resolver that owns this work item.
256 : posix_resolver* resolver_ = nullptr;
257 :
258 : /// Prevent impl destruction while work is in flight.
259 : std::shared_ptr<posix_resolver> ref_;
260 : };
261 :
262 : explicit posix_resolver(posix_resolver_service& svc) noexcept;
263 :
264 : std::coroutine_handle<> resolve(
265 : std::coroutine_handle<>,
266 : capy::executor_ref,
267 : std::string_view host,
268 : std::string_view service,
269 : resolve_flags flags,
270 : std::stop_token,
271 : std::error_code*,
272 : resolver_results*) override;
273 :
274 : std::coroutine_handle<> reverse_resolve(
275 : std::coroutine_handle<>,
276 : capy::executor_ref,
277 : endpoint const& ep,
278 : reverse_flags flags,
279 : std::stop_token,
280 : std::error_code*,
281 : reverse_resolver_result*) override;
282 :
283 : void cancel() noexcept override;
284 :
285 : resolve_op op_;
286 : reverse_resolve_op reverse_op_;
287 :
288 : /// Pool work item for forward resolution.
289 : pool_op resolve_pool_op_;
290 :
291 : /// Pool work item for reverse resolution.
292 : pool_op reverse_pool_op_;
293 :
294 : /// Execute blocking `getaddrinfo()` on a pool thread.
295 : static void do_resolve_work(pool_work_item*) noexcept;
296 :
297 : /// Execute blocking `getnameinfo()` on a pool thread.
298 : static void do_reverse_resolve_work(pool_work_item*) noexcept;
299 :
300 : private:
301 : posix_resolver_service& svc_;
302 : };
303 :
304 : } // namespace boost::corosio::detail
305 :
306 : #endif // BOOST_COROSIO_POSIX
307 :
308 : #endif // BOOST_COROSIO_NATIVE_DETAIL_POSIX_POSIX_RESOLVER_HPP
|