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