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 515 : posix_resolver_service(capy::execution_context& ctx, scheduler& sched)
37 1030 : : sched_(&sched)
38 515 : , pool_(ctx.make_service<thread_pool>())
39 : {
40 515 : }
41 :
42 1030 : ~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 : cont_op.cont.h = h;
269 16 : dispatch_coro(ex, cont_op.cont).resume();
270 16 : }
271 :
272 : inline void
273 MIS 0 : posix_resolver::resolve_op::destroy()
274 : {
275 0 : stop_cb.reset();
276 0 : }
277 :
278 : inline void
279 HIT 33 : posix_resolver::resolve_op::request_cancel() noexcept
280 : {
281 33 : cancelled.store(true, std::memory_order_release);
282 33 : }
283 :
284 : inline void
285 16 : posix_resolver::resolve_op::start(std::stop_token const& token)
286 : {
287 16 : cancelled.store(false, std::memory_order_release);
288 16 : stop_cb.reset();
289 :
290 16 : if (token.stop_possible())
291 MIS 0 : stop_cb.emplace(token, canceller{this});
292 HIT 16 : }
293 :
294 : // posix_resolver::reverse_resolve_op implementation
295 :
296 : inline void
297 10 : posix_resolver::reverse_resolve_op::reset() noexcept
298 : {
299 10 : ep = endpoint{};
300 10 : flags = reverse_flags::none;
301 10 : stored_host.clear();
302 10 : stored_service.clear();
303 10 : gai_error = 0;
304 10 : cancelled.store(false, std::memory_order_relaxed);
305 10 : stop_cb.reset();
306 10 : ec_out = nullptr;
307 10 : result_out = nullptr;
308 10 : }
309 :
310 : inline void
311 10 : posix_resolver::reverse_resolve_op::operator()()
312 : {
313 10 : stop_cb.reset(); // Disconnect stop callback
314 :
315 10 : bool const was_cancelled = cancelled.load(std::memory_order_acquire);
316 :
317 10 : if (ec_out)
318 : {
319 10 : if (was_cancelled)
320 MIS 0 : *ec_out = capy::error::canceled;
321 HIT 10 : else if (gai_error != 0)
322 1 : *ec_out = posix_resolver_detail::make_gai_error(gai_error);
323 : else
324 9 : *ec_out = {}; // Clear on success
325 : }
326 :
327 10 : if (result_out && !was_cancelled && gai_error == 0)
328 : {
329 27 : *result_out = reverse_resolver_result(
330 27 : ep, std::move(stored_host), std::move(stored_service));
331 : }
332 :
333 10 : impl->svc_.work_finished();
334 10 : cont_op.cont.h = h;
335 10 : dispatch_coro(ex, cont_op.cont).resume();
336 10 : }
337 :
338 : inline void
339 MIS 0 : posix_resolver::reverse_resolve_op::destroy()
340 : {
341 0 : stop_cb.reset();
342 0 : }
343 :
344 : inline void
345 HIT 33 : posix_resolver::reverse_resolve_op::request_cancel() noexcept
346 : {
347 33 : cancelled.store(true, std::memory_order_release);
348 33 : }
349 :
350 : inline void
351 10 : posix_resolver::reverse_resolve_op::start(std::stop_token const& token)
352 : {
353 10 : cancelled.store(false, std::memory_order_release);
354 10 : stop_cb.reset();
355 :
356 10 : if (token.stop_possible())
357 MIS 0 : stop_cb.emplace(token, canceller{this});
358 HIT 10 : }
359 :
360 : // posix_resolver implementation
361 :
362 : inline std::coroutine_handle<>
363 16 : posix_resolver::resolve(
364 : std::coroutine_handle<> h,
365 : capy::executor_ref ex,
366 : std::string_view host,
367 : std::string_view service,
368 : resolve_flags flags,
369 : std::stop_token token,
370 : std::error_code* ec,
371 : resolver_results* out)
372 : {
373 16 : auto& op = op_;
374 16 : op.reset();
375 16 : op.h = h;
376 16 : op.ex = ex;
377 16 : op.impl = this;
378 16 : op.ec_out = ec;
379 16 : op.out = out;
380 16 : op.host = host;
381 16 : op.service = service;
382 16 : op.flags = flags;
383 16 : op.start(token);
384 :
385 : // Keep io_context alive while resolution is pending
386 16 : op.ex.on_work_started();
387 :
388 : // Prevent impl destruction while work is in flight
389 16 : resolve_pool_op_.resolver_ = this;
390 16 : resolve_pool_op_.ref_ = this->shared_from_this();
391 16 : resolve_pool_op_.func_ = &posix_resolver::do_resolve_work;
392 16 : if (!svc_.pool().post(&resolve_pool_op_))
393 : {
394 : // Pool shut down — complete with cancellation
395 MIS 0 : resolve_pool_op_.ref_.reset();
396 0 : op.cancelled.store(true, std::memory_order_release);
397 0 : svc_.post(&op_);
398 : }
399 HIT 16 : return std::noop_coroutine();
400 : }
401 :
402 : inline std::coroutine_handle<>
403 10 : posix_resolver::reverse_resolve(
404 : std::coroutine_handle<> h,
405 : capy::executor_ref ex,
406 : endpoint const& ep,
407 : reverse_flags flags,
408 : std::stop_token token,
409 : std::error_code* ec,
410 : reverse_resolver_result* result_out)
411 : {
412 10 : auto& op = reverse_op_;
413 10 : op.reset();
414 10 : op.h = h;
415 10 : op.ex = ex;
416 10 : op.impl = this;
417 10 : op.ec_out = ec;
418 10 : op.result_out = result_out;
419 10 : op.ep = ep;
420 10 : op.flags = flags;
421 10 : op.start(token);
422 :
423 : // Keep io_context alive while resolution is pending
424 10 : op.ex.on_work_started();
425 :
426 : // Prevent impl destruction while work is in flight
427 10 : reverse_pool_op_.resolver_ = this;
428 10 : reverse_pool_op_.ref_ = this->shared_from_this();
429 10 : reverse_pool_op_.func_ = &posix_resolver::do_reverse_resolve_work;
430 10 : if (!svc_.pool().post(&reverse_pool_op_))
431 : {
432 : // Pool shut down — complete with cancellation
433 MIS 0 : reverse_pool_op_.ref_.reset();
434 0 : op.cancelled.store(true, std::memory_order_release);
435 0 : svc_.post(&reverse_op_);
436 : }
437 HIT 10 : return std::noop_coroutine();
438 : }
439 :
440 : inline void
441 33 : posix_resolver::cancel() noexcept
442 : {
443 33 : op_.request_cancel();
444 33 : reverse_op_.request_cancel();
445 33 : }
446 :
447 : inline void
448 16 : posix_resolver::do_resolve_work(pool_work_item* w) noexcept
449 : {
450 16 : auto* pw = static_cast<pool_op*>(w);
451 16 : auto* self = pw->resolver_;
452 :
453 16 : struct addrinfo hints{};
454 16 : hints.ai_family = AF_UNSPEC;
455 16 : hints.ai_socktype = SOCK_STREAM;
456 16 : hints.ai_flags = posix_resolver_detail::flags_to_hints(self->op_.flags);
457 :
458 16 : struct addrinfo* ai = nullptr;
459 48 : int result = ::getaddrinfo(
460 32 : self->op_.host.empty() ? nullptr : self->op_.host.c_str(),
461 32 : self->op_.service.empty() ? nullptr : self->op_.service.c_str(), &hints,
462 : &ai);
463 :
464 16 : if (!self->op_.cancelled.load(std::memory_order_acquire))
465 : {
466 16 : if (result == 0 && ai)
467 : {
468 26 : self->op_.stored_results = posix_resolver_detail::convert_results(
469 13 : ai, self->op_.host, self->op_.service);
470 13 : self->op_.gai_error = 0;
471 : }
472 : else
473 : {
474 3 : self->op_.gai_error = result;
475 : }
476 : }
477 :
478 16 : if (ai)
479 13 : ::freeaddrinfo(ai);
480 :
481 : // Move ref to stack before post — post may trigger destroy_impl
482 : // which erases the last shared_ptr, destroying *self (and *pw)
483 16 : auto ref = std::move(pw->ref_);
484 16 : self->svc_.post(&self->op_);
485 16 : }
486 :
487 : inline void
488 10 : posix_resolver::do_reverse_resolve_work(pool_work_item* w) noexcept
489 : {
490 10 : auto* pw = static_cast<pool_op*>(w);
491 10 : auto* self = pw->resolver_;
492 :
493 10 : sockaddr_storage ss{};
494 : socklen_t ss_len;
495 :
496 10 : if (self->reverse_op_.ep.is_v4())
497 : {
498 8 : auto sa = to_sockaddr_in(self->reverse_op_.ep);
499 8 : std::memcpy(&ss, &sa, sizeof(sa));
500 8 : ss_len = sizeof(sockaddr_in);
501 : }
502 : else
503 : {
504 2 : auto sa = to_sockaddr_in6(self->reverse_op_.ep);
505 2 : std::memcpy(&ss, &sa, sizeof(sa));
506 2 : ss_len = sizeof(sockaddr_in6);
507 : }
508 :
509 : char host[NI_MAXHOST];
510 : char service[NI_MAXSERV];
511 :
512 10 : int result = ::getnameinfo(
513 : reinterpret_cast<sockaddr*>(&ss), ss_len, host, sizeof(host), service,
514 : sizeof(service),
515 : posix_resolver_detail::flags_to_ni_flags(self->reverse_op_.flags));
516 :
517 10 : if (!self->reverse_op_.cancelled.load(std::memory_order_acquire))
518 : {
519 10 : if (result == 0)
520 : {
521 9 : self->reverse_op_.stored_host = host;
522 9 : self->reverse_op_.stored_service = service;
523 9 : self->reverse_op_.gai_error = 0;
524 : }
525 : else
526 : {
527 1 : self->reverse_op_.gai_error = result;
528 : }
529 : }
530 :
531 : // Move ref to stack before post — post may trigger destroy_impl
532 : // which erases the last shared_ptr, destroying *self (and *pw)
533 10 : auto ref = std::move(pw->ref_);
534 10 : self->svc_.post(&self->reverse_op_);
535 10 : }
536 :
537 : // posix_resolver_service implementation
538 :
539 : inline void
540 515 : posix_resolver_service::shutdown()
541 : {
542 515 : std::lock_guard<std::mutex> lock(mutex_);
543 :
544 : // Cancel all resolvers (sets cancelled flag checked by pool threads)
545 515 : for (auto* impl = resolver_list_.pop_front(); impl != nullptr;
546 MIS 0 : impl = resolver_list_.pop_front())
547 : {
548 0 : impl->cancel();
549 : }
550 :
551 : // Clear the map which releases shared_ptrs.
552 : // The thread pool service shuts down separately via
553 : // execution_context service ordering.
554 HIT 515 : resolver_ptrs_.clear();
555 515 : }
556 :
557 : inline io_object::implementation*
558 29 : posix_resolver_service::construct()
559 : {
560 29 : auto ptr = std::make_shared<posix_resolver>(*this);
561 29 : auto* impl = ptr.get();
562 :
563 : {
564 29 : std::lock_guard<std::mutex> lock(mutex_);
565 29 : resolver_list_.push_back(impl);
566 29 : resolver_ptrs_[impl] = std::move(ptr);
567 29 : }
568 :
569 29 : return impl;
570 29 : }
571 :
572 : inline void
573 29 : posix_resolver_service::destroy_impl(posix_resolver& impl)
574 : {
575 29 : std::lock_guard<std::mutex> lock(mutex_);
576 29 : resolver_list_.remove(&impl);
577 29 : resolver_ptrs_.erase(&impl);
578 29 : }
579 :
580 : inline void
581 26 : posix_resolver_service::post(scheduler_op* op)
582 : {
583 26 : sched_->post(op);
584 26 : }
585 :
586 : inline void
587 : posix_resolver_service::work_started() noexcept
588 : {
589 : sched_->work_started();
590 : }
591 :
592 : inline void
593 26 : posix_resolver_service::work_finished() noexcept
594 : {
595 26 : sched_->work_finished();
596 26 : }
597 :
598 : // Free function to get/create the resolver service
599 :
600 : inline posix_resolver_service&
601 515 : get_resolver_service(capy::execution_context& ctx, scheduler& sched)
602 : {
603 515 : return ctx.make_service<posix_resolver_service>(sched);
604 : }
605 :
606 : } // namespace boost::corosio::detail
607 :
608 : #endif // BOOST_COROSIO_POSIX
609 :
610 : #endif // BOOST_COROSIO_NATIVE_DETAIL_POSIX_POSIX_RESOLVER_SERVICE_HPP
|