LCOV - code coverage report
Current view: top level - src/detail/posix - resolver_service.cpp (source / functions) Coverage Total Hit
Test: coverage_filtered.info Lines: 80.9 % 304 246
Test Date: 2026-02-04 16:37:34 Functions: 84.6 % 39 33

            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          304 :     posix_resolver_service_impl(
     430              :         capy::execution_context&,
     431              :         scheduler& sched)
     432          304 :         : sched_(&sched)
     433              :     {
     434          304 :     }
     435              : 
     436          608 :     ~posix_resolver_service_impl()
     437          304 :     {
     438          608 :     }
     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          304 : posix_resolver_service_impl::
     809              : shutdown()
     810              : {
     811              :     {
     812          304 :         std::lock_guard<std::mutex> lock(mutex_);
     813              : 
     814              :         // Signal threads to not access service after getaddrinfo returns
     815          304 :         shutting_down_.store(true, std::memory_order_release);
     816              : 
     817              :         // Cancel all resolvers (sets cancelled flag checked by threads)
     818          305 :         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          304 :         resolver_ptrs_.clear();
     826          304 :     }
     827              : 
     828              :     // Wait for all worker threads to finish before service is destroyed
     829              :     {
     830          304 :         std::unique_lock<std::mutex> lock(mutex_);
     831          608 :         cv_.wait(lock, [this] { return active_threads_ == 0; });
     832          304 :     }
     833          304 : }
     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          304 : get_resolver_service(capy::execution_context& ctx, scheduler& sched)
     911              : {
     912          304 :     return ctx.make_service<posix_resolver_service_impl>(sched);
     913              : }
     914              : 
     915              : } // namespace boost::corosio::detail
     916              : 
     917              : #endif // BOOST_COROSIO_POSIX
        

Generated by: LCOV version 2.3