95.37% Lines (268/281) 100.00% Functions (26/26)
TLA Baseline Branch
Line Hits Code Line Hits Code
1   // 1   //
2   // Copyright (c) 2026 Steve Gerbino 2   // Copyright (c) 2026 Steve Gerbino
3   // Copyright (c) 2026 Michael Vandeberg 3   // Copyright (c) 2026 Michael Vandeberg
4   // 4   //
5   // Distributed under the Boost Software License, Version 1.0. (See accompanying 5   // Distributed under the Boost Software License, Version 1.0. (See accompanying
6   // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 6   // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
7   // 7   //
8   // Official repository: https://github.com/cppalliance/corosio 8   // Official repository: https://github.com/cppalliance/corosio
9   // 9   //
10   10  
11   #ifndef BOOST_COROSIO_NATIVE_DETAIL_POSIX_POSIX_RESOLVER_SERVICE_HPP 11   #ifndef BOOST_COROSIO_NATIVE_DETAIL_POSIX_POSIX_RESOLVER_SERVICE_HPP
12   #define BOOST_COROSIO_NATIVE_DETAIL_POSIX_POSIX_RESOLVER_SERVICE_HPP 12   #define BOOST_COROSIO_NATIVE_DETAIL_POSIX_POSIX_RESOLVER_SERVICE_HPP
13   13  
14   #include <boost/corosio/detail/platform.hpp> 14   #include <boost/corosio/detail/platform.hpp>
15   15  
16   #if BOOST_COROSIO_POSIX 16   #if BOOST_COROSIO_POSIX
17   17  
18   #include <boost/corosio/native/detail/posix/posix_resolver.hpp> 18   #include <boost/corosio/native/detail/posix/posix_resolver.hpp>
19   #include <boost/corosio/native/detail/reactor/reactor_scheduler.hpp> 19   #include <boost/corosio/native/detail/reactor/reactor_scheduler.hpp>
20   #include <boost/corosio/detail/thread_pool.hpp> 20   #include <boost/corosio/detail/thread_pool.hpp>
21   21  
22   #include <unordered_map> 22   #include <unordered_map>
23   23  
24   namespace boost::corosio::detail { 24   namespace boost::corosio::detail {
25   25  
26   /** Resolver service for POSIX backends. 26   /** Resolver service for POSIX backends.
27   27  
28   Owns all posix_resolver instances. Thread lifecycle is managed 28   Owns all posix_resolver instances. Thread lifecycle is managed
29   by the thread_pool service. 29   by the thread_pool service.
30   */ 30   */
31   class BOOST_COROSIO_DECL posix_resolver_service final 31   class BOOST_COROSIO_DECL posix_resolver_service final
32   : public capy::execution_context::service 32   : public capy::execution_context::service
33   , public io_object::io_service 33   , public io_object::io_service
34   { 34   {
35   public: 35   public:
36   using key_type = posix_resolver_service; 36   using key_type = posix_resolver_service;
37   37  
HITCBC 38   1603 posix_resolver_service(capy::execution_context& ctx, scheduler& sched) 38   1790 posix_resolver_service(capy::execution_context& ctx, scheduler& sched)
HITCBC 39   3206 : sched_(&sched) 39   3580 : sched_(&sched)
HITCBC 40 - 1603 , pool_(ctx.use_service<thread_pool>()) 40 + 1790 , pool_(ctx)
41   { 41   {
HITCBC 42   1603 } 42   1790 }
43   43  
HITCBC 44   3206 ~posix_resolver_service() override = default; 44   3580 ~posix_resolver_service() override = default;
45   45  
46   posix_resolver_service(posix_resolver_service const&) = delete; 46   posix_resolver_service(posix_resolver_service const&) = delete;
47   posix_resolver_service& operator=(posix_resolver_service const&) = delete; 47   posix_resolver_service& operator=(posix_resolver_service const&) = delete;
48   48  
49   io_object::implementation* construct() override; 49   io_object::implementation* construct() override;
50   50  
HITCBC 51   49 void destroy(io_object::implementation* p) override 51   56 void destroy(io_object::implementation* p) override
52   { 52   {
HITCBC 53   49 auto& impl = static_cast<posix_resolver&>(*p); 53   56 auto& impl = static_cast<posix_resolver&>(*p);
HITCBC 54   49 impl.cancel(); 54   56 impl.cancel();
HITCBC 55   49 destroy_impl(impl); 55   56 destroy_impl(impl);
HITCBC 56   49 } 56   56 }
57   57  
58   void shutdown() override; 58   void shutdown() override;
59   void destroy_impl(posix_resolver& impl); 59   void destroy_impl(posix_resolver& impl);
60   60  
61 - void work_started() noexcept;  
62 - void work_finished() noexcept;  
63   void post(scheduler_op* op); 61   void post(scheduler_op* op);
64   62  
65 - /** Return the resolver thread pool. */ 63 + /** Return the resolver thread pool.
ECB 66 - 36 thread_pool& pool() noexcept 64 +
  65 + The pool's service is created on first use, so this can fail
  66 + where a plain accessor could not. Its workers start later, on
  67 + the first post, and a thread the system refuses there is
  68 + reported by that post rather than thrown here.
  69 +
  70 + @throws std::bad_alloc If the service cannot be allocated.
  71 +
  72 + @return The context's shared blocking-I/O pool.
  73 +
  74 + @see thread_pool_ref::get
  75 + */
HITGNC   76 + 48 thread_pool& pool()
67   { 77   {
HITCBC 68 - 36 return pool_; 78 + 48 return pool_.get();
69   } 79   }
70   80  
71   /// True when the resolver thread pool is unavailable: the `unsafe` tier, 81   /// True when the resolver thread pool is unavailable: the `unsafe` tier,
72   /// whose lockless scheduler cannot accept the pool's cross-thread 82   /// whose lockless scheduler cannot accept the pool's cross-thread
73   /// completions. 83   /// completions.
HITCBC 74   38 bool resolver_unavailable() const noexcept 84   50 bool resolver_unavailable() const noexcept
75   { 85   {
HITCBC 76   38 return sched_->scheduler_locking_disabled(); 86   50 return sched_->scheduler_locking_disabled();
77   } 87   }
78   88  
79   private: 89   private:
80   scheduler* sched_; 90   scheduler* sched_;
81 - thread_pool& pool_; 91 + thread_pool_ref pool_;
82   std::mutex mutex_; 92   std::mutex mutex_;
83   intrusive_list<posix_resolver> resolver_list_; 93   intrusive_list<posix_resolver> resolver_list_;
84   std::unordered_map<posix_resolver*, std::shared_ptr<posix_resolver>> 94   std::unordered_map<posix_resolver*, std::shared_ptr<posix_resolver>>
85   resolver_ptrs_; 95   resolver_ptrs_;
86   }; 96   };
87   97  
88   /** Get or create the resolver service for the given context. 98   /** Get or create the resolver service for the given context.
89   99  
90   This function is called by the concrete scheduler during initialization 100   This function is called by the concrete scheduler during initialization
91   to create the resolver service with a reference to itself. 101   to create the resolver service with a reference to itself.
92   102  
93   @param ctx Reference to the owning execution_context. 103   @param ctx Reference to the owning execution_context.
94   @param sched Reference to the scheduler for posting completions. 104   @param sched Reference to the scheduler for posting completions.
95   @return Reference to the resolver service. 105   @return Reference to the resolver service.
96   */ 106   */
97   posix_resolver_service& 107   posix_resolver_service&
98   get_resolver_service(capy::execution_context& ctx, scheduler& sched); 108   get_resolver_service(capy::execution_context& ctx, scheduler& sched);
99   109  
100   // --------------------------------------------------------------------------- 110   // ---------------------------------------------------------------------------
101   // Inline implementation 111   // Inline implementation
102   // --------------------------------------------------------------------------- 112   // ---------------------------------------------------------------------------
103   113  
104   // posix_resolver_detail helpers 114   // posix_resolver_detail helpers
105   115  
106   inline int 116   inline int
HITCBC 107   24 posix_resolver_detail::flags_to_hints(resolve_flags flags) 117   30 posix_resolver_detail::flags_to_hints(resolve_flags flags)
108   { 118   {
HITCBC 109   24 int hints = 0; 119   30 int hints = 0;
110   120  
HITCBC 111   24 if ((flags & resolve_flags::passive) != resolve_flags::none) 121   30 if ((flags & resolve_flags::passive) != resolve_flags::none)
HITCBC 112   1 hints |= AI_PASSIVE; 122   1 hints |= AI_PASSIVE;
HITCBC 113   24 if ((flags & resolve_flags::numeric_host) != resolve_flags::none) 123   30 if ((flags & resolve_flags::numeric_host) != resolve_flags::none)
HITCBC 114   14 hints |= AI_NUMERICHOST; 124   15 hints |= AI_NUMERICHOST;
HITCBC 115   24 if ((flags & resolve_flags::numeric_service) != resolve_flags::none) 125   30 if ((flags & resolve_flags::numeric_service) != resolve_flags::none)
HITCBC 116   11 hints |= AI_NUMERICSERV; 126   12 hints |= AI_NUMERICSERV;
HITCBC 117   24 if ((flags & resolve_flags::address_configured) != resolve_flags::none) 127   30 if ((flags & resolve_flags::address_configured) != resolve_flags::none)
HITCBC 118   1 hints |= AI_ADDRCONFIG; 128   1 hints |= AI_ADDRCONFIG;
HITCBC 119   24 if ((flags & resolve_flags::v4_mapped) != resolve_flags::none) 129   30 if ((flags & resolve_flags::v4_mapped) != resolve_flags::none)
HITCBC 120   1 hints |= AI_V4MAPPED; 130   1 hints |= AI_V4MAPPED;
HITCBC 121   24 if ((flags & resolve_flags::all_matching) != resolve_flags::none) 131   30 if ((flags & resolve_flags::all_matching) != resolve_flags::none)
HITCBC 122   1 hints |= AI_ALL; 132   1 hints |= AI_ALL;
123   133  
HITCBC 124   24 return hints; 134   30 return hints;
125   } 135   }
126   136  
127   inline int 137   inline int
HITCBC 128   12 posix_resolver_detail::flags_to_ni_flags(reverse_flags flags) 138   18 posix_resolver_detail::flags_to_ni_flags(reverse_flags flags)
129   { 139   {
HITCBC 130   12 int ni_flags = 0; 140   18 int ni_flags = 0;
131   141  
HITCBC 132   12 if ((flags & reverse_flags::numeric_host) != reverse_flags::none) 142   18 if ((flags & reverse_flags::numeric_host) != reverse_flags::none)
HITCBC 133   6 ni_flags |= NI_NUMERICHOST; 143   7 ni_flags |= NI_NUMERICHOST;
HITCBC 134   12 if ((flags & reverse_flags::numeric_service) != reverse_flags::none) 144   18 if ((flags & reverse_flags::numeric_service) != reverse_flags::none)
HITCBC 135   6 ni_flags |= NI_NUMERICSERV; 145   7 ni_flags |= NI_NUMERICSERV;
HITCBC 136   12 if ((flags & reverse_flags::name_required) != reverse_flags::none) 146   18 if ((flags & reverse_flags::name_required) != reverse_flags::none)
HITCBC 137   1 ni_flags |= NI_NAMEREQD; 147   1 ni_flags |= NI_NAMEREQD;
HITCBC 138   12 if ((flags & reverse_flags::datagram_service) != reverse_flags::none) 148   18 if ((flags & reverse_flags::datagram_service) != reverse_flags::none)
HITCBC 139   1 ni_flags |= NI_DGRAM; 149   1 ni_flags |= NI_DGRAM;
140   150  
HITCBC 141   12 return ni_flags; 151   18 return ni_flags;
142   } 152   }
143   153  
144   inline resolver_results 154   inline resolver_results
HITCBC 145   19 posix_resolver_detail::convert_results( 155   19 posix_resolver_detail::convert_results(
146   struct addrinfo* ai, std::string_view host, std::string_view service) 156   struct addrinfo* ai, std::string_view host, std::string_view service)
147   { 157   {
HITCBC 148   19 std::vector<resolver_entry> entries; 158   19 std::vector<resolver_entry> entries;
HITCBC 149   19 entries.reserve(4); // Most lookups return 1-4 addresses 159   19 entries.reserve(4); // Most lookups return 1-4 addresses
150   160  
HITCBC 151   38 for (auto* p = ai; p != nullptr; p = p->ai_next) 161   38 for (auto* p = ai; p != nullptr; p = p->ai_next)
152   { 162   {
HITCBC 153   19 if (p->ai_family == AF_INET) 163   19 if (p->ai_family == AF_INET)
154   { 164   {
HITCBC 155   17 auto* addr = reinterpret_cast<sockaddr_in*>(p->ai_addr); 165   17 auto* addr = reinterpret_cast<sockaddr_in*>(p->ai_addr);
HITCBC 156   17 auto ep = from_sockaddr_in(*addr); 166   17 auto ep = from_sockaddr_in(*addr);
HITCBC 157   17 entries.emplace_back(ep, host, service); 167   17 entries.emplace_back(ep, host, service);
158   } 168   }
HITCBC 159   2 else if (p->ai_family == AF_INET6) 169   2 else if (p->ai_family == AF_INET6)
160   { 170   {
HITCBC 161   2 auto* addr = reinterpret_cast<sockaddr_in6*>(p->ai_addr); 171   2 auto* addr = reinterpret_cast<sockaddr_in6*>(p->ai_addr);
HITCBC 162   2 auto ep = from_sockaddr_in6(*addr); 172   2 auto ep = from_sockaddr_in6(*addr);
HITCBC 163   2 entries.emplace_back(ep, host, service); 173   2 entries.emplace_back(ep, host, service);
164   } 174   }
165   } 175   }
166   176  
HITCBC 167   19 return entries; 177   19 return entries;
MISUBC 168   } 178   }
169   179  
170   inline std::error_code 180   inline std::error_code
HITCBC 171   14 posix_resolver_detail::make_gai_error(int gai_err) 181   24 posix_resolver_detail::make_gai_error(int gai_err)
172   { 182   {
173   // Map GAI errors to appropriate generic error codes 183   // Map GAI errors to appropriate generic error codes
HITCBC 174   14 switch (gai_err) 184   24 switch (gai_err)
175   { 185   {
HITCBC 176   1 case EAI_AGAIN: 186   1 case EAI_AGAIN:
177   // Temporary failure - try again later 187   // Temporary failure - try again later
HITCBC 178   1 return std::error_code( 188   1 return std::error_code(
179   static_cast<int>(std::errc::resource_unavailable_try_again), 189   static_cast<int>(std::errc::resource_unavailable_try_again),
HITCBC 180   1 std::generic_category()); 190   1 std::generic_category());
181   191  
HITCBC 182   1 case EAI_BADFLAGS: 192   1 case EAI_BADFLAGS:
183   // Invalid flags 193   // Invalid flags
HITCBC 184   1 return std::error_code( 194   1 return std::error_code(
185   static_cast<int>(std::errc::invalid_argument), 195   static_cast<int>(std::errc::invalid_argument),
HITCBC 186   1 std::generic_category()); 196   1 std::generic_category());
187   197  
HITCBC 188   1 case EAI_FAIL: 198   11 case EAI_FAIL:
189   // Non-recoverable failure 199   // Non-recoverable failure
HITCBC 190   1 return std::error_code( 200   11 return std::error_code(
HITCBC 191   1 static_cast<int>(std::errc::io_error), std::generic_category()); 201   11 static_cast<int>(std::errc::io_error), std::generic_category());
192   202  
HITCBC 193   1 case EAI_FAMILY: 203   1 case EAI_FAMILY:
194   // Address family not supported 204   // Address family not supported
HITCBC 195   1 return std::error_code( 205   1 return std::error_code(
196   static_cast<int>(std::errc::address_family_not_supported), 206   static_cast<int>(std::errc::address_family_not_supported),
HITCBC 197   1 std::generic_category()); 207   1 std::generic_category());
198   208  
HITCBC 199   1 case EAI_MEMORY: 209   1 case EAI_MEMORY:
200   // Memory allocation failure 210   // Memory allocation failure
HITCBC 201   1 return std::error_code( 211   1 return std::error_code(
202   static_cast<int>(std::errc::not_enough_memory), 212   static_cast<int>(std::errc::not_enough_memory),
HITCBC 203   1 std::generic_category()); 213   1 std::generic_category());
204   214  
HITCBC 205   5 case EAI_NONAME: 215   5 case EAI_NONAME:
206   // Host or service not found 216   // Host or service not found
HITCBC 207   5 return std::error_code( 217   5 return std::error_code(
208   static_cast<int>(std::errc::no_such_device_or_address), 218   static_cast<int>(std::errc::no_such_device_or_address),
HITCBC 209   5 std::generic_category()); 219   5 std::generic_category());
210   220  
HITCBC 211   1 case EAI_SERVICE: 221   1 case EAI_SERVICE:
212   // Service not supported for socket type 222   // Service not supported for socket type
HITCBC 213   1 return std::error_code( 223   1 return std::error_code(
214   static_cast<int>(std::errc::invalid_argument), 224   static_cast<int>(std::errc::invalid_argument),
HITCBC 215   1 std::generic_category()); 225   1 std::generic_category());
216   226  
HITCBC 217   1 case EAI_SOCKTYPE: 227   1 case EAI_SOCKTYPE:
218   // Socket type not supported 228   // Socket type not supported
HITCBC 219   1 return std::error_code( 229   1 return std::error_code(
220   static_cast<int>(std::errc::not_supported), 230   static_cast<int>(std::errc::not_supported),
HITCBC 221   1 std::generic_category()); 231   1 std::generic_category());
222   232  
HITCBC 223   1 case EAI_SYSTEM: 233   1 case EAI_SYSTEM:
224   // System error - use errno 234   // System error - use errno
HITCBC 225   1 return std::error_code(errno, std::generic_category()); 235   1 return std::error_code(errno, std::generic_category());
226   236  
HITCBC 227   1 default: 237   1 default:
228   // Unknown error 238   // Unknown error
HITCBC 229   1 return std::error_code( 239   1 return std::error_code(
HITCBC 230   1 static_cast<int>(std::errc::io_error), std::generic_category()); 240   1 static_cast<int>(std::errc::io_error), std::generic_category());
231   } 241   }
232   } 242   }
233   243  
234   // posix_resolver 244   // posix_resolver
235   245  
HITCBC 236   49 inline posix_resolver::posix_resolver(posix_resolver_service& svc) noexcept 246   57 inline posix_resolver::posix_resolver(posix_resolver_service& svc) noexcept
HITCBC 237   49 : svc_(svc) 247   57 : svc_(svc)
238   { 248   {
HITCBC 239   49 } 249   57 }
240   250  
241   // posix_resolver::resolve_op implementation 251   // posix_resolver::resolve_op implementation
242   252  
243   inline void 253   inline void
HITCBC 244   24 posix_resolver::resolve_op::reset() noexcept 254   30 posix_resolver::resolve_op::reset() noexcept
245   { 255   {
HITCBC 246   24 host.clear(); 256   30 host.clear();
HITCBC 247   24 service.clear(); 257   30 service.clear();
HITCBC 248   24 flags = resolve_flags::none; 258   30 flags = resolve_flags::none;
HITCBC 249   24 stored_results = resolver_results{}; 259   30 stored_results = resolver_results{};
HITCBC 250   24 gai_error = 0; 260   30 gai_error = 0;
HITCBC 251   24 cancelled.store(false, std::memory_order_relaxed); 261   30 cancelled.store(false, std::memory_order_relaxed);
HITCBC 252   24 stop_cb.reset(); 262   30 stop_cb.reset();
HITCBC 253   24 ec_out = nullptr; 263   30 ec_out = nullptr;
HITCBC 254   24 out = nullptr; 264   30 out = nullptr;
HITCBC 255   24 } 265   30 }
256   266  
257   inline void 267   inline void
HITCBC 258   24 posix_resolver::resolve_op::operator()() 268   29 posix_resolver::resolve_op::operator()()
259   { 269   {
HITCBC 260   24 stop_cb.reset(); // Disconnect stop callback 270   29 stop_cb.reset(); // Disconnect stop callback
261   271  
HITCBC 262   24 bool const was_cancelled = cancelled.load(std::memory_order_acquire); 272   29 bool const was_cancelled = cancelled.load(std::memory_order_acquire);
263   273  
HITCBC 264   24 if (ec_out) 274   29 if (ec_out)
265   { 275   {
HITCBC 266   24 if (was_cancelled) 276   29 if (was_cancelled)
HITCBC 267   1 *ec_out = capy::error::canceled; 277   1 *ec_out = capy::error::canceled;
HITCBC 268   23 else if (gai_error != 0) 278   28 else if (gai_error != 0)
HITCBC 269   4 *ec_out = posix_resolver_detail::make_gai_error(gai_error); 279   9 *ec_out = posix_resolver_detail::make_gai_error(gai_error);
270   else 280   else
HITCBC 271   19 *ec_out = {}; // Clear on success 281   19 *ec_out = {}; // Clear on success
272   } 282   }
273   283  
HITCBC 274   24 if (out && !was_cancelled && gai_error == 0) 284   29 if (out && !was_cancelled && gai_error == 0)
HITCBC 275   19 *out = std::move(stored_results); 285   19 *out = std::move(stored_results);
276   286  
ECB 277 - 24 impl->svc_.work_finished(); 287 + // Hold the keepalive across the dispatch: it may be the last
  288 + // reference to the implementation this op is embedded in.
HITGNC   289 + 29 auto prevent_destroy = std::move(impl_ptr);
HITGNC   290 + 29 ex.on_work_finished();
HITCBC 278   24 cont.h = h; 291   29 cont.h = h;
HITCBC 279   24 dispatch_coro(ex, cont).resume(); 292   29 dispatch_coro(ex, cont).resume();
HITCBC 280   24 } 293   29 }
281   294  
282   inline void 295   inline void
HITGBC 283   posix_resolver::resolve_op::destroy() 296   1 posix_resolver::resolve_op::destroy()
284   { 297   {
HITGBC 285   stop_cb.reset(); 298   1 stop_cb.reset();
HITGBC 286 - } 299 + 1 auto local_ex = ex;
287 - 300 + // May destroy the implementation, and with it this op.
HITGIC 288 - inline void 301 + 1 impl_ptr.reset();
HITCBC 289 - 57 posix_resolver::resolve_op::request_cancel() noexcept 302 + 1 local_ex.on_work_finished();
290 - {  
DCB 291 - 57 cancelled.store(true, std::memory_order_release);  
DCB 292 - 57 }  
293 -  
294 - inline void  
DCB 295 - 24 posix_resolver::resolve_op::start(std::stop_token const& token)  
296 - {  
DCB 297 - 24 cancelled.store(false, std::memory_order_release);  
DCB 298 - 24 stop_cb.reset();  
299 -  
DCB 300 - 24 if (token.stop_possible())  
DCB 301 - 1 stop_cb.emplace(token, canceller{this});  
HITCBC 302   24 } 303   1 }
303   304  
304   // posix_resolver::reverse_resolve_op implementation 305   // posix_resolver::reverse_resolve_op implementation
305   306  
306   inline void 307   inline void
HITCBC 307   12 posix_resolver::reverse_resolve_op::reset() noexcept 308   18 posix_resolver::reverse_resolve_op::reset() noexcept
308   { 309   {
HITCBC 309   12 ep = endpoint{}; 310   18 ep = endpoint{};
HITCBC 310   12 flags = reverse_flags::none; 311   18 flags = reverse_flags::none;
HITCBC 311   12 stored_host.clear(); 312   18 stored_host.clear();
HITCBC 312   12 stored_service.clear(); 313   18 stored_service.clear();
HITCBC 313   12 gai_error = 0; 314   18 gai_error = 0;
HITCBC 314   12 cancelled.store(false, std::memory_order_relaxed); 315   18 cancelled.store(false, std::memory_order_relaxed);
HITCBC 315   12 stop_cb.reset(); 316   18 stop_cb.reset();
HITCBC 316   12 ec_out = nullptr; 317   18 ec_out = nullptr;
HITCBC 317   12 result_out = nullptr; 318   18 result_out = nullptr;
HITCBC 318   12 } 319   18 }
319   320  
320   inline void 321   inline void
HITCBC 321   12 posix_resolver::reverse_resolve_op::operator()() 322   17 posix_resolver::reverse_resolve_op::operator()()
322   { 323   {
HITCBC 323   12 stop_cb.reset(); // Disconnect stop callback 324   17 stop_cb.reset(); // Disconnect stop callback
324   325  
HITCBC 325   12 bool const was_cancelled = cancelled.load(std::memory_order_acquire); 326   17 bool const was_cancelled = cancelled.load(std::memory_order_acquire);
326   327  
HITCBC 327   12 if (ec_out) 328   17 if (ec_out)
328   { 329   {
HITCBC 329   12 if (was_cancelled) 330   17 if (was_cancelled)
HITCBC 330   1 *ec_out = capy::error::canceled; 331   1 *ec_out = capy::error::canceled;
HITCBC 331   11 else if (gai_error != 0) 332   16 else if (gai_error != 0)
HITCBC 332   1 *ec_out = posix_resolver_detail::make_gai_error(gai_error); 333   6 *ec_out = posix_resolver_detail::make_gai_error(gai_error);
333   else 334   else
HITCBC 334   10 *ec_out = {}; // Clear on success 335   10 *ec_out = {}; // Clear on success
335   } 336   }
336   337  
HITCBC 337   12 if (result_out && !was_cancelled && gai_error == 0) 338   17 if (result_out && !was_cancelled && gai_error == 0)
338   { 339   {
HITCBC 339   30 *result_out = reverse_resolver_result( 340   30 *result_out = reverse_resolver_result(
HITCBC 340   30 ep, std::move(stored_host), std::move(stored_service)); 341   30 ep, std::move(stored_host), std::move(stored_service));
341   } 342   }
342   343  
ECB 343 - 12 impl->svc_.work_finished(); 344 + // Hold the keepalive across the dispatch: it may be the last
  345 + // reference to the implementation this op is embedded in.
HITGNC   346 + 17 auto prevent_destroy = std::move(impl_ptr);
HITGNC   347 + 17 ex.on_work_finished();
HITCBC 344   12 cont.h = h; 348   17 cont.h = h;
HITCBC 345   12 dispatch_coro(ex, cont).resume(); 349   17 dispatch_coro(ex, cont).resume();
HITCBC 346   12 } 350   17 }
347   351  
348   inline void 352   inline void
HITGBC 349   posix_resolver::reverse_resolve_op::destroy() 353   1 posix_resolver::reverse_resolve_op::destroy()
350   { 354   {
HITGBC 351   stop_cb.reset(); 355   1 stop_cb.reset();
HITGBC 352 - } 356 + 1 auto local_ex = ex;
353 - 357 + // May destroy the implementation, and with it this op.
HITGIC 354 - inline void 358 + 1 impl_ptr.reset();
HITCBC 355 - 57 posix_resolver::reverse_resolve_op::request_cancel() noexcept 359 + 1 local_ex.on_work_finished();
356 - {  
DCB 357 - 57 cancelled.store(true, std::memory_order_release);  
DCB 358 - 57 }  
359 -  
360 - inline void  
DCB 361 - 12 posix_resolver::reverse_resolve_op::start(std::stop_token const& token)  
362 - {  
DCB 363 - 12 cancelled.store(false, std::memory_order_release);  
DCB 364 - 12 stop_cb.reset();  
365 -  
DCB 366 - 12 if (token.stop_possible())  
DCB 367 - 1 stop_cb.emplace(token, canceller{this});  
HITCBC 368   12 } 360   1 }
369   361  
370   // posix_resolver implementation 362   // posix_resolver implementation
371   363  
372   inline std::coroutine_handle<> 364   inline std::coroutine_handle<>
HITCBC 373   25 posix_resolver::resolve( 365   31 posix_resolver::resolve(
374   std::coroutine_handle<> h, 366   std::coroutine_handle<> h,
375   capy::executor_ref ex, 367   capy::executor_ref ex,
376   std::string_view host, 368   std::string_view host,
377   std::string_view service, 369   std::string_view service,
378   resolve_flags flags, 370   resolve_flags flags,
379   std::stop_token token, 371   std::stop_token token,
380   std::error_code* ec, 372   std::error_code* ec,
381   resolver_results* out) 373   resolver_results* out)
382   { 374   {
HITCBC 383   25 if (svc_.resolver_unavailable()) 375   31 if (svc_.resolver_unavailable())
384   { 376   {
HITCBC 385   1 *ec = std::make_error_code(std::errc::operation_not_supported); 377   1 *ec = std::make_error_code(std::errc::operation_not_supported);
HITCBC 386   1 op_.cont.h = h; 378   1 op_.cont.h = h;
HITCBC 387   1 return dispatch_coro(ex, op_.cont); 379   1 return dispatch_coro(ex, op_.cont);
388   } 380   }
389   381  
HITCBC 390   24 auto& op = op_; 382   30 auto& op = op_;
HITCBC 391   24 op.reset(); 383   30 op.reset();
HITCBC 392   24 op.h = h; 384   30 op.h = h;
DCB 393 - 24 op.impl = this;  
HITCBC 394   24 op.ex = ex; 385   30 op.ex = ex;
HITCBC 395   24 op.ec_out = ec; 386   30 op.ec_out = ec;
HITCBC 396   24 op.out = out; 387   30 op.out = out;
HITCBC 397   24 op.host = host; 388   30 op.host = host;
HITCBC 398   24 op.service = service; 389   30 op.service = service;
HITCBC 399   24 op.flags = flags; 390   30 op.flags = flags;
HITCBC 400   24 op.start(token); 391   30 op.start(token);
401   392  
402   // Keep io_context alive while resolution is pending 393   // Keep io_context alive while resolution is pending
HITCBC 403   24 op.ex.on_work_started(); 394   30 op.ex.on_work_started();
404   395  
405   // Prevent impl destruction while work is in flight 396   // Prevent impl destruction while work is in flight
HITCBC 406   24 resolve_pool_op_.resolver_ = this; 397   30 resolve_pool_op_.resolver_ = this;
HITCBC 407   24 resolve_pool_op_.ref_ = this->shared_from_this(); 398   30 resolve_pool_op_.ref_ = this->shared_from_this();
HITCBC 408   24 resolve_pool_op_.func_ = &posix_resolver::do_resolve_work; 399   30 resolve_pool_op_.func_ = &posix_resolver::do_resolve_work;
HITCBC 409 - 24 if (!svc_.pool().post(&resolve_pool_op_)) 400 + 30 if (auto pec = svc_.pool().post(&resolve_pool_op_))
410   { 401   {
411 - // Pool shut down — complete with cancellation 402 + // The pool is shutting down, or the system refused it a thread.
  403 + // Nothing of this resolve went cross-thread, so it answers here
  404 + // like the no-resolver exit above rather than through a
  405 + // completion the scheduler has to carry back.
MISUBC 412   resolve_pool_op_.ref_.reset(); 406   resolve_pool_op_.ref_.reset();
MISUBC 413 - op.cancelled.store(true, std::memory_order_release); 407 + op.stop_cb.reset();
MISUBC 414 - svc_.post(&op_); 408 + op.ex.on_work_finished();
MISUNC   409 + *ec = pec;
MISUNC   410 + op.cont.h = h;
MISUNC   411 + return dispatch_coro(ex, op.cont);
415   } 412   }
HITCBC 416   24 return std::noop_coroutine(); 413   30 return std::noop_coroutine();
417   } 414   }
418   415  
419   inline std::coroutine_handle<> 416   inline std::coroutine_handle<>
HITCBC 420   13 posix_resolver::reverse_resolve( 417   19 posix_resolver::reverse_resolve(
421   std::coroutine_handle<> h, 418   std::coroutine_handle<> h,
422   capy::executor_ref ex, 419   capy::executor_ref ex,
423   endpoint const& ep, 420   endpoint const& ep,
424   reverse_flags flags, 421   reverse_flags flags,
425   std::stop_token token, 422   std::stop_token token,
426   std::error_code* ec, 423   std::error_code* ec,
427   reverse_resolver_result* result_out) 424   reverse_resolver_result* result_out)
428   { 425   {
HITCBC 429   13 if (svc_.resolver_unavailable()) 426   19 if (svc_.resolver_unavailable())
430   { 427   {
HITCBC 431   1 *ec = std::make_error_code(std::errc::operation_not_supported); 428   1 *ec = std::make_error_code(std::errc::operation_not_supported);
HITCBC 432   1 reverse_op_.cont.h = h; 429   1 reverse_op_.cont.h = h;
HITCBC 433   1 return dispatch_coro(ex, reverse_op_.cont); 430   1 return dispatch_coro(ex, reverse_op_.cont);
434   } 431   }
435   432  
HITCBC 436   12 auto& op = reverse_op_; 433   18 auto& op = reverse_op_;
HITCBC 437   12 op.reset(); 434   18 op.reset();
HITCBC 438   12 op.h = h; 435   18 op.h = h;
DCB 439 - 12 op.impl = this;  
HITCBC 440   12 op.ex = ex; 436   18 op.ex = ex;
HITCBC 441   12 op.ec_out = ec; 437   18 op.ec_out = ec;
HITCBC 442   12 op.result_out = result_out; 438   18 op.result_out = result_out;
HITCBC 443   12 op.ep = ep; 439   18 op.ep = ep;
HITCBC 444   12 op.flags = flags; 440   18 op.flags = flags;
HITCBC 445   12 op.start(token); 441   18 op.start(token);
446   442  
447   // Keep io_context alive while resolution is pending 443   // Keep io_context alive while resolution is pending
HITCBC 448   12 op.ex.on_work_started(); 444   18 op.ex.on_work_started();
449   445  
450   // Prevent impl destruction while work is in flight 446   // Prevent impl destruction while work is in flight
HITCBC 451   12 reverse_pool_op_.resolver_ = this; 447   18 reverse_pool_op_.resolver_ = this;
HITCBC 452   12 reverse_pool_op_.ref_ = this->shared_from_this(); 448   18 reverse_pool_op_.ref_ = this->shared_from_this();
HITCBC 453   12 reverse_pool_op_.func_ = &posix_resolver::do_reverse_resolve_work; 449   18 reverse_pool_op_.func_ = &posix_resolver::do_reverse_resolve_work;
HITCBC 454 - 12 if (!svc_.pool().post(&reverse_pool_op_)) 450 + 18 if (auto pec = svc_.pool().post(&reverse_pool_op_))
455   { 451   {
456 - // Pool shut down — complete with cancellation 452 + // The pool is shutting down, or the system refused it a thread.
  453 + // Nothing of this resolve went cross-thread, so it answers here
  454 + // like the no-resolver exit above rather than through a
  455 + // completion the scheduler has to carry back.
MISUBC 457   reverse_pool_op_.ref_.reset(); 456   reverse_pool_op_.ref_.reset();
MISUBC 458 - op.cancelled.store(true, std::memory_order_release); 457 + op.stop_cb.reset();
MISUBC 459 - svc_.post(&reverse_op_); 458 + op.ex.on_work_finished();
MISUNC   459 + *ec = pec;
MISUNC   460 + op.cont.h = h;
MISUNC   461 + return dispatch_coro(ex, op.cont);
460   } 462   }
HITCBC 461   12 return std::noop_coroutine(); 463   18 return std::noop_coroutine();
462   } 464   }
463   465  
464   inline void 466   inline void
HITCBC 465   56 posix_resolver::cancel() noexcept 467   64 posix_resolver::cancel() noexcept
466   { 468   {
HITCBC 467   56 op_.request_cancel(); 469   64 op_.request_cancel();
HITCBC 468   56 reverse_op_.request_cancel(); 470   64 reverse_op_.request_cancel();
HITCBC 469   56 } 471   64 }
470   472  
471   inline void 473   inline void
HITCBC 472   24 posix_resolver::do_resolve_work(pool_work_item* w) noexcept 474   30 posix_resolver::do_resolve_work(pool_work_item* w) noexcept
473   { 475   {
HITCBC 474   24 auto* pw = static_cast<pool_op*>(w); 476   30 auto* pw = static_cast<pool_op*>(w);
HITCBC 475   24 auto* self = pw->resolver_; 477   30 auto* self = pw->resolver_;
476   478  
HITCBC 477   24 struct addrinfo hints{}; 479   30 struct addrinfo hints{};
HITCBC 478   24 hints.ai_family = AF_UNSPEC; 480   30 hints.ai_family = AF_UNSPEC;
HITCBC 479   24 hints.ai_socktype = SOCK_STREAM; 481   30 hints.ai_socktype = SOCK_STREAM;
HITCBC 480   24 hints.ai_flags = posix_resolver_detail::flags_to_hints(self->op_.flags); 482   30 hints.ai_flags = posix_resolver_detail::flags_to_hints(self->op_.flags);
481   483  
HITCBC 482   24 struct addrinfo* ai = nullptr; 484   30 struct addrinfo* ai = nullptr;
HITCBC 483   72 int result = ::getaddrinfo( 485   90 int result = ::getaddrinfo(
HITCBC 484   48 self->op_.host.empty() ? nullptr : self->op_.host.c_str(), 486   60 self->op_.host.empty() ? nullptr : self->op_.host.c_str(),
HITCBC 485   48 self->op_.service.empty() ? nullptr : self->op_.service.c_str(), &hints, 487   60 self->op_.service.empty() ? nullptr : self->op_.service.c_str(), &hints,
486   &ai); 488   &ai);
487   489  
HITCBC 488   24 if (!self->op_.cancelled.load(std::memory_order_acquire)) 490   30 if (!self->op_.cancelled.load(std::memory_order_acquire))
489   { 491   {
HITCBC 490   23 if (result == 0 && ai) 492   28 if (result == 0 && ai)
491   { 493   {
HITCBC 492   38 self->op_.stored_results = posix_resolver_detail::convert_results( 494   38 self->op_.stored_results = posix_resolver_detail::convert_results(
HITCBC 493   19 ai, self->op_.host, self->op_.service); 495   19 ai, self->op_.host, self->op_.service);
HITCBC 494   19 self->op_.gai_error = 0; 496   19 self->op_.gai_error = 0;
495   } 497   }
496   else 498   else
497   { 499   {
HITCBC 498   4 self->op_.gai_error = result; 500   9 self->op_.gai_error = result;
499   } 501   }
500   } 502   }
501   503  
HITCBC 502   24 if (ai) 504   30 if (ai)
HITCBC 503   20 ::freeaddrinfo(ai); 505   21 ::freeaddrinfo(ai);
504   506  
505 - // Move ref to stack before post — post may trigger destroy_impl 507 + // Hand the keepalive to the op: the completion waits in the
506 - // which erases the last shared_ptr, destroying *self (and *pw) 508 + // scheduler's queue, and the implementation embedding it must
ECB 507 - 24 auto ref = std::move(pw->ref_); 509 + // outlive that wait. Nothing may touch *self after the post.
HITGNC   510 + 30 self->op_.impl_ptr = std::move(pw->ref_);
HITCBC 508   24 self->svc_.post(&self->op_); 511   30 self->svc_.post(&self->op_);
HITCBC 509   24 } 512   30 }
510   513  
511   inline void 514   inline void
HITCBC 512   12 posix_resolver::do_reverse_resolve_work(pool_work_item* w) noexcept 515   18 posix_resolver::do_reverse_resolve_work(pool_work_item* w) noexcept
513   { 516   {
HITCBC 514   12 auto* pw = static_cast<pool_op*>(w); 517   18 auto* pw = static_cast<pool_op*>(w);
HITCBC 515   12 auto* self = pw->resolver_; 518   18 auto* self = pw->resolver_;
516   519  
HITCBC 517   12 sockaddr_storage ss{}; 520   18 sockaddr_storage ss{};
518   socklen_t ss_len; 521   socklen_t ss_len;
519   522  
HITCBC 520   12 if (self->reverse_op_.ep.is_v4()) 523   18 if (self->reverse_op_.ep.is_v4())
521   { 524   {
HITCBC 522   10 auto sa = to_sockaddr_in(self->reverse_op_.ep); 525   16 auto sa = to_sockaddr_in(self->reverse_op_.ep);
HITCBC 523   10 std::memcpy(&ss, &sa, sizeof(sa)); 526   16 std::memcpy(&ss, &sa, sizeof(sa));
HITCBC 524   10 ss_len = sizeof(sockaddr_in); 527   16 ss_len = sizeof(sockaddr_in);
525   } 528   }
526   else 529   else
527   { 530   {
HITCBC 528   2 auto sa = to_sockaddr_in6(self->reverse_op_.ep); 531   2 auto sa = to_sockaddr_in6(self->reverse_op_.ep);
HITCBC 529   2 std::memcpy(&ss, &sa, sizeof(sa)); 532   2 std::memcpy(&ss, &sa, sizeof(sa));
HITCBC 530   2 ss_len = sizeof(sockaddr_in6); 533   2 ss_len = sizeof(sockaddr_in6);
531   } 534   }
532   535  
533   char host[NI_MAXHOST]; 536   char host[NI_MAXHOST];
534   char service[NI_MAXSERV]; 537   char service[NI_MAXSERV];
535   538  
HITCBC 536   12 int result = ::getnameinfo( 539   18 int result = ::getnameinfo(
537   reinterpret_cast<sockaddr*>(&ss), ss_len, host, sizeof(host), service, 540   reinterpret_cast<sockaddr*>(&ss), ss_len, host, sizeof(host), service,
538   sizeof(service), 541   sizeof(service),
539   posix_resolver_detail::flags_to_ni_flags(self->reverse_op_.flags)); 542   posix_resolver_detail::flags_to_ni_flags(self->reverse_op_.flags));
540   543  
HITCBC 541   12 if (!self->reverse_op_.cancelled.load(std::memory_order_acquire)) 544   18 if (!self->reverse_op_.cancelled.load(std::memory_order_acquire))
542   { 545   {
HITCBC 543   11 if (result == 0) 546   16 if (result == 0)
544   { 547   {
HITCBC 545   10 self->reverse_op_.stored_host = host; 548   10 self->reverse_op_.stored_host = host;
HITCBC 546   10 self->reverse_op_.stored_service = service; 549   10 self->reverse_op_.stored_service = service;
HITCBC 547   10 self->reverse_op_.gai_error = 0; 550   10 self->reverse_op_.gai_error = 0;
548   } 551   }
549   else 552   else
550   { 553   {
HITCBC 551   1 self->reverse_op_.gai_error = result; 554   6 self->reverse_op_.gai_error = result;
552   } 555   }
553   } 556   }
554   557  
555 - // Move ref to stack before post — post may trigger destroy_impl 558 + // Hand the keepalive to the op: the completion waits in the
556 - // which erases the last shared_ptr, destroying *self (and *pw) 559 + // scheduler's queue, and the implementation embedding it must
ECB 557 - 12 auto ref = std::move(pw->ref_); 560 + // outlive that wait. Nothing may touch *self after the post.
HITGNC   561 + 18 self->reverse_op_.impl_ptr = std::move(pw->ref_);
HITCBC 558   12 self->svc_.post(&self->reverse_op_); 562   18 self->svc_.post(&self->reverse_op_);
HITCBC 559   12 } 563   18 }
560   564  
561   // posix_resolver_service implementation 565   // posix_resolver_service implementation
562   566  
563   inline void 567   inline void
HITCBC 564   1603 posix_resolver_service::shutdown() 568   1790 posix_resolver_service::shutdown()
565   { 569   {
HITCBC 566   1603 std::lock_guard<std::mutex> lock(mutex_); 570   1790 std::lock_guard<std::mutex> lock(mutex_);
567   571  
568   // Cancel all resolvers (sets cancelled flag checked by pool threads) 572   // Cancel all resolvers (sets cancelled flag checked by pool threads)
HITCBC 569   1603 for (auto* impl = resolver_list_.pop_front(); impl != nullptr; 573   1791 for (auto* impl = resolver_list_.pop_front(); impl != nullptr;
HITGBC 570   impl = resolver_list_.pop_front()) 574   1 impl = resolver_list_.pop_front())
571   { 575   {
HITGBC 572   impl->cancel(); 576   1 impl->cancel();
573   } 577   }
574   578  
575   // Clear the map which releases shared_ptrs. 579   // Clear the map which releases shared_ptrs.
576   // The thread pool service shuts down separately via 580   // The thread pool service shuts down separately via
577   // execution_context service ordering. 581   // execution_context service ordering.
HITCBC 578   1603 resolver_ptrs_.clear(); 582   1790 resolver_ptrs_.clear();
HITCBC 579   1603 } 583   1790 }
580   584  
581   inline io_object::implementation* 585   inline io_object::implementation*
HITCBC 582   49 posix_resolver_service::construct() 586   57 posix_resolver_service::construct()
583   { 587   {
HITCBC 584   49 auto ptr = std::make_shared<posix_resolver>(*this); 588   57 auto ptr = std::make_shared<posix_resolver>(*this);
HITCBC 585   49 auto* impl = ptr.get(); 589   57 auto* impl = ptr.get();
586   590  
587   { 591   {
HITCBC 588   49 std::lock_guard<std::mutex> lock(mutex_); 592   57 std::lock_guard<std::mutex> lock(mutex_);
HITCBC 589   49 resolver_list_.push_back(impl); 593   57 resolver_list_.push_back(impl);
HITCBC 590   49 resolver_ptrs_[impl] = std::move(ptr); 594   57 resolver_ptrs_[impl] = std::move(ptr);
HITCBC 591   49 } 595   57 }
592   596  
HITCBC 593   49 return impl; 597   57 return impl;
HITCBC 594   49 } 598   57 }
595   599  
596   inline void 600   inline void
HITCBC 597   49 posix_resolver_service::destroy_impl(posix_resolver& impl) 601   56 posix_resolver_service::destroy_impl(posix_resolver& impl)
598   { 602   {
HITCBC 599   49 std::lock_guard<std::mutex> lock(mutex_); 603   56 std::lock_guard<std::mutex> lock(mutex_);
HITCBC 600   49 resolver_list_.remove(&impl); 604   56 resolver_list_.remove(&impl);
HITCBC 601   49 resolver_ptrs_.erase(&impl); 605   56 resolver_ptrs_.erase(&impl);
HITCBC 602   49 } 606   56 }
603   607  
604   inline void 608   inline void
HITCBC 605   36 posix_resolver_service::post(scheduler_op* op) 609   48 posix_resolver_service::post(scheduler_op* op)
606   { 610   {
DCB 607 - 36 }  
DCB 608 - 36  
609 - inline void  
610 - posix_resolver_service::work_started() noexcept  
611 - {  
612 - sched_->work_started();  
613 - }  
614 -  
615 - inline void  
616 - posix_resolver_service::work_finished() noexcept  
DCB 617 - 36 {  
618 - sched_->work_finished();  
HITCBC 619   36 sched_->post(op); 611   48 sched_->post(op);
HITCBC 620   36 } 612   48 }
621   613  
622   // Free function to get/create the resolver service 614   // Free function to get/create the resolver service
623   615  
624   inline posix_resolver_service& 616   inline posix_resolver_service&
HITCBC 625   1603 get_resolver_service(capy::execution_context& ctx, scheduler& sched) 617   1790 get_resolver_service(capy::execution_context& ctx, scheduler& sched)
626   { 618   {
HITCBC 627   1603 return ctx.make_service<posix_resolver_service>(sched); 619   1790 return ctx.make_service<posix_resolver_service>(sched);
628   } 620   }
629   621  
630   } // namespace boost::corosio::detail 622   } // namespace boost::corosio::detail
631   623  
632   #endif // BOOST_COROSIO_POSIX 624   #endif // BOOST_COROSIO_POSIX
633   625  
634   #endif // BOOST_COROSIO_NATIVE_DETAIL_POSIX_POSIX_RESOLVER_SERVICE_HPP 626   #endif // BOOST_COROSIO_NATIVE_DETAIL_POSIX_POSIX_RESOLVER_SERVICE_HPP