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

98.9% Lines (268 / 271) 100.0% Functions (26 / 26)
posix_resolver_service.hpp
f(x) Functions (26)
Function Calls Lines Blocks
boost::corosio::detail::posix_resolver_service::posix_resolver_service(boost::capy::execution_context&) :42 67x 100.0% 70.0% boost::corosio::detail::posix_resolver_service::~posix_resolver_service() :48 134x 100.0% 100.0% boost::corosio::detail::posix_resolver_service::destroy(boost::corosio::io_object::implementation*) :55 131x 100.0% 100.0% boost::corosio::detail::posix_resolver_service::pool() :79 54x 100.0% 100.0% boost::corosio::detail::posix_resolver_service::resolver_unavailable() const :87 56x 100.0% 100.0% boost::corosio::detail::posix_resolver_detail::flags_to_hints(boost::corosio::resolve_flags) :105 35x 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 23x 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 68x 100.0% 100.0% boost::corosio::detail::posix_resolver::resolve_op::reset() :239 36x 100.0% 100.0% boost::corosio::detail::posix_resolver::resolve_op::operator()() :253 34x 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 37x 100.0% 100.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% 100.0% boost::corosio::detail::posix_resolver::cancel() :452 141x 100.0% 100.0% boost::corosio::detail::posix_resolver::do_resolve_work(boost::corosio::detail::pool_work_item*) :459 35x 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 67x 100.0% 80.0% boost::corosio::detail::posix_resolver_service::shutdown()::{lambda(boost::corosio::detail::posix_resolver*)#1}::operator()(boost::corosio::detail::posix_resolver*) const :563 3x 100.0% 100.0% boost::corosio::detail::posix_resolver_service::construct() :576 132x 100.0% 100.0% boost::corosio::detail::posix_resolver_service::post(boost::corosio::detail::scheduler_op*) :582 52x 100.0% 100.0% boost::corosio::detail::posix_resolver::retire() :588 131x 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/object_pool.hpp>
21 #include <boost/corosio/detail/object_ref.hpp>
22 #include <boost/corosio/detail/thread_pool.hpp>
23
24 #include <vector>
25
26 namespace boost::corosio::detail {
27
28 /** Resolver service for POSIX backends.
29
30 Owns all posix_resolver instances. Thread lifecycle is managed
31 by the thread_pool service.
32 */
33 class BOOST_COROSIO_DECL posix_resolver_service final
34 : public capy::execution_context::service
35 , public io_object::io_service
36 {
37 friend class posix_resolver;
38
39 public:
40 using key_type = posix_resolver_service;
41
42 67x explicit posix_resolver_service(capy::execution_context& ctx)
43 201x : sched_(&get_scheduler(ctx))
44 67x , pool_(ctx)
45 {
46 67x }
47
48 134x ~posix_resolver_service() override = default;
49
50 posix_resolver_service(posix_resolver_service const&) = delete;
51 posix_resolver_service& operator=(posix_resolver_service const&) = delete;
52
53 io_object::implementation* construct() override;
54
55 131x void destroy(io_object::implementation* p) override
56 {
57 131x auto& impl = static_cast<posix_resolver&>(*p);
58 131x impl.cancel();
59 131x release(&impl);
60 131x }
61
62 void shutdown() override;
63
64 void post(scheduler_op* op);
65
66 /** Return the resolver thread pool.
67
68 The pool's service is created on first use, so this can fail
69 where a plain accessor could not. Its workers start later, on
70 the first post, and a thread the system refuses there is
71 reported by that post rather than thrown here.
72
73 @throws std::bad_alloc If the service cannot be allocated.
74
75 @return The context's shared blocking-I/O pool.
76
77 @see thread_pool_ref::get
78 */
79 54x thread_pool& pool()
80 {
81 54x return pool_.get();
82 }
83
84 /// True when the resolver thread pool is unavailable: the `unsafe` tier,
85 /// whose lockless scheduler cannot accept the pool's cross-thread
86 /// completions.
87 56x bool resolver_unavailable() const noexcept
88 {
89 56x return sched_->scheduler_locking_disabled();
90 }
91
92 private:
93 scheduler* sched_;
94 thread_pool_ref pool_;
95 object_pool<posix_resolver> object_pool_;
96 };
97
98 // ---------------------------------------------------------------------------
99 // Inline implementation
100 // ---------------------------------------------------------------------------
101
102 // posix_resolver_detail helpers
103
104 inline int
105 35x posix_resolver_detail::flags_to_hints(resolve_flags flags)
106 {
107 35x int hints = 0;
108
109 35x if ((flags & resolve_flags::passive) != resolve_flags::none)
110 1x hints |= AI_PASSIVE;
111 35x if ((flags & resolve_flags::numeric_host) != resolve_flags::none)
112 18x hints |= AI_NUMERICHOST;
113 35x if ((flags & resolve_flags::numeric_service) != resolve_flags::none)
114 12x hints |= AI_NUMERICSERV;
115 35x if ((flags & resolve_flags::address_configured) != resolve_flags::none)
116 1x hints |= AI_ADDRCONFIG;
117 35x if ((flags & resolve_flags::v4_mapped) != resolve_flags::none)
118 1x hints |= AI_V4MAPPED;
119 35x if ((flags & resolve_flags::all_matching) != resolve_flags::none)
120 1x hints |= AI_ALL;
121
122 35x 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 23x posix_resolver_detail::convert_results(struct addrinfo* ai)
144 {
145 23x std::vector<endpoint> endpoints;
146 23x endpoints.reserve(4); // Most lookups return 1-4 addresses
147
148 46x for (auto* p = ai; p != nullptr; p = p->ai_next)
149 {
150 23x if (p->ai_family == AF_INET)
151 {
152 20x auto* addr = reinterpret_cast<sockaddr_in*>(p->ai_addr);
153 20x 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 23x 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 68x inline posix_resolver::posix_resolver(posix_resolver_service& svc) noexcept
232 68x : svc_(svc)
233 {
234 68x }
235
236 // posix_resolver::resolve_op implementation
237
238 inline void
239 36x posix_resolver::resolve_op::reset() noexcept
240 {
241 36x host.clear();
242 36x service.clear();
243 36x flags = resolve_flags::none;
244 36x stored_results = std::vector<endpoint>{};
245 36x gai_error = 0;
246 36x cancelled.store(false, std::memory_order_relaxed);
247 36x stop_cb.reset();
248 36x ec_out = nullptr;
249 36x out = nullptr;
250 36x }
251
252 inline void
253 34x posix_resolver::resolve_op::operator()()
254 {
255 34x stop_cb.reset(); // Disconnect stop callback
256
257 34x bool const was_cancelled = cancelled.load(std::memory_order_acquire);
258
259 34x if (ec_out)
260 {
261 34x if (was_cancelled)
262 ✗ *ec_out = capy::error::canceled;
263 34x else if (gai_error != 0)
264 11x *ec_out = posix_resolver_detail::make_gai_error(gai_error);
265 else
266 23x *ec_out = {}; // Clear on success
267 }
268
269 34x if (out && !was_cancelled && gai_error == 0)
270 23x *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 34x auto prevent_destroy = std::move(object_ref_);
275 34x ex.on_work_finished();
276 34x cont.h = h;
277 34x dispatch_coro(ex, cont).resume();
278 34x }
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 object_ref_.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(object_ref_);
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 object_ref_.reset();
344 1x local_ex.on_work_finished();
345 1x }
346
347 // posix_resolver implementation
348
349 inline std::coroutine_handle<>
350 37x 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 37x 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 36x auto& op = op_;
368 36x op.reset();
369 36x op.h = h;
370 36x op.ex = ex;
371 36x op.ec_out = ec;
372 36x op.out = out;
373 36x op.host = host;
374 36x op.service = service;
375 36x op.flags = flags;
376 36x op.start(token);
377
378 // Keep io_context alive while resolution is pending
379 36x op.ex.on_work_started();
380
381 // Prevent impl destruction while work is in flight
382 36x resolve_pool_op_.resolver_ = this;
383 36x resolve_pool_op_.ref_ = detail::object_ref(this);
384 36x resolve_pool_op_.func_ = &posix_resolver::do_resolve_work;
385 36x 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 35x 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_ = detail::object_ref(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 141x posix_resolver::cancel() noexcept
453 {
454 141x op_.request_cancel();
455 141x reverse_op_.request_cancel();
456 141x }
457
458 inline void
459 35x posix_resolver::do_resolve_work(pool_work_item* w) noexcept
460 {
461 35x auto* pw = static_cast<pool_op*>(w);
462 35x auto* self = pw->resolver_;
463
464 35x struct addrinfo hints{};
465 35x hints.ai_family = AF_UNSPEC;
466 35x hints.ai_socktype = SOCK_STREAM;
467 35x hints.ai_flags = posix_resolver_detail::flags_to_hints(self->op_.flags);
468
469 35x struct addrinfo* ai = nullptr;
470 105x int result = ::getaddrinfo(
471 70x self->op_.host.empty() ? nullptr : self->op_.host.c_str(),
472 65x self->op_.service.empty() ? nullptr : self->op_.service.c_str(), &hints,
473 &ai);
474
475 35x if (!self->op_.cancelled.load(std::memory_order_acquire))
476 {
477 34x if (result == 0 && ai)
478 {
479 self->op_.stored_results =
480 23x posix_resolver_detail::convert_results(ai);
481 23x self->op_.gai_error = 0;
482 }
483 else
484 {
485 11x self->op_.gai_error = result;
486 }
487 }
488
489 35x if (ai)
490 24x ::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 35x self->op_.object_ref_ = std::move(pw->ref_);
496 35x self->svc_.post(&self->op_);
497 35x }
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_.object_ref_ = 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 67x posix_resolver_service::shutdown()
554 {
555 // See uring_socket_service_base::shutdown(): snapshot under an
556 // acquired reference, cancel without the pool lock held; shutdown()
557 // sets shutting-down and takes the snapshot in one critical
558 // section, so a cancel that drops the last ref deletes rather than
559 // recycles. The thread pool service shuts down separately via
560 // execution_context ordering.
561 67x std::vector<posix_resolver*> live;
562 67x object_pool_.shutdown(
563 67x [&](posix_resolver* r)
564 {
565 3x acquire(r);
566 3x live.push_back(r);
567 3x });
568 70x for (auto* r : live)
569 {
570 3x r->cancel();
571 3x release(r);
572 }
573 67x }
574
575 inline io_object::implementation*
576 132x posix_resolver_service::construct()
577 {
578 132x return object_pool_.acquire(*this);
579 }
580
581 inline void
582 52x posix_resolver_service::post(scheduler_op* op)
583 {
584 52x sched_->post(op);
585 52x }
586
587 inline void
588 131x posix_resolver::retire() noexcept
589 {
590 131x svc_.object_pool_.recycle(this);
591 131x }
592
593 } // namespace boost::corosio::detail
594
595 #endif // BOOST_COROSIO_POSIX
596
597 #endif // BOOST_COROSIO_NATIVE_DETAIL_POSIX_POSIX_RESOLVER_SERVICE_HPP
598