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