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