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

82.5% Lines (231/280) 93.3% List of functions (28/30)
posix_resolver_service.hpp
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 515x 100.0% 73.0% boost::corosio::detail::posix_resolver_service::~posix_resolver_service() :42 1030x 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 93.3% 90.0% boost::corosio::detail::posix_resolver::resolve_op::destroy() :273 0 0.0% 0.0% boost::corosio::detail::posix_resolver::resolve_op::request_cancel() :279 33x 100.0% 100.0% boost::corosio::detail::posix_resolver::resolve_op::start(std::stop_token const&) :285 16x 83.3% 71.0% boost::corosio::detail::posix_resolver::reverse_resolve_op::reset() :297 10x 100.0% 100.0% boost::corosio::detail::posix_resolver::reverse_resolve_op::operator()() :311 10x 93.8% 93.0% boost::corosio::detail::posix_resolver::reverse_resolve_op::destroy() :339 0 0.0% 0.0% boost::corosio::detail::posix_resolver::reverse_resolve_op::request_cancel() :345 33x 100.0% 100.0% boost::corosio::detail::posix_resolver::reverse_resolve_op::start(std::stop_token const&) :351 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*) :363 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*) :403 10x 85.0% 82.0% boost::corosio::detail::posix_resolver::cancel() :441 33x 100.0% 100.0% boost::corosio::detail::posix_resolver::do_resolve_work(boost::corosio::detail::pool_work_item*) :448 16x 100.0% 93.0% boost::corosio::detail::posix_resolver::do_reverse_resolve_work(boost::corosio::detail::pool_work_item*) :488 10x 100.0% 100.0% boost::corosio::detail::posix_resolver_service::shutdown() :540 515x 71.4% 78.0% boost::corosio::detail::posix_resolver_service::construct() :558 29x 100.0% 71.0% boost::corosio::detail::posix_resolver_service::destroy_impl(boost::corosio::detail::posix_resolver&) :573 29x 100.0% 67.0% boost::corosio::detail::posix_resolver_service::post(boost::corosio::detail::scheduler_op*) :581 26x 100.0% 100.0% boost::corosio::detail::posix_resolver_service::work_finished() :593 26x 100.0% 100.0% boost::corosio::detail::get_resolver_service(boost::capy::execution_context&, boost::corosio::detail::scheduler&) :601 515x 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 515x posix_resolver_service(capy::execution_context& ctx, scheduler& sched)
37 1030x : sched_(&sched)
38 515x , pool_(ctx.make_service<thread_pool>())
39 {
40 515x }
41
42 1030x ~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 26x for (auto* p = ai; p != nullptr; p = p->ai_next)
142 {
143 13x 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 2x else if (p->ai_family == AF_INET6)
150 {
151 2x auto* addr = reinterpret_cast<sockaddr_in6*>(p->ai_addr);
152 2x auto ep = from_sockaddr_in6(*addr);
153 2x 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 cont_op.cont.h = h;
269 16x dispatch_coro(ex, cont_op.cont).resume();
270 16x }
271
272 inline void
273 posix_resolver::resolve_op::destroy()
274 {
275 stop_cb.reset();
276 }
277
278 inline void
279 33x posix_resolver::resolve_op::request_cancel() noexcept
280 {
281 33x cancelled.store(true, std::memory_order_release);
282 33x }
283
284 inline void
285 16x posix_resolver::resolve_op::start(std::stop_token const& token)
286 {
287 16x cancelled.store(false, std::memory_order_release);
288 16x stop_cb.reset();
289
290 16x if (token.stop_possible())
291 stop_cb.emplace(token, canceller{this});
292 16x }
293
294 // posix_resolver::reverse_resolve_op implementation
295
296 inline void
297 10x posix_resolver::reverse_resolve_op::reset() noexcept
298 {
299 10x ep = endpoint{};
300 10x flags = reverse_flags::none;
301 10x stored_host.clear();
302 10x stored_service.clear();
303 10x gai_error = 0;
304 10x cancelled.store(false, std::memory_order_relaxed);
305 10x stop_cb.reset();
306 10x ec_out = nullptr;
307 10x result_out = nullptr;
308 10x }
309
310 inline void
311 10x posix_resolver::reverse_resolve_op::operator()()
312 {
313 10x stop_cb.reset(); // Disconnect stop callback
314
315 10x bool const was_cancelled = cancelled.load(std::memory_order_acquire);
316
317 10x if (ec_out)
318 {
319 10x if (was_cancelled)
320 *ec_out = capy::error::canceled;
321 10x else if (gai_error != 0)
322 1x *ec_out = posix_resolver_detail::make_gai_error(gai_error);
323 else
324 9x *ec_out = {}; // Clear on success
325 }
326
327 10x if (result_out && !was_cancelled && gai_error == 0)
328 {
329 27x *result_out = reverse_resolver_result(
330 27x ep, std::move(stored_host), std::move(stored_service));
331 }
332
333 10x impl->svc_.work_finished();
334 10x cont_op.cont.h = h;
335 10x dispatch_coro(ex, cont_op.cont).resume();
336 10x }
337
338 inline void
339 posix_resolver::reverse_resolve_op::destroy()
340 {
341 stop_cb.reset();
342 }
343
344 inline void
345 33x posix_resolver::reverse_resolve_op::request_cancel() noexcept
346 {
347 33x cancelled.store(true, std::memory_order_release);
348 33x }
349
350 inline void
351 10x posix_resolver::reverse_resolve_op::start(std::stop_token const& token)
352 {
353 10x cancelled.store(false, std::memory_order_release);
354 10x stop_cb.reset();
355
356 10x if (token.stop_possible())
357 stop_cb.emplace(token, canceller{this});
358 10x }
359
360 // posix_resolver implementation
361
362 inline std::coroutine_handle<>
363 16x posix_resolver::resolve(
364 std::coroutine_handle<> h,
365 capy::executor_ref ex,
366 std::string_view host,
367 std::string_view service,
368 resolve_flags flags,
369 std::stop_token token,
370 std::error_code* ec,
371 resolver_results* out)
372 {
373 16x auto& op = op_;
374 16x op.reset();
375 16x op.h = h;
376 16x op.ex = ex;
377 16x op.impl = this;
378 16x op.ec_out = ec;
379 16x op.out = out;
380 16x op.host = host;
381 16x op.service = service;
382 16x op.flags = flags;
383 16x op.start(token);
384
385 // Keep io_context alive while resolution is pending
386 16x op.ex.on_work_started();
387
388 // Prevent impl destruction while work is in flight
389 16x resolve_pool_op_.resolver_ = this;
390 16x resolve_pool_op_.ref_ = this->shared_from_this();
391 16x resolve_pool_op_.func_ = &posix_resolver::do_resolve_work;
392 16x if (!svc_.pool().post(&resolve_pool_op_))
393 {
394 // Pool shut down — complete with cancellation
395 resolve_pool_op_.ref_.reset();
396 op.cancelled.store(true, std::memory_order_release);
397 svc_.post(&op_);
398 }
399 16x return std::noop_coroutine();
400 }
401
402 inline std::coroutine_handle<>
403 10x posix_resolver::reverse_resolve(
404 std::coroutine_handle<> h,
405 capy::executor_ref ex,
406 endpoint const& ep,
407 reverse_flags flags,
408 std::stop_token token,
409 std::error_code* ec,
410 reverse_resolver_result* result_out)
411 {
412 10x auto& op = reverse_op_;
413 10x op.reset();
414 10x op.h = h;
415 10x op.ex = ex;
416 10x op.impl = this;
417 10x op.ec_out = ec;
418 10x op.result_out = result_out;
419 10x op.ep = ep;
420 10x op.flags = flags;
421 10x op.start(token);
422
423 // Keep io_context alive while resolution is pending
424 10x op.ex.on_work_started();
425
426 // Prevent impl destruction while work is in flight
427 10x reverse_pool_op_.resolver_ = this;
428 10x reverse_pool_op_.ref_ = this->shared_from_this();
429 10x reverse_pool_op_.func_ = &posix_resolver::do_reverse_resolve_work;
430 10x if (!svc_.pool().post(&reverse_pool_op_))
431 {
432 // Pool shut down — complete with cancellation
433 reverse_pool_op_.ref_.reset();
434 op.cancelled.store(true, std::memory_order_release);
435 svc_.post(&reverse_op_);
436 }
437 10x return std::noop_coroutine();
438 }
439
440 inline void
441 33x posix_resolver::cancel() noexcept
442 {
443 33x op_.request_cancel();
444 33x reverse_op_.request_cancel();
445 33x }
446
447 inline void
448 16x posix_resolver::do_resolve_work(pool_work_item* w) noexcept
449 {
450 16x auto* pw = static_cast<pool_op*>(w);
451 16x auto* self = pw->resolver_;
452
453 16x struct addrinfo hints{};
454 16x hints.ai_family = AF_UNSPEC;
455 16x hints.ai_socktype = SOCK_STREAM;
456 16x hints.ai_flags = posix_resolver_detail::flags_to_hints(self->op_.flags);
457
458 16x struct addrinfo* ai = nullptr;
459 48x int result = ::getaddrinfo(
460 32x self->op_.host.empty() ? nullptr : self->op_.host.c_str(),
461 32x self->op_.service.empty() ? nullptr : self->op_.service.c_str(), &hints,
462 &ai);
463
464 16x if (!self->op_.cancelled.load(std::memory_order_acquire))
465 {
466 16x if (result == 0 && ai)
467 {
468 26x self->op_.stored_results = posix_resolver_detail::convert_results(
469 13x ai, self->op_.host, self->op_.service);
470 13x self->op_.gai_error = 0;
471 }
472 else
473 {
474 3x self->op_.gai_error = result;
475 }
476 }
477
478 16x if (ai)
479 13x ::freeaddrinfo(ai);
480
481 // Move ref to stack before post — post may trigger destroy_impl
482 // which erases the last shared_ptr, destroying *self (and *pw)
483 16x auto ref = std::move(pw->ref_);
484 16x self->svc_.post(&self->op_);
485 16x }
486
487 inline void
488 10x posix_resolver::do_reverse_resolve_work(pool_work_item* w) noexcept
489 {
490 10x auto* pw = static_cast<pool_op*>(w);
491 10x auto* self = pw->resolver_;
492
493 10x sockaddr_storage ss{};
494 socklen_t ss_len;
495
496 10x if (self->reverse_op_.ep.is_v4())
497 {
498 8x auto sa = to_sockaddr_in(self->reverse_op_.ep);
499 8x std::memcpy(&ss, &sa, sizeof(sa));
500 8x ss_len = sizeof(sockaddr_in);
501 }
502 else
503 {
504 2x auto sa = to_sockaddr_in6(self->reverse_op_.ep);
505 2x std::memcpy(&ss, &sa, sizeof(sa));
506 2x ss_len = sizeof(sockaddr_in6);
507 }
508
509 char host[NI_MAXHOST];
510 char service[NI_MAXSERV];
511
512 10x int result = ::getnameinfo(
513 reinterpret_cast<sockaddr*>(&ss), ss_len, host, sizeof(host), service,
514 sizeof(service),
515 posix_resolver_detail::flags_to_ni_flags(self->reverse_op_.flags));
516
517 10x if (!self->reverse_op_.cancelled.load(std::memory_order_acquire))
518 {
519 10x if (result == 0)
520 {
521 9x self->reverse_op_.stored_host = host;
522 9x self->reverse_op_.stored_service = service;
523 9x self->reverse_op_.gai_error = 0;
524 }
525 else
526 {
527 1x self->reverse_op_.gai_error = result;
528 }
529 }
530
531 // Move ref to stack before post — post may trigger destroy_impl
532 // which erases the last shared_ptr, destroying *self (and *pw)
533 10x auto ref = std::move(pw->ref_);
534 10x self->svc_.post(&self->reverse_op_);
535 10x }
536
537 // posix_resolver_service implementation
538
539 inline void
540 515x posix_resolver_service::shutdown()
541 {
542 515x std::lock_guard<std::mutex> lock(mutex_);
543
544 // Cancel all resolvers (sets cancelled flag checked by pool threads)
545 515x for (auto* impl = resolver_list_.pop_front(); impl != nullptr;
546 impl = resolver_list_.pop_front())
547 {
548 impl->cancel();
549 }
550
551 // Clear the map which releases shared_ptrs.
552 // The thread pool service shuts down separately via
553 // execution_context service ordering.
554 515x resolver_ptrs_.clear();
555 515x }
556
557 inline io_object::implementation*
558 29x posix_resolver_service::construct()
559 {
560 29x auto ptr = std::make_shared<posix_resolver>(*this);
561 29x auto* impl = ptr.get();
562
563 {
564 29x std::lock_guard<std::mutex> lock(mutex_);
565 29x resolver_list_.push_back(impl);
566 29x resolver_ptrs_[impl] = std::move(ptr);
567 29x }
568
569 29x return impl;
570 29x }
571
572 inline void
573 29x posix_resolver_service::destroy_impl(posix_resolver& impl)
574 {
575 29x std::lock_guard<std::mutex> lock(mutex_);
576 29x resolver_list_.remove(&impl);
577 29x resolver_ptrs_.erase(&impl);
578 29x }
579
580 inline void
581 26x posix_resolver_service::post(scheduler_op* op)
582 {
583 26x sched_->post(op);
584 26x }
585
586 inline void
587 posix_resolver_service::work_started() noexcept
588 {
589 sched_->work_started();
590 }
591
592 inline void
593 26x posix_resolver_service::work_finished() noexcept
594 {
595 26x sched_->work_finished();
596 26x }
597
598 // Free function to get/create the resolver service
599
600 inline posix_resolver_service&
601 515x get_resolver_service(capy::execution_context& ctx, scheduler& sched)
602 {
603 515x return ctx.make_service<posix_resolver_service>(sched);
604 }
605
606 } // namespace boost::corosio::detail
607
608 #endif // BOOST_COROSIO_POSIX
609
610 #endif // BOOST_COROSIO_NATIVE_DETAIL_POSIX_POSIX_RESOLVER_SERVICE_HPP
611