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/continuation.hpp>
20 : #include <boost/capy/ex/execution_context.hpp>
21 :
22 : #include <boost/corosio/native/detail/endpoint_convert.hpp>
23 : #include <boost/corosio/detail/intrusive.hpp>
24 : #include <boost/corosio/detail/dispatch_coro.hpp>
25 : #include <boost/corosio/detail/scheduler_op.hpp>
26 : #include <boost/corosio/detail/thread_pool.hpp>
27 : #include <boost/corosio/native/detail/coro_op.hpp>
28 :
29 : #include <boost/corosio/detail/scheduler.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 a recycling object_pool
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 : - Each op holds an object_ref keepalive while its pool work is
66 : in flight, preventing premature destruction
67 : - resolve_op (forward resolution state)
68 : - Uses getaddrinfo() to resolve host/service to endpoints
69 : - reverse_resolve_op (reverse resolution state)
70 : - Uses getnameinfo() to resolve endpoint to host/service
71 :
72 : Completion Flow
73 : ---------------
74 : Forward resolution:
75 : 1. resolve() sets up op_, posts work to the thread pool
76 : 2. Pool thread runs getaddrinfo() (blocking)
77 : 3. Pool thread stores results in op_.stored_results
78 : 4. Pool thread calls svc_.post(&op_) to queue completion
79 : 5. Scheduler invokes op_() which resumes the coroutine
80 :
81 : Reverse resolution follows the same pattern using getnameinfo().
82 :
83 : Single-Inflight Constraint
84 : --------------------------
85 : Each resolver has ONE embedded op_ for forward and ONE reverse_op_ for
86 : reverse resolution. Concurrent operations of the same type on the same
87 : resolver would corrupt state. Users must serialize operations per-resolver.
88 :
89 : Shutdown
90 : --------
91 : The resolver service cancels all resolvers and releases the
92 : service's own reference on each; the recycling pool frees (rather
93 : than recycles) once each resolver's in-flight pool work also
94 : releases its reference. The thread pool service shuts down
95 : separately via execution_context service ordering, joining all
96 : worker threads.
97 : */
98 :
99 : namespace boost::corosio::detail {
100 :
101 : class scheduler;
102 :
103 : namespace posix_resolver_detail {
104 :
105 : // Convert resolve_flags to addrinfo ai_flags
106 : int flags_to_hints(resolve_flags flags);
107 :
108 : // Convert reverse_flags to getnameinfo NI_* flags
109 : int flags_to_ni_flags(reverse_flags flags);
110 :
111 : // Convert addrinfo results to endpoints
112 : std::vector<endpoint> convert_results(struct addrinfo* ai);
113 :
114 : // Convert getaddrinfo error codes to std::error_code
115 : std::error_code make_gai_error(int gai_err);
116 :
117 : } // namespace posix_resolver_detail
118 :
119 : class posix_resolver_service;
120 :
121 : /** Resolver implementation for POSIX backends.
122 :
123 : Each resolver instance contains a single embedded operation object (op_)
124 : that is reused for each resolve() call. This design avoids per-operation
125 : heap allocation but imposes a critical constraint:
126 :
127 : @par Single-Inflight Contract
128 :
129 : Only ONE resolve operation may be in progress at a time per resolver
130 : instance. Calling resolve() while a previous resolve() is still pending
131 : results in undefined behavior:
132 :
133 : - The new call overwrites op_ fields (host, service, coroutine handle)
134 : - The worker thread from the first call reads corrupted state
135 : - The wrong coroutine may be resumed, or resumed multiple times
136 : - Data races occur on non-atomic op_ members
137 :
138 : @par Safe Usage Patterns
139 :
140 : @code
141 : // CORRECT: Sequential resolves
142 : auto [ec1, r1] = co_await resolver.resolve("host1", "80");
143 : auto [ec2, r2] = co_await resolver.resolve("host2", "80");
144 :
145 : // CORRECT: Parallel resolves with separate resolver instances
146 : resolver r1(ctx), r2(ctx);
147 : auto [ec1, res1] = co_await r1.resolve("host1", "80"); // in one coroutine
148 : auto [ec2, res2] = co_await r2.resolve("host2", "80"); // in another
149 :
150 : // WRONG: Concurrent resolves on same resolver
151 : // These may run concurrently if launched in parallel - UNDEFINED BEHAVIOR
152 : auto f1 = resolver.resolve("host1", "80");
153 : auto f2 = resolver.resolve("host2", "80"); // BAD: overlaps with f1
154 : @endcode
155 :
156 : @par Thread Safety
157 : Distinct objects: Safe.
158 : Shared objects: Unsafe. See single-inflight contract above.
159 : */
160 : class posix_resolver final
161 : : public resolver::implementation
162 : , public intrusive_list<posix_resolver>::node
163 : {
164 : friend class posix_resolver_service;
165 :
166 : public:
167 : // resolve_op - operation state for a single DNS resolution
168 :
169 : struct resolve_op : coro_op
170 : {
171 : /// Where the endpoints are handed back.
172 : std::vector<endpoint>* out = nullptr;
173 :
174 : /// The service whose context counts this op as work.
175 : posix_resolver_service* svc = nullptr;
176 :
177 : // Input parameters (owned copies for thread safety)
178 : std::string host;
179 : std::string service;
180 : resolve_flags flags = resolve_flags::none;
181 :
182 : // Result storage (populated by worker thread)
183 : std::vector<endpoint> stored_results;
184 : int gai_error = 0;
185 :
186 HIT 78 : resolve_op() = default;
187 :
188 : void reset() noexcept;
189 : void operator()() override;
190 : void destroy() override;
191 : };
192 :
193 : // reverse_resolve_op - operation state for reverse DNS resolution
194 :
195 : struct reverse_resolve_op : coro_op
196 : {
197 : /// Where the name is handed back.
198 : endpoint_name* result_out = nullptr;
199 :
200 : /// The service whose context counts this op as work.
201 : posix_resolver_service* svc = nullptr;
202 :
203 : // Input parameters
204 : endpoint ep;
205 : reverse_flags flags = reverse_flags::none;
206 :
207 : // Result storage (populated by worker thread)
208 : std::string stored_host;
209 : std::string stored_service;
210 : int gai_error = 0;
211 :
212 78 : reverse_resolve_op() = default;
213 :
214 : void reset() noexcept;
215 : void operator()() override;
216 : void destroy() override;
217 : };
218 :
219 : /// Embedded pool work item for thread pool dispatch.
220 : struct pool_op : pool_work_item
221 : {
222 : /// Resolver that owns this work item.
223 : posix_resolver* resolver_ = nullptr;
224 :
225 : /// Prevent impl destruction while work is in flight.
226 : detail::object_ref ref_;
227 : };
228 :
229 : explicit posix_resolver(posix_resolver_service& svc) noexcept;
230 :
231 : /// Recycle into the owning service's pool. Defined out-of-line
232 : /// after posix_resolver_service for its complete type.
233 : void retire() noexcept override;
234 :
235 : /** Reset for recycling.
236 :
237 : Both ops' own `reset()` already clears `host`/`service`/
238 : `stored_host`/`stored_service` via `.clear()` (preserving their
239 : allocated capacity — the whole point of recycling), run at the
240 : top of the next `resolve()`/`reverse_resolve()`. refs_ cannot
241 : reach zero while a resolve is in flight (the pool op's
242 : `object_ref` holds a reference for the duration), so asserting
243 : `stop_cb` disengaged on both ops is a precondition check, not
244 : a defensive one.
245 :
246 : @pre refs_ == 0, no resolve in flight.
247 : */
248 64 : void reuse() noexcept
249 : {
250 64 : BOOST_COROSIO_ASSERT(!op_.stop_cb);
251 64 : BOOST_COROSIO_ASSERT(!reverse_op_.stop_cb);
252 64 : }
253 :
254 : std::coroutine_handle<> resolve(
255 : capy::continuation&,
256 : capy::executor_ref,
257 : std::string_view host,
258 : std::string_view service,
259 : resolve_flags flags,
260 : std::stop_token,
261 : std::error_code*,
262 : std::vector<endpoint>*) override;
263 :
264 : std::coroutine_handle<> reverse_resolve(
265 : capy::continuation&,
266 : capy::executor_ref,
267 : endpoint const& ep,
268 : reverse_flags flags,
269 : std::stop_token,
270 : std::error_code*,
271 : endpoint_name*) override;
272 :
273 : void cancel() noexcept override;
274 :
275 : resolve_op op_;
276 : reverse_resolve_op reverse_op_;
277 :
278 : /// Pool work item for forward resolution.
279 : pool_op resolve_pool_op_;
280 :
281 : /// Pool work item for reverse resolution.
282 : pool_op reverse_pool_op_;
283 :
284 : /// Execute blocking `getaddrinfo()` on a pool thread.
285 : static void do_resolve_work(pool_work_item*) noexcept;
286 :
287 : /// Execute blocking `getnameinfo()` on a pool thread.
288 : static void do_reverse_resolve_work(pool_work_item*) noexcept;
289 :
290 : /// Destroy a forward resolution the pool shut down before running.
291 : static void destroy_resolve_work(pool_work_item*) noexcept;
292 :
293 : /// Destroy a reverse resolution the pool shut down before running.
294 : static void destroy_reverse_resolve_work(pool_work_item*) noexcept;
295 :
296 : private:
297 : posix_resolver_service& svc_;
298 : };
299 :
300 : } // namespace boost::corosio::detail
301 :
302 : #endif // BOOST_COROSIO_POSIX
303 :
304 : #endif // BOOST_COROSIO_NATIVE_DETAIL_POSIX_POSIX_RESOLVER_HPP
|