include/boost/corosio/native/detail/posix/posix_resolver.hpp

25.0% Lines (2/8) 50.0% List of functions (2/4)
posix_resolver.hpp
f(x) Functions (4)
Line TLA 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_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 void operator()() const noexcept
172 {
173 op->request_cancel();
174 }
175 };
176
177 // Coroutine state
178 std::coroutine_handle<> h;
179 detail::continuation_op cont_op;
180 capy::executor_ref ex;
181 posix_resolver* impl = nullptr;
182
183 // Output parameters
184 std::error_code* ec_out = nullptr;
185 resolver_results* out = nullptr;
186
187 // Input parameters (owned copies for thread safety)
188 std::string host;
189 std::string service;
190 resolve_flags flags = resolve_flags::none;
191
192 // Result storage (populated by worker thread)
193 resolver_results stored_results;
194 int gai_error = 0;
195
196 // Thread coordination
197 std::atomic<bool> cancelled{false};
198 std::optional<std::stop_callback<canceller>> stop_cb;
199
200 29x resolve_op() = default;
201
202 void reset() noexcept;
203 void operator()() override;
204 void destroy() override;
205 void request_cancel() noexcept;
206 void start(std::stop_token const& token);
207 };
208
209 // reverse_resolve_op - operation state for reverse DNS resolution
210
211 struct reverse_resolve_op : scheduler_op
212 {
213 struct canceller
214 {
215 reverse_resolve_op* op;
216 void operator()() const noexcept
217 {
218 op->request_cancel();
219 }
220 };
221
222 // Coroutine state
223 std::coroutine_handle<> h;
224 detail::continuation_op cont_op;
225 capy::executor_ref ex;
226 posix_resolver* impl = nullptr;
227
228 // Output parameters
229 std::error_code* ec_out = nullptr;
230 reverse_resolver_result* result_out = nullptr;
231
232 // Input parameters
233 endpoint ep;
234 reverse_flags flags = reverse_flags::none;
235
236 // Result storage (populated by worker thread)
237 std::string stored_host;
238 std::string stored_service;
239 int gai_error = 0;
240
241 // Thread coordination
242 std::atomic<bool> cancelled{false};
243 std::optional<std::stop_callback<canceller>> stop_cb;
244
245 29x reverse_resolve_op() = default;
246
247 void reset() noexcept;
248 void operator()() override;
249 void destroy() override;
250 void request_cancel() noexcept;
251 void start(std::stop_token const& token);
252 };
253
254 /// Embedded pool work item for thread pool dispatch.
255 struct pool_op : pool_work_item
256 {
257 /// Resolver that owns this work item.
258 posix_resolver* resolver_ = nullptr;
259
260 /// Prevent impl destruction while work is in flight.
261 std::shared_ptr<posix_resolver> ref_;
262 };
263
264 explicit posix_resolver(posix_resolver_service& svc) noexcept;
265
266 std::coroutine_handle<> resolve(
267 std::coroutine_handle<>,
268 capy::executor_ref,
269 std::string_view host,
270 std::string_view service,
271 resolve_flags flags,
272 std::stop_token,
273 std::error_code*,
274 resolver_results*) override;
275
276 std::coroutine_handle<> reverse_resolve(
277 std::coroutine_handle<>,
278 capy::executor_ref,
279 endpoint const& ep,
280 reverse_flags flags,
281 std::stop_token,
282 std::error_code*,
283 reverse_resolver_result*) override;
284
285 void cancel() noexcept override;
286
287 resolve_op op_;
288 reverse_resolve_op reverse_op_;
289
290 /// Pool work item for forward resolution.
291 pool_op resolve_pool_op_;
292
293 /// Pool work item for reverse resolution.
294 pool_op reverse_pool_op_;
295
296 /// Execute blocking `getaddrinfo()` on a pool thread.
297 static void do_resolve_work(pool_work_item*) noexcept;
298
299 /// Execute blocking `getnameinfo()` on a pool thread.
300 static void do_reverse_resolve_work(pool_work_item*) noexcept;
301
302 private:
303 posix_resolver_service& svc_;
304 };
305
306 } // namespace boost::corosio::detail
307
308 #endif // BOOST_COROSIO_POSIX
309
310 #endif // BOOST_COROSIO_NATIVE_DETAIL_POSIX_POSIX_RESOLVER_HPP
311