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

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