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