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