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