TLA Line data Source code
1 : //
2 : // Copyright (c) 2026 Steve Gerbino
3 : // Copyright (c) 2026 Michael Vandeberg
4 : //
5 : // Distributed under the Boost Software License, Version 1.0. (See accompanying
6 : // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
7 : //
8 : // Official repository: https://github.com/cppalliance/corosio
9 : //
10 :
11 : #ifndef BOOST_COROSIO_NATIVE_DETAIL_POSIX_POSIX_RESOLVER_SERVICE_HPP
12 : #define BOOST_COROSIO_NATIVE_DETAIL_POSIX_POSIX_RESOLVER_SERVICE_HPP
13 :
14 : #include <boost/corosio/detail/platform.hpp>
15 :
16 : #if BOOST_COROSIO_POSIX
17 :
18 : #include <boost/corosio/native/detail/posix/posix_resolver.hpp>
19 : #include <boost/corosio/native/detail/reactor/reactor_scheduler.hpp>
20 : #include <boost/corosio/detail/object_pool.hpp>
21 : #include <boost/corosio/detail/object_ref.hpp>
22 : #include <boost/corosio/detail/thread_pool.hpp>
23 :
24 : #include <vector>
25 :
26 : namespace boost::corosio::detail {
27 :
28 : /** Resolver service for POSIX backends.
29 :
30 : Owns all posix_resolver instances. Thread lifecycle is managed
31 : by the thread_pool service.
32 : */
33 : class BOOST_COROSIO_DECL posix_resolver_service final
34 : : public capy::execution_context::service
35 : , public io_object::io_service
36 : {
37 : friend class posix_resolver;
38 :
39 : public:
40 : using key_type = posix_resolver_service;
41 :
42 HIT 77 : explicit posix_resolver_service(capy::execution_context& ctx)
43 231 : : sched_(&get_scheduler(ctx))
44 77 : , pool_(ctx)
45 : {
46 77 : }
47 :
48 154 : ~posix_resolver_service() override = default;
49 :
50 : posix_resolver_service(posix_resolver_service const&) = delete;
51 : posix_resolver_service& operator=(posix_resolver_service const&) = delete;
52 :
53 : io_object::implementation* construct() override;
54 :
55 142 : void destroy(io_object::implementation* p) override
56 : {
57 142 : auto& impl = static_cast<posix_resolver&>(*p);
58 142 : impl.cancel();
59 142 : release(&impl);
60 142 : }
61 :
62 : void shutdown() override;
63 :
64 : void post(scheduler_op* op);
65 :
66 : /** Return the resolver thread pool.
67 :
68 : The pool's service is created on first use, so this can fail
69 : where a plain accessor could not. Its workers start later, on
70 : the first post, and a thread the system refuses there is
71 : reported by that post rather than thrown here.
72 :
73 : @throws std::bad_alloc If the service cannot be allocated.
74 :
75 : @return The context's shared blocking-I/O pool.
76 :
77 : @see thread_pool_ref::get
78 : */
79 62 : thread_pool& pool()
80 : {
81 62 : return pool_.get();
82 : }
83 :
84 : /// True when the resolver thread pool is unavailable: the `unsafe` tier,
85 : /// whose lockless scheduler cannot accept the pool's cross-thread
86 : /// completions.
87 64 : bool resolver_unavailable() const noexcept
88 : {
89 64 : return sched_->scheduler_locking_disabled();
90 : }
91 :
92 : /// Count an operation as work on this service's context.
93 62 : void work_started() noexcept
94 : {
95 62 : sched_->work_started();
96 62 : }
97 :
98 : /// Release a count taken by @ref work_started.
99 62 : void work_finished() noexcept
100 : {
101 62 : sched_->work_finished();
102 62 : }
103 :
104 : private:
105 : scheduler* sched_;
106 : thread_pool_ref pool_;
107 : object_pool<posix_resolver> object_pool_;
108 : };
109 :
110 : // ---------------------------------------------------------------------------
111 : // Inline implementation
112 : // ---------------------------------------------------------------------------
113 :
114 : // posix_resolver_detail helpers
115 :
116 : inline int
117 38 : posix_resolver_detail::flags_to_hints(resolve_flags flags)
118 : {
119 38 : int hints = 0;
120 :
121 38 : if ((flags & resolve_flags::passive) != resolve_flags::none)
122 1 : hints |= AI_PASSIVE;
123 38 : if ((flags & resolve_flags::numeric_host) != resolve_flags::none)
124 21 : hints |= AI_NUMERICHOST;
125 38 : if ((flags & resolve_flags::numeric_service) != resolve_flags::none)
126 15 : hints |= AI_NUMERICSERV;
127 38 : if ((flags & resolve_flags::address_configured) != resolve_flags::none)
128 1 : hints |= AI_ADDRCONFIG;
129 38 : if ((flags & resolve_flags::v4_mapped) != resolve_flags::none)
130 1 : hints |= AI_V4MAPPED;
131 38 : if ((flags & resolve_flags::all_matching) != resolve_flags::none)
132 1 : hints |= AI_ALL;
133 :
134 38 : return hints;
135 : }
136 :
137 : inline int
138 16 : posix_resolver_detail::flags_to_ni_flags(reverse_flags flags)
139 : {
140 16 : int ni_flags = 0;
141 :
142 16 : if ((flags & reverse_flags::numeric_host) != reverse_flags::none)
143 6 : ni_flags |= NI_NUMERICHOST;
144 16 : if ((flags & reverse_flags::numeric_service) != reverse_flags::none)
145 6 : ni_flags |= NI_NUMERICSERV;
146 16 : if ((flags & reverse_flags::name_required) != reverse_flags::none)
147 1 : ni_flags |= NI_NAMEREQD;
148 16 : if ((flags & reverse_flags::datagram_service) != reverse_flags::none)
149 1 : ni_flags |= NI_DGRAM;
150 :
151 16 : return ni_flags;
152 : }
153 :
154 : inline std::vector<endpoint>
155 25 : posix_resolver_detail::convert_results(struct addrinfo* ai)
156 : {
157 25 : std::vector<endpoint> endpoints;
158 25 : endpoints.reserve(4); // Most lookups return 1-4 addresses
159 :
160 50 : for (auto* p = ai; p != nullptr; p = p->ai_next)
161 : {
162 25 : if (p->ai_family == AF_INET)
163 : {
164 22 : auto* addr = reinterpret_cast<sockaddr_in*>(p->ai_addr);
165 22 : endpoints.push_back(from_sockaddr_in(*addr));
166 : }
167 3 : else if (p->ai_family == AF_INET6)
168 : {
169 3 : auto* addr = reinterpret_cast<sockaddr_in6*>(p->ai_addr);
170 3 : endpoints.push_back(from_sockaddr_in6(*addr));
171 : }
172 : }
173 :
174 25 : return endpoints;
175 MIS 0 : }
176 :
177 : inline std::error_code
178 HIT 26 : posix_resolver_detail::make_gai_error(int gai_err)
179 : {
180 : // Map GAI errors to appropriate generic error codes
181 26 : switch (gai_err)
182 : {
183 1 : case EAI_AGAIN:
184 : // Temporary failure - try again later
185 1 : return std::error_code(
186 : static_cast<int>(std::errc::resource_unavailable_try_again),
187 1 : std::generic_category());
188 :
189 1 : case EAI_BADFLAGS:
190 : // Invalid flags
191 1 : return std::error_code(
192 : static_cast<int>(std::errc::invalid_argument),
193 1 : std::generic_category());
194 :
195 11 : case EAI_FAIL:
196 : // Non-recoverable failure
197 11 : return std::error_code(
198 11 : static_cast<int>(std::errc::io_error), std::generic_category());
199 :
200 1 : case EAI_FAMILY:
201 : // Address family not supported
202 1 : return std::error_code(
203 : static_cast<int>(std::errc::address_family_not_supported),
204 1 : std::generic_category());
205 :
206 1 : case EAI_MEMORY:
207 : // Memory allocation failure
208 1 : return std::error_code(
209 : static_cast<int>(std::errc::not_enough_memory),
210 1 : std::generic_category());
211 :
212 7 : case EAI_NONAME:
213 : // Host or service not found
214 7 : return std::error_code(
215 : static_cast<int>(std::errc::no_such_device_or_address),
216 7 : std::generic_category());
217 :
218 1 : case EAI_SERVICE:
219 : // Service not supported for socket type
220 1 : return std::error_code(
221 : static_cast<int>(std::errc::invalid_argument),
222 1 : std::generic_category());
223 :
224 1 : case EAI_SOCKTYPE:
225 : // Socket type not supported
226 1 : return std::error_code(
227 : static_cast<int>(std::errc::not_supported),
228 1 : std::generic_category());
229 :
230 1 : case EAI_SYSTEM:
231 : // System error - use errno
232 1 : return std::error_code(errno, std::generic_category());
233 :
234 1 : default:
235 : // Unknown error
236 1 : return std::error_code(
237 1 : static_cast<int>(std::errc::io_error), std::generic_category());
238 : }
239 : }
240 :
241 : // posix_resolver
242 :
243 78 : inline posix_resolver::posix_resolver(posix_resolver_service& svc) noexcept
244 78 : : svc_(svc)
245 : {
246 78 : }
247 :
248 : // posix_resolver::resolve_op implementation
249 :
250 : inline void
251 42 : posix_resolver::resolve_op::reset() noexcept
252 : {
253 42 : host.clear();
254 42 : service.clear();
255 42 : flags = resolve_flags::none;
256 42 : stored_results = std::vector<endpoint>{};
257 42 : gai_error = 0;
258 42 : cancelled.store(false, std::memory_order_relaxed);
259 42 : stop_cb.reset();
260 42 : ec_out = nullptr;
261 42 : out = nullptr;
262 42 : }
263 :
264 : inline void
265 38 : posix_resolver::resolve_op::operator()()
266 : {
267 38 : stop_cb.reset(); // Disconnect stop callback
268 :
269 38 : bool const was_cancelled = cancelled.load(std::memory_order_acquire);
270 :
271 38 : if (ec_out)
272 : {
273 38 : if (was_cancelled)
274 2 : *ec_out = capy::error::canceled;
275 36 : else if (gai_error != 0)
276 11 : *ec_out = posix_resolver_detail::make_gai_error(gai_error);
277 : else
278 25 : *ec_out = {}; // Clear on success
279 : }
280 :
281 38 : if (out && !was_cancelled && gai_error == 0)
282 25 : *out = std::move(stored_results);
283 :
284 : // Hold the keepalive across the dispatch: it may be the last
285 : // reference to the implementation this op is embedded in.
286 38 : auto prevent_destroy = std::move(object_ref_);
287 38 : svc->work_finished();
288 38 : dispatch_coro(ex, *cont).resume();
289 38 : }
290 :
291 : inline void
292 3 : posix_resolver::resolve_op::destroy()
293 : {
294 : // Copied first: the abandon may unwind the frame at once, destroying
295 : // the resolver, and with it this op.
296 3 : auto* s = svc;
297 3 : destroy_awaiting();
298 3 : s->work_finished();
299 3 : }
300 :
301 : // posix_resolver::reverse_resolve_op implementation
302 :
303 : inline void
304 20 : posix_resolver::reverse_resolve_op::reset() noexcept
305 : {
306 20 : ep = endpoint{};
307 20 : flags = reverse_flags::none;
308 20 : stored_host.clear();
309 20 : stored_service.clear();
310 20 : gai_error = 0;
311 20 : cancelled.store(false, std::memory_order_relaxed);
312 20 : stop_cb.reset();
313 20 : ec_out = nullptr;
314 20 : result_out = nullptr;
315 20 : }
316 :
317 : inline void
318 16 : posix_resolver::reverse_resolve_op::operator()()
319 : {
320 16 : stop_cb.reset(); // Disconnect stop callback
321 :
322 16 : bool const was_cancelled = cancelled.load(std::memory_order_acquire);
323 :
324 16 : if (ec_out)
325 : {
326 16 : if (was_cancelled)
327 MIS 0 : *ec_out = capy::error::canceled;
328 HIT 16 : else if (gai_error != 0)
329 6 : *ec_out = posix_resolver_detail::make_gai_error(gai_error);
330 : else
331 10 : *ec_out = {}; // Clear on success
332 : }
333 :
334 16 : if (result_out && !was_cancelled && gai_error == 0)
335 : {
336 10 : *result_out =
337 10 : endpoint_name{std::move(stored_host), std::move(stored_service)};
338 : }
339 :
340 : // Hold the keepalive across the dispatch: it may be the last
341 : // reference to the implementation this op is embedded in.
342 16 : auto prevent_destroy = std::move(object_ref_);
343 16 : svc->work_finished();
344 16 : dispatch_coro(ex, *cont).resume();
345 16 : }
346 :
347 : inline void
348 3 : posix_resolver::reverse_resolve_op::destroy()
349 : {
350 : // Copied first: the abandon may unwind the frame at once, destroying
351 : // the resolver, and with it this op.
352 3 : auto* s = svc;
353 3 : destroy_awaiting();
354 3 : s->work_finished();
355 3 : }
356 :
357 : // posix_resolver implementation
358 :
359 : inline std::coroutine_handle<>
360 43 : posix_resolver::resolve(
361 : capy::continuation& cont,
362 : capy::executor_ref ex,
363 : std::string_view host,
364 : std::string_view service,
365 : resolve_flags flags,
366 : std::stop_token token,
367 : std::error_code* ec,
368 : std::vector<endpoint>* out)
369 : {
370 43 : if (svc_.resolver_unavailable())
371 : {
372 1 : *ec = std::make_error_code(std::errc::operation_not_supported);
373 1 : return dispatch_coro(ex, cont);
374 : }
375 :
376 42 : auto& op = op_;
377 42 : op.reset();
378 42 : op.cont = &cont;
379 42 : op.ex = ex;
380 42 : op.ec_out = ec;
381 42 : op.out = out;
382 42 : op.host = host;
383 42 : op.service = service;
384 42 : op.flags = flags;
385 42 : op.start(token);
386 :
387 : // Keep this context alive while resolution is pending
388 42 : op.svc = &svc_;
389 42 : svc_.work_started();
390 :
391 : // Prevent impl destruction while work is in flight
392 42 : resolve_pool_op_.resolver_ = this;
393 42 : resolve_pool_op_.ref_ = detail::object_ref(this);
394 42 : resolve_pool_op_.func_ = &posix_resolver::do_resolve_work;
395 42 : resolve_pool_op_.destroy_ = &posix_resolver::destroy_resolve_work;
396 42 : if (auto pec = svc_.pool().post(&resolve_pool_op_))
397 : {
398 : // The pool is shutting down, or the system refused it a thread.
399 : // Nothing of this resolve went cross-thread, so it answers here
400 : // like the no-resolver exit above rather than through a
401 : // completion the scheduler has to carry back.
402 1 : resolve_pool_op_.ref_.reset();
403 1 : op.stop_cb.reset();
404 1 : svc_.work_finished();
405 1 : *ec = pec;
406 1 : return dispatch_coro(ex, cont);
407 : }
408 41 : return std::noop_coroutine();
409 : }
410 :
411 : inline std::coroutine_handle<>
412 21 : posix_resolver::reverse_resolve(
413 : capy::continuation& cont,
414 : capy::executor_ref ex,
415 : endpoint const& ep,
416 : reverse_flags flags,
417 : std::stop_token token,
418 : std::error_code* ec,
419 : endpoint_name* result_out)
420 : {
421 21 : if (svc_.resolver_unavailable())
422 : {
423 1 : *ec = std::make_error_code(std::errc::operation_not_supported);
424 1 : return dispatch_coro(ex, cont);
425 : }
426 :
427 20 : auto& op = reverse_op_;
428 20 : op.reset();
429 20 : op.cont = &cont;
430 20 : op.ex = ex;
431 20 : op.ec_out = ec;
432 20 : op.result_out = result_out;
433 20 : op.ep = ep;
434 20 : op.flags = flags;
435 20 : op.start(token);
436 :
437 : // Keep this context alive while resolution is pending
438 20 : op.svc = &svc_;
439 20 : svc_.work_started();
440 :
441 : // Prevent impl destruction while work is in flight
442 20 : reverse_pool_op_.resolver_ = this;
443 20 : reverse_pool_op_.ref_ = detail::object_ref(this);
444 20 : reverse_pool_op_.func_ = &posix_resolver::do_reverse_resolve_work;
445 20 : reverse_pool_op_.destroy_ = &posix_resolver::destroy_reverse_resolve_work;
446 20 : if (auto pec = svc_.pool().post(&reverse_pool_op_))
447 : {
448 : // The pool is shutting down, or the system refused it a thread.
449 : // Nothing of this resolve went cross-thread, so it answers here
450 : // like the no-resolver exit above rather than through a
451 : // completion the scheduler has to carry back.
452 1 : reverse_pool_op_.ref_.reset();
453 1 : op.stop_cb.reset();
454 1 : svc_.work_finished();
455 1 : *ec = pec;
456 1 : return dispatch_coro(ex, cont);
457 : }
458 19 : return std::noop_coroutine();
459 : }
460 :
461 : inline void
462 152 : posix_resolver::cancel() noexcept
463 : {
464 152 : op_.request_cancel();
465 152 : reverse_op_.request_cancel();
466 152 : }
467 :
468 : inline void
469 38 : posix_resolver::do_resolve_work(pool_work_item* w) noexcept
470 : {
471 38 : auto* pw = static_cast<pool_op*>(w);
472 38 : auto* self = pw->resolver_;
473 :
474 38 : struct addrinfo hints{};
475 38 : hints.ai_family = AF_UNSPEC;
476 38 : hints.ai_socktype = SOCK_STREAM;
477 38 : hints.ai_flags = posix_resolver_detail::flags_to_hints(self->op_.flags);
478 :
479 38 : struct addrinfo* ai = nullptr;
480 114 : int result = ::getaddrinfo(
481 76 : self->op_.host.empty() ? nullptr : self->op_.host.c_str(),
482 71 : self->op_.service.empty() ? nullptr : self->op_.service.c_str(), &hints,
483 : &ai);
484 :
485 38 : if (!self->op_.cancelled.load(std::memory_order_acquire))
486 : {
487 36 : if (result == 0 && ai)
488 : {
489 : self->op_.stored_results =
490 25 : posix_resolver_detail::convert_results(ai);
491 25 : self->op_.gai_error = 0;
492 : }
493 : else
494 : {
495 11 : self->op_.gai_error = result;
496 : }
497 : }
498 :
499 38 : if (ai)
500 27 : ::freeaddrinfo(ai);
501 :
502 : // Hand the keepalive to the op: the completion waits in the
503 : // scheduler's queue, and the implementation embedding it must
504 : // outlive that wait. Nothing may touch *self after the post.
505 38 : self->op_.object_ref_ = std::move(pw->ref_);
506 38 : self->svc_.post(&self->op_);
507 38 : }
508 :
509 : inline void
510 16 : posix_resolver::do_reverse_resolve_work(pool_work_item* w) noexcept
511 : {
512 16 : auto* pw = static_cast<pool_op*>(w);
513 16 : auto* self = pw->resolver_;
514 :
515 16 : sockaddr_storage ss{};
516 : socklen_t ss_len;
517 :
518 16 : if (self->reverse_op_.ep.is_v4())
519 : {
520 14 : auto sa = to_sockaddr_in(self->reverse_op_.ep);
521 14 : std::memcpy(&ss, &sa, sizeof(sa));
522 14 : 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 16 : 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 16 : if (!self->reverse_op_.cancelled.load(std::memory_order_acquire))
540 : {
541 16 : if (result == 0)
542 : {
543 10 : self->reverse_op_.stored_host = host;
544 10 : self->reverse_op_.stored_service = service;
545 10 : self->reverse_op_.gai_error = 0;
546 : }
547 : else
548 : {
549 6 : self->reverse_op_.gai_error = result;
550 : }
551 : }
552 :
553 : // Hand the keepalive to the op: the completion waits in the
554 : // scheduler's queue, and the implementation embedding it must
555 : // outlive that wait. Nothing may touch *self after the post.
556 16 : self->reverse_op_.object_ref_ = std::move(pw->ref_);
557 16 : self->svc_.post(&self->reverse_op_);
558 16 : }
559 :
560 : inline void
561 3 : posix_resolver::destroy_resolve_work(pool_work_item* w) noexcept
562 : {
563 3 : auto* pw = static_cast<pool_op*>(w);
564 3 : auto* self = pw->resolver_;
565 3 : self->op_.object_ref_ = std::move(pw->ref_);
566 3 : self->op_.destroy();
567 3 : }
568 :
569 : inline void
570 3 : posix_resolver::destroy_reverse_resolve_work(pool_work_item* w) noexcept
571 : {
572 3 : auto* pw = static_cast<pool_op*>(w);
573 3 : auto* self = pw->resolver_;
574 3 : self->reverse_op_.object_ref_ = std::move(pw->ref_);
575 3 : self->reverse_op_.destroy();
576 3 : }
577 :
578 : // posix_resolver_service implementation
579 :
580 : inline void
581 77 : posix_resolver_service::shutdown()
582 : {
583 : // See uring_socket_service_base::shutdown(): snapshot under an
584 : // acquired reference, cancel without the pool lock held; shutdown()
585 : // sets shutting-down and takes the snapshot in one critical
586 : // section, so a cancel that drops the last ref deletes rather than
587 : // recycles. The thread pool service shuts down separately via
588 : // execution_context ordering.
589 77 : std::vector<posix_resolver*> live;
590 77 : object_pool_.shutdown(
591 77 : [&](posix_resolver* r)
592 : {
593 3 : acquire(r);
594 3 : live.push_back(r);
595 3 : });
596 80 : for (auto* r : live)
597 : {
598 3 : r->cancel();
599 3 : release(r);
600 : }
601 77 : }
602 :
603 : inline io_object::implementation*
604 142 : posix_resolver_service::construct()
605 : {
606 142 : return object_pool_.acquire(*this);
607 : }
608 :
609 : inline void
610 54 : posix_resolver_service::post(scheduler_op* op)
611 : {
612 54 : sched_->post(op);
613 54 : }
614 :
615 : inline void
616 142 : posix_resolver::retire() noexcept
617 : {
618 142 : svc_.object_pool_.recycle(this);
619 142 : }
620 :
621 : } // namespace boost::corosio::detail
622 :
623 : #endif // BOOST_COROSIO_POSIX
624 :
625 : #endif // BOOST_COROSIO_NATIVE_DETAIL_POSIX_POSIX_RESOLVER_SERVICE_HPP
|