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