include/boost/corosio/native/detail/posix/posix_resolver_service.hpp

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