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

100.0% Lines (6 / 6) 100.0% Functions (3 / 3)
posix_resolver.hpp
f(x) Functions (3)
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/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 78x 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 78x 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 64x void reuse() noexcept
249 {
250 64x BOOST_COROSIO_ASSERT(!op_.stop_cb);
251 64x BOOST_COROSIO_ASSERT(!reverse_op_.stop_cb);
252 64x }
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
305