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 : #include <boost/corosio/detail/platform.hpp>
11 :
12 : #if BOOST_COROSIO_POSIX
13 :
14 : #include "src/detail/posix/resolver_service.hpp"
15 : #include "src/detail/endpoint_convert.hpp"
16 : #include "src/detail/intrusive.hpp"
17 : #include "src/detail/resume_coro.hpp"
18 : #include "src/detail/scheduler_op.hpp"
19 :
20 : #include <boost/corosio/detail/scheduler.hpp>
21 : #include <boost/corosio/resolver_results.hpp>
22 : #include <boost/capy/ex/executor_ref.hpp>
23 : #include <boost/capy/coro.hpp>
24 : #include <boost/capy/error.hpp>
25 :
26 : #include <netdb.h>
27 : #include <netinet/in.h>
28 : #include <sys/socket.h>
29 :
30 : #include <atomic>
31 : #include <cassert>
32 : #include <condition_variable>
33 : #include <cstring>
34 : #include <memory>
35 : #include <mutex>
36 : #include <optional>
37 : #include <stop_token>
38 : #include <string>
39 : #include <thread>
40 : #include <unordered_map>
41 : #include <vector>
42 :
43 : /*
44 : POSIX Resolver Implementation
45 : =============================
46 :
47 : This file implements async DNS resolution for POSIX backends using a
48 : thread-per-resolution approach. See resolver_service.hpp for the design
49 : rationale.
50 :
51 : Class Hierarchy
52 : ---------------
53 : - posix_resolver_service (abstract base in header)
54 : - posix_resolver_service_impl (concrete, defined here)
55 : - Owns all posix_resolver_impl instances via shared_ptr
56 : - Stores scheduler* for posting completions
57 : - posix_resolver_impl (one per resolver object)
58 : - Contains embedded resolve_op and reverse_resolve_op for reuse
59 : - Uses shared_from_this to prevent premature destruction
60 : - resolve_op (forward resolution state)
61 : - Uses getaddrinfo() to resolve host/service to endpoints
62 : - reverse_resolve_op (reverse resolution state)
63 : - Uses getnameinfo() to resolve endpoint to host/service
64 :
65 : Worker Thread Lifetime
66 : ----------------------
67 : Each resolve() spawns a detached thread. The thread captures a shared_ptr
68 : to posix_resolver_impl, ensuring the impl (and its embedded op_) stays
69 : alive until the thread completes, even if the resolver is destroyed.
70 :
71 : Completion Flow
72 : ---------------
73 : Forward resolution:
74 : 1. resolve() sets up op_, spawns worker thread
75 : 2. Worker runs getaddrinfo() (blocking)
76 : 3. Worker stores results in op_.stored_results
77 : 4. Worker calls svc_.post(&op_) to queue completion
78 : 5. Scheduler invokes op_() which resumes the coroutine
79 :
80 : Reverse resolution follows the same pattern using getnameinfo().
81 :
82 : Single-Inflight Constraint
83 : --------------------------
84 : Each resolver has ONE embedded op_ for forward and ONE reverse_op_ for
85 : reverse resolution. Concurrent operations of the same type on the same
86 : resolver would corrupt state. Users must serialize operations per-resolver.
87 :
88 : Shutdown Synchronization
89 : ------------------------
90 : The service tracks active worker threads via thread_started()/thread_finished().
91 : During shutdown(), the service sets shutting_down_ flag and waits for all
92 : threads to complete before destroying resources.
93 : */
94 :
95 : namespace boost::corosio::detail {
96 :
97 : namespace {
98 :
99 : // Convert resolve_flags to addrinfo ai_flags
100 : int
101 16 : flags_to_hints(resolve_flags flags)
102 : {
103 16 : int hints = 0;
104 :
105 16 : if ((flags & resolve_flags::passive) != resolve_flags::none)
106 0 : hints |= AI_PASSIVE;
107 16 : if ((flags & resolve_flags::numeric_host) != resolve_flags::none)
108 11 : hints |= AI_NUMERICHOST;
109 16 : if ((flags & resolve_flags::numeric_service) != resolve_flags::none)
110 8 : hints |= AI_NUMERICSERV;
111 16 : if ((flags & resolve_flags::address_configured) != resolve_flags::none)
112 0 : hints |= AI_ADDRCONFIG;
113 16 : if ((flags & resolve_flags::v4_mapped) != resolve_flags::none)
114 0 : hints |= AI_V4MAPPED;
115 16 : if ((flags & resolve_flags::all_matching) != resolve_flags::none)
116 0 : hints |= AI_ALL;
117 :
118 16 : return hints;
119 : }
120 :
121 : // Convert reverse_flags to getnameinfo NI_* flags
122 : int
123 10 : flags_to_ni_flags(reverse_flags flags)
124 : {
125 10 : int ni_flags = 0;
126 :
127 10 : if ((flags & reverse_flags::numeric_host) != reverse_flags::none)
128 5 : ni_flags |= NI_NUMERICHOST;
129 10 : if ((flags & reverse_flags::numeric_service) != reverse_flags::none)
130 5 : ni_flags |= NI_NUMERICSERV;
131 10 : if ((flags & reverse_flags::name_required) != reverse_flags::none)
132 1 : ni_flags |= NI_NAMEREQD;
133 10 : if ((flags & reverse_flags::datagram_service) != reverse_flags::none)
134 0 : ni_flags |= NI_DGRAM;
135 :
136 10 : return ni_flags;
137 : }
138 :
139 : // Convert addrinfo results to resolver_results
140 : resolver_results
141 13 : convert_results(
142 : struct addrinfo* ai,
143 : std::string_view host,
144 : std::string_view service)
145 : {
146 13 : std::vector<resolver_entry> entries;
147 13 : entries.reserve(4); // Most lookups return 1-4 addresses
148 :
149 26 : for (auto* p = ai; p != nullptr; p = p->ai_next)
150 : {
151 13 : if (p->ai_family == AF_INET)
152 : {
153 11 : auto* addr = reinterpret_cast<sockaddr_in*>(p->ai_addr);
154 11 : auto ep = from_sockaddr_in(*addr);
155 11 : entries.emplace_back(ep, host, service);
156 : }
157 2 : else if (p->ai_family == AF_INET6)
158 : {
159 2 : auto* addr = reinterpret_cast<sockaddr_in6*>(p->ai_addr);
160 2 : auto ep = from_sockaddr_in6(*addr);
161 2 : entries.emplace_back(ep, host, service);
162 : }
163 : }
164 :
165 26 : return resolver_results(std::move(entries));
166 13 : }
167 :
168 : // Convert getaddrinfo error codes to std::error_code
169 : std::error_code
170 4 : make_gai_error(int gai_err)
171 : {
172 : // Map GAI errors to appropriate generic error codes
173 4 : switch (gai_err)
174 : {
175 0 : case EAI_AGAIN:
176 : // Temporary failure - try again later
177 0 : return std::error_code(
178 : static_cast<int>(std::errc::resource_unavailable_try_again),
179 0 : std::generic_category());
180 :
181 0 : case EAI_BADFLAGS:
182 : // Invalid flags
183 0 : return std::error_code(
184 : static_cast<int>(std::errc::invalid_argument),
185 0 : std::generic_category());
186 :
187 0 : case EAI_FAIL:
188 : // Non-recoverable failure
189 0 : return std::error_code(
190 : static_cast<int>(std::errc::io_error),
191 0 : std::generic_category());
192 :
193 0 : case EAI_FAMILY:
194 : // Address family not supported
195 0 : return std::error_code(
196 : static_cast<int>(std::errc::address_family_not_supported),
197 0 : std::generic_category());
198 :
199 0 : case EAI_MEMORY:
200 : // Memory allocation failure
201 0 : return std::error_code(
202 : static_cast<int>(std::errc::not_enough_memory),
203 0 : std::generic_category());
204 :
205 4 : case EAI_NONAME:
206 : // Host or service not found
207 4 : return std::error_code(
208 : static_cast<int>(std::errc::no_such_device_or_address),
209 4 : std::generic_category());
210 :
211 0 : case EAI_SERVICE:
212 : // Service not supported for socket type
213 0 : return std::error_code(
214 : static_cast<int>(std::errc::invalid_argument),
215 0 : std::generic_category());
216 :
217 0 : case EAI_SOCKTYPE:
218 : // Socket type not supported
219 0 : return std::error_code(
220 : static_cast<int>(std::errc::not_supported),
221 0 : std::generic_category());
222 :
223 0 : case EAI_SYSTEM:
224 : // System error - use errno
225 0 : return std::error_code(errno, std::generic_category());
226 :
227 0 : default:
228 : // Unknown error
229 0 : return std::error_code(
230 : static_cast<int>(std::errc::io_error),
231 0 : std::generic_category());
232 : }
233 : }
234 :
235 : } // anonymous namespace
236 :
237 : //------------------------------------------------------------------------------
238 :
239 : class posix_resolver_impl;
240 : class posix_resolver_service_impl;
241 :
242 : //------------------------------------------------------------------------------
243 : // posix_resolver_impl - per-resolver implementation
244 : //------------------------------------------------------------------------------
245 :
246 : /** Resolver implementation for POSIX backends.
247 :
248 : Each resolver instance contains a single embedded operation object (op_)
249 : that is reused for each resolve() call. This design avoids per-operation
250 : heap allocation but imposes a critical constraint:
251 :
252 : @par Single-Inflight Contract
253 :
254 : Only ONE resolve operation may be in progress at a time per resolver
255 : instance. Calling resolve() while a previous resolve() is still pending
256 : results in undefined behavior:
257 :
258 : - The new call overwrites op_ fields (host, service, coroutine handle)
259 : - The worker thread from the first call reads corrupted state
260 : - The wrong coroutine may be resumed, or resumed multiple times
261 : - Data races occur on non-atomic op_ members
262 :
263 : @par Safe Usage Patterns
264 :
265 : @code
266 : // CORRECT: Sequential resolves
267 : auto [ec1, r1] = co_await resolver.resolve("host1", "80");
268 : auto [ec2, r2] = co_await resolver.resolve("host2", "80");
269 :
270 : // CORRECT: Parallel resolves with separate resolver instances
271 : resolver r1(ctx), r2(ctx);
272 : auto [ec1, res1] = co_await r1.resolve("host1", "80"); // in one coroutine
273 : auto [ec2, res2] = co_await r2.resolve("host2", "80"); // in another
274 :
275 : // WRONG: Concurrent resolves on same resolver
276 : // These may run concurrently if launched in parallel - UNDEFINED BEHAVIOR
277 : auto f1 = resolver.resolve("host1", "80");
278 : auto f2 = resolver.resolve("host2", "80"); // BAD: overlaps with f1
279 : @endcode
280 :
281 : @par Thread Safety
282 : Distinct objects: Safe.
283 : Shared objects: Unsafe. See single-inflight contract above.
284 : */
285 : class posix_resolver_impl
286 : : public resolver::resolver_impl
287 : , public std::enable_shared_from_this<posix_resolver_impl>
288 : , public intrusive_list<posix_resolver_impl>::node
289 : {
290 : friend class posix_resolver_service_impl;
291 :
292 : public:
293 : //--------------------------------------------------------------------------
294 : // resolve_op - operation state for a single DNS resolution
295 : //--------------------------------------------------------------------------
296 :
297 : struct resolve_op : scheduler_op
298 : {
299 : struct canceller
300 : {
301 : resolve_op* op;
302 0 : void operator()() const noexcept { op->request_cancel(); }
303 : };
304 :
305 : // Coroutine state
306 : capy::coro h;
307 : capy::executor_ref ex;
308 : posix_resolver_impl* impl = nullptr;
309 :
310 : // Output parameters
311 : std::error_code* ec_out = nullptr;
312 : resolver_results* out = nullptr;
313 :
314 : // Input parameters (owned copies for thread safety)
315 : std::string host;
316 : std::string service;
317 : resolve_flags flags = resolve_flags::none;
318 :
319 : // Result storage (populated by worker thread)
320 : resolver_results stored_results;
321 : int gai_error = 0;
322 :
323 : // Thread coordination
324 : std::atomic<bool> cancelled{false};
325 : std::optional<std::stop_callback<canceller>> stop_cb;
326 :
327 29 : resolve_op()
328 29 : {
329 29 : data_ = this;
330 29 : }
331 :
332 : void reset() noexcept;
333 : void operator()() override;
334 : void destroy() override;
335 : void request_cancel() noexcept;
336 : void start(std::stop_token token);
337 : };
338 :
339 : //--------------------------------------------------------------------------
340 : // reverse_resolve_op - operation state for reverse DNS resolution
341 : //--------------------------------------------------------------------------
342 :
343 : struct reverse_resolve_op : scheduler_op
344 : {
345 : struct canceller
346 : {
347 : reverse_resolve_op* op;
348 0 : void operator()() const noexcept { op->request_cancel(); }
349 : };
350 :
351 : // Coroutine state
352 : capy::coro h;
353 : capy::executor_ref ex;
354 : posix_resolver_impl* impl = nullptr;
355 :
356 : // Output parameters
357 : std::error_code* ec_out = nullptr;
358 : reverse_resolver_result* result_out = nullptr;
359 :
360 : // Input parameters
361 : endpoint ep;
362 : reverse_flags flags = reverse_flags::none;
363 :
364 : // Result storage (populated by worker thread)
365 : std::string stored_host;
366 : std::string stored_service;
367 : int gai_error = 0;
368 :
369 : // Thread coordination
370 : std::atomic<bool> cancelled{false};
371 : std::optional<std::stop_callback<canceller>> stop_cb;
372 :
373 29 : reverse_resolve_op()
374 29 : {
375 29 : data_ = this;
376 29 : }
377 :
378 : void reset() noexcept;
379 : void operator()() override;
380 : void destroy() override;
381 : void request_cancel() noexcept;
382 : void start(std::stop_token token);
383 : };
384 :
385 29 : explicit posix_resolver_impl(posix_resolver_service_impl& svc) noexcept
386 29 : : svc_(svc)
387 : {
388 29 : }
389 :
390 : void release() override;
391 :
392 : void resolve(
393 : std::coroutine_handle<>,
394 : capy::executor_ref,
395 : std::string_view host,
396 : std::string_view service,
397 : resolve_flags flags,
398 : std::stop_token,
399 : std::error_code*,
400 : resolver_results*) override;
401 :
402 : void reverse_resolve(
403 : std::coroutine_handle<>,
404 : capy::executor_ref,
405 : endpoint const& ep,
406 : reverse_flags flags,
407 : std::stop_token,
408 : std::error_code*,
409 : reverse_resolver_result*) override;
410 :
411 : void cancel() noexcept override;
412 :
413 : resolve_op op_;
414 : reverse_resolve_op reverse_op_;
415 :
416 : private:
417 : posix_resolver_service_impl& svc_;
418 : };
419 :
420 : //------------------------------------------------------------------------------
421 : // posix_resolver_service_impl - concrete service implementation
422 : //------------------------------------------------------------------------------
423 :
424 : class posix_resolver_service_impl : public posix_resolver_service
425 : {
426 : public:
427 : using key_type = posix_resolver_service;
428 :
429 309 : posix_resolver_service_impl(
430 : capy::execution_context&,
431 : scheduler& sched)
432 309 : : sched_(&sched)
433 : {
434 309 : }
435 :
436 618 : ~posix_resolver_service_impl()
437 309 : {
438 618 : }
439 :
440 : posix_resolver_service_impl(posix_resolver_service_impl const&) = delete;
441 : posix_resolver_service_impl& operator=(posix_resolver_service_impl const&) = delete;
442 :
443 : void shutdown() override;
444 : resolver::resolver_impl& create_impl() override;
445 : void destroy_impl(posix_resolver_impl& impl);
446 :
447 : void post(scheduler_op* op);
448 : void work_started() noexcept;
449 : void work_finished() noexcept;
450 :
451 : // Thread tracking for safe shutdown
452 : void thread_started() noexcept;
453 : void thread_finished() noexcept;
454 : bool is_shutting_down() const noexcept;
455 :
456 : private:
457 : scheduler* sched_;
458 : std::mutex mutex_;
459 : std::condition_variable cv_;
460 : std::atomic<bool> shutting_down_{false};
461 : std::size_t active_threads_ = 0;
462 : intrusive_list<posix_resolver_impl> resolver_list_;
463 : std::unordered_map<posix_resolver_impl*,
464 : std::shared_ptr<posix_resolver_impl>> resolver_ptrs_;
465 : };
466 :
467 : //------------------------------------------------------------------------------
468 : // posix_resolver_impl::resolve_op implementation
469 : //------------------------------------------------------------------------------
470 :
471 : void
472 16 : posix_resolver_impl::resolve_op::
473 : reset() noexcept
474 : {
475 16 : host.clear();
476 16 : service.clear();
477 16 : flags = resolve_flags::none;
478 16 : stored_results = resolver_results{};
479 16 : gai_error = 0;
480 16 : cancelled.store(false, std::memory_order_relaxed);
481 16 : stop_cb.reset();
482 16 : ec_out = nullptr;
483 16 : out = nullptr;
484 16 : }
485 :
486 : void
487 16 : posix_resolver_impl::resolve_op::
488 : operator()()
489 : {
490 16 : stop_cb.reset(); // Disconnect stop callback
491 :
492 16 : bool const was_cancelled = cancelled.load(std::memory_order_acquire);
493 :
494 16 : if (ec_out)
495 : {
496 16 : if (was_cancelled)
497 0 : *ec_out = capy::error::canceled;
498 16 : else if (gai_error != 0)
499 3 : *ec_out = make_gai_error(gai_error);
500 : else
501 13 : *ec_out = {}; // Clear on success
502 : }
503 :
504 16 : if (out && !was_cancelled && gai_error == 0)
505 13 : *out = std::move(stored_results);
506 :
507 16 : impl->svc_.work_finished();
508 16 : resume_coro(ex, h);
509 16 : }
510 :
511 : void
512 0 : posix_resolver_impl::resolve_op::
513 : destroy()
514 : {
515 0 : stop_cb.reset();
516 0 : }
517 :
518 : void
519 34 : posix_resolver_impl::resolve_op::
520 : request_cancel() noexcept
521 : {
522 34 : cancelled.store(true, std::memory_order_release);
523 34 : }
524 :
525 : void
526 16 : posix_resolver_impl::resolve_op::
527 : start(std::stop_token token)
528 : {
529 16 : cancelled.store(false, std::memory_order_release);
530 16 : stop_cb.reset();
531 :
532 16 : if (token.stop_possible())
533 0 : stop_cb.emplace(token, canceller{this});
534 16 : }
535 :
536 : //------------------------------------------------------------------------------
537 : // posix_resolver_impl::reverse_resolve_op implementation
538 : //------------------------------------------------------------------------------
539 :
540 : void
541 10 : posix_resolver_impl::reverse_resolve_op::
542 : reset() noexcept
543 : {
544 10 : ep = endpoint{};
545 10 : flags = reverse_flags::none;
546 10 : stored_host.clear();
547 10 : stored_service.clear();
548 10 : gai_error = 0;
549 10 : cancelled.store(false, std::memory_order_relaxed);
550 10 : stop_cb.reset();
551 10 : ec_out = nullptr;
552 10 : result_out = nullptr;
553 10 : }
554 :
555 : void
556 10 : posix_resolver_impl::reverse_resolve_op::
557 : operator()()
558 : {
559 10 : stop_cb.reset(); // Disconnect stop callback
560 :
561 10 : bool const was_cancelled = cancelled.load(std::memory_order_acquire);
562 :
563 10 : if (ec_out)
564 : {
565 10 : if (was_cancelled)
566 0 : *ec_out = capy::error::canceled;
567 10 : else if (gai_error != 0)
568 1 : *ec_out = make_gai_error(gai_error);
569 : else
570 9 : *ec_out = {}; // Clear on success
571 : }
572 :
573 10 : if (result_out && !was_cancelled && gai_error == 0)
574 : {
575 27 : *result_out = reverse_resolver_result(
576 27 : ep, std::move(stored_host), std::move(stored_service));
577 : }
578 :
579 10 : impl->svc_.work_finished();
580 10 : resume_coro(ex, h);
581 10 : }
582 :
583 : void
584 0 : posix_resolver_impl::reverse_resolve_op::
585 : destroy()
586 : {
587 0 : stop_cb.reset();
588 0 : }
589 :
590 : void
591 34 : posix_resolver_impl::reverse_resolve_op::
592 : request_cancel() noexcept
593 : {
594 34 : cancelled.store(true, std::memory_order_release);
595 34 : }
596 :
597 : void
598 10 : posix_resolver_impl::reverse_resolve_op::
599 : start(std::stop_token token)
600 : {
601 10 : cancelled.store(false, std::memory_order_release);
602 10 : stop_cb.reset();
603 :
604 10 : if (token.stop_possible())
605 0 : stop_cb.emplace(token, canceller{this});
606 10 : }
607 :
608 : //------------------------------------------------------------------------------
609 : // posix_resolver_impl implementation
610 : //------------------------------------------------------------------------------
611 :
612 : void
613 28 : posix_resolver_impl::
614 : release()
615 : {
616 28 : cancel();
617 28 : svc_.destroy_impl(*this);
618 28 : }
619 :
620 : void
621 16 : posix_resolver_impl::
622 : resolve(
623 : std::coroutine_handle<> h,
624 : capy::executor_ref ex,
625 : std::string_view host,
626 : std::string_view service,
627 : resolve_flags flags,
628 : std::stop_token token,
629 : std::error_code* ec,
630 : resolver_results* out)
631 : {
632 16 : auto& op = op_;
633 16 : op.reset();
634 16 : op.h = h;
635 16 : op.ex = ex;
636 16 : op.impl = this;
637 16 : op.ec_out = ec;
638 16 : op.out = out;
639 16 : op.host = host;
640 16 : op.service = service;
641 16 : op.flags = flags;
642 16 : op.start(token);
643 :
644 : // Keep io_context alive while resolution is pending
645 16 : op.ex.on_work_started();
646 :
647 : // Track thread for safe shutdown
648 16 : svc_.thread_started();
649 :
650 : try
651 : {
652 : // Prevent impl destruction while worker thread is running
653 16 : auto self = this->shared_from_this();
654 32 : std::thread worker([this, self = std::move(self)]() {
655 16 : struct addrinfo hints{};
656 16 : hints.ai_family = AF_UNSPEC;
657 16 : hints.ai_socktype = SOCK_STREAM;
658 16 : hints.ai_flags = flags_to_hints(op_.flags);
659 :
660 16 : struct addrinfo* ai = nullptr;
661 48 : int result = ::getaddrinfo(
662 32 : op_.host.empty() ? nullptr : op_.host.c_str(),
663 32 : op_.service.empty() ? nullptr : op_.service.c_str(),
664 : &hints, &ai);
665 :
666 16 : if (!op_.cancelled.load(std::memory_order_acquire))
667 : {
668 16 : if (result == 0 && ai)
669 : {
670 13 : op_.stored_results = convert_results(ai, op_.host, op_.service);
671 13 : op_.gai_error = 0;
672 : }
673 : else
674 : {
675 3 : op_.gai_error = result;
676 : }
677 : }
678 :
679 16 : if (ai)
680 13 : ::freeaddrinfo(ai);
681 :
682 : // Always post so the scheduler can properly drain the op
683 : // during shutdown via destroy().
684 16 : svc_.post(&op_);
685 :
686 : // Signal thread completion for shutdown synchronization
687 16 : svc_.thread_finished();
688 32 : });
689 16 : worker.detach();
690 16 : }
691 0 : catch (std::system_error const&)
692 : {
693 : // Thread creation failed - no thread was started
694 0 : svc_.thread_finished();
695 :
696 : // Set error and post completion to avoid hanging the coroutine
697 0 : op_.gai_error = EAI_MEMORY; // Map to "not enough memory"
698 0 : svc_.post(&op_);
699 0 : }
700 16 : }
701 :
702 : void
703 10 : posix_resolver_impl::
704 : reverse_resolve(
705 : std::coroutine_handle<> h,
706 : capy::executor_ref ex,
707 : endpoint const& ep,
708 : reverse_flags flags,
709 : std::stop_token token,
710 : std::error_code* ec,
711 : reverse_resolver_result* result_out)
712 : {
713 10 : auto& op = reverse_op_;
714 10 : op.reset();
715 10 : op.h = h;
716 10 : op.ex = ex;
717 10 : op.impl = this;
718 10 : op.ec_out = ec;
719 10 : op.result_out = result_out;
720 10 : op.ep = ep;
721 10 : op.flags = flags;
722 10 : op.start(token);
723 :
724 : // Keep io_context alive while resolution is pending
725 10 : op.ex.on_work_started();
726 :
727 : // Track thread for safe shutdown
728 10 : svc_.thread_started();
729 :
730 : try
731 : {
732 : // Prevent impl destruction while worker thread is running
733 10 : auto self = this->shared_from_this();
734 20 : std::thread worker([this, self = std::move(self)]() {
735 : // Build sockaddr from endpoint
736 10 : sockaddr_storage ss{};
737 : socklen_t ss_len;
738 :
739 10 : if (reverse_op_.ep.is_v4())
740 : {
741 8 : auto sa = to_sockaddr_in(reverse_op_.ep);
742 8 : std::memcpy(&ss, &sa, sizeof(sa));
743 8 : ss_len = sizeof(sockaddr_in);
744 : }
745 : else
746 : {
747 2 : auto sa = to_sockaddr_in6(reverse_op_.ep);
748 2 : std::memcpy(&ss, &sa, sizeof(sa));
749 2 : ss_len = sizeof(sockaddr_in6);
750 : }
751 :
752 : char host[NI_MAXHOST];
753 : char service[NI_MAXSERV];
754 :
755 10 : int result = ::getnameinfo(
756 : reinterpret_cast<sockaddr*>(&ss), ss_len,
757 : host, sizeof(host),
758 : service, sizeof(service),
759 : flags_to_ni_flags(reverse_op_.flags));
760 :
761 10 : if (!reverse_op_.cancelled.load(std::memory_order_acquire))
762 : {
763 10 : if (result == 0)
764 : {
765 9 : reverse_op_.stored_host = host;
766 9 : reverse_op_.stored_service = service;
767 9 : reverse_op_.gai_error = 0;
768 : }
769 : else
770 : {
771 1 : reverse_op_.gai_error = result;
772 : }
773 : }
774 :
775 : // Always post so the scheduler can properly drain the op
776 : // during shutdown via destroy().
777 10 : svc_.post(&reverse_op_);
778 :
779 : // Signal thread completion for shutdown synchronization
780 10 : svc_.thread_finished();
781 20 : });
782 10 : worker.detach();
783 10 : }
784 0 : catch (std::system_error const&)
785 : {
786 : // Thread creation failed - no thread was started
787 0 : svc_.thread_finished();
788 :
789 : // Set error and post completion to avoid hanging the coroutine
790 0 : reverse_op_.gai_error = EAI_MEMORY;
791 0 : svc_.post(&reverse_op_);
792 0 : }
793 10 : }
794 :
795 : void
796 34 : posix_resolver_impl::
797 : cancel() noexcept
798 : {
799 34 : op_.request_cancel();
800 34 : reverse_op_.request_cancel();
801 34 : }
802 :
803 : //------------------------------------------------------------------------------
804 : // posix_resolver_service_impl implementation
805 : //------------------------------------------------------------------------------
806 :
807 : void
808 309 : posix_resolver_service_impl::
809 : shutdown()
810 : {
811 : {
812 309 : std::lock_guard<std::mutex> lock(mutex_);
813 :
814 : // Signal threads to not access service after getaddrinfo returns
815 309 : shutting_down_.store(true, std::memory_order_release);
816 :
817 : // Cancel all resolvers (sets cancelled flag checked by threads)
818 310 : for (auto* impl = resolver_list_.pop_front(); impl != nullptr;
819 1 : impl = resolver_list_.pop_front())
820 : {
821 1 : impl->cancel();
822 : }
823 :
824 : // Clear the map which releases shared_ptrs
825 309 : resolver_ptrs_.clear();
826 309 : }
827 :
828 : // Wait for all worker threads to finish before service is destroyed
829 : {
830 309 : std::unique_lock<std::mutex> lock(mutex_);
831 618 : cv_.wait(lock, [this] { return active_threads_ == 0; });
832 309 : }
833 309 : }
834 :
835 : resolver::resolver_impl&
836 29 : posix_resolver_service_impl::
837 : create_impl()
838 : {
839 29 : auto ptr = std::make_shared<posix_resolver_impl>(*this);
840 29 : auto* impl = ptr.get();
841 :
842 : {
843 29 : std::lock_guard<std::mutex> lock(mutex_);
844 29 : resolver_list_.push_back(impl);
845 29 : resolver_ptrs_[impl] = std::move(ptr);
846 29 : }
847 :
848 29 : return *impl;
849 29 : }
850 :
851 : void
852 28 : posix_resolver_service_impl::
853 : destroy_impl(posix_resolver_impl& impl)
854 : {
855 28 : std::lock_guard<std::mutex> lock(mutex_);
856 28 : resolver_list_.remove(&impl);
857 28 : resolver_ptrs_.erase(&impl);
858 28 : }
859 :
860 : void
861 26 : posix_resolver_service_impl::
862 : post(scheduler_op* op)
863 : {
864 26 : sched_->post(op);
865 26 : }
866 :
867 : void
868 0 : posix_resolver_service_impl::
869 : work_started() noexcept
870 : {
871 0 : sched_->work_started();
872 0 : }
873 :
874 : void
875 26 : posix_resolver_service_impl::
876 : work_finished() noexcept
877 : {
878 26 : sched_->work_finished();
879 26 : }
880 :
881 : void
882 26 : posix_resolver_service_impl::
883 : thread_started() noexcept
884 : {
885 26 : std::lock_guard<std::mutex> lock(mutex_);
886 26 : ++active_threads_;
887 26 : }
888 :
889 : void
890 26 : posix_resolver_service_impl::
891 : thread_finished() noexcept
892 : {
893 26 : std::lock_guard<std::mutex> lock(mutex_);
894 26 : --active_threads_;
895 26 : cv_.notify_one();
896 26 : }
897 :
898 : bool
899 0 : posix_resolver_service_impl::
900 : is_shutting_down() const noexcept
901 : {
902 0 : return shutting_down_.load(std::memory_order_acquire);
903 : }
904 :
905 : //------------------------------------------------------------------------------
906 : // Free function to get/create the resolver service
907 : //------------------------------------------------------------------------------
908 :
909 : posix_resolver_service&
910 309 : get_resolver_service(capy::execution_context& ctx, scheduler& sched)
911 : {
912 309 : return ctx.make_service<posix_resolver_service_impl>(sched);
913 : }
914 :
915 : } // namespace boost::corosio::detail
916 :
917 : #endif // BOOST_COROSIO_POSIX
|