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