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