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