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