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.5 % 298 240
Test Date: 2026-02-06 05:04:16 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() = default;
     328              : 
     329              :         void reset() noexcept;
     330              :         void operator()() override;
     331              :         void destroy() override;
     332              :         void request_cancel() noexcept;
     333              :         void start(std::stop_token token);
     334              :     };
     335              : 
     336              :     //--------------------------------------------------------------------------
     337              :     // reverse_resolve_op - operation state for reverse DNS resolution
     338              :     //--------------------------------------------------------------------------
     339              : 
     340              :     struct reverse_resolve_op : scheduler_op
     341              :     {
     342              :         struct canceller
     343              :         {
     344              :             reverse_resolve_op* op;
     345            0 :             void operator()() const noexcept { op->request_cancel(); }
     346              :         };
     347              : 
     348              :         // Coroutine state
     349              :         capy::coro h;
     350              :         capy::executor_ref ex;
     351              :         posix_resolver_impl* impl = nullptr;
     352              : 
     353              :         // Output parameters
     354              :         std::error_code* ec_out = nullptr;
     355              :         reverse_resolver_result* result_out = nullptr;
     356              : 
     357              :         // Input parameters
     358              :         endpoint ep;
     359              :         reverse_flags flags = reverse_flags::none;
     360              : 
     361              :         // Result storage (populated by worker thread)
     362              :         std::string stored_host;
     363              :         std::string stored_service;
     364              :         int gai_error = 0;
     365              : 
     366              :         // Thread coordination
     367              :         std::atomic<bool> cancelled{false};
     368              :         std::optional<std::stop_callback<canceller>> stop_cb;
     369              : 
     370           29 :         reverse_resolve_op() = default;
     371              : 
     372              :         void reset() noexcept;
     373              :         void operator()() override;
     374              :         void destroy() override;
     375              :         void request_cancel() noexcept;
     376              :         void start(std::stop_token token);
     377              :     };
     378              : 
     379           29 :     explicit posix_resolver_impl(posix_resolver_service_impl& svc) noexcept
     380           29 :         : svc_(svc)
     381              :     {
     382           29 :     }
     383              : 
     384              :     void release() override;
     385              : 
     386              :     std::coroutine_handle<> resolve(
     387              :         std::coroutine_handle<>,
     388              :         capy::executor_ref,
     389              :         std::string_view host,
     390              :         std::string_view service,
     391              :         resolve_flags flags,
     392              :         std::stop_token,
     393              :         std::error_code*,
     394              :         resolver_results*) override;
     395              : 
     396              :     std::coroutine_handle<> reverse_resolve(
     397              :         std::coroutine_handle<>,
     398              :         capy::executor_ref,
     399              :         endpoint const& ep,
     400              :         reverse_flags flags,
     401              :         std::stop_token,
     402              :         std::error_code*,
     403              :         reverse_resolver_result*) override;
     404              : 
     405              :     void cancel() noexcept override;
     406              : 
     407              :     resolve_op op_;
     408              :     reverse_resolve_op reverse_op_;
     409              : 
     410              : private:
     411              :     posix_resolver_service_impl& svc_;
     412              : };
     413              : 
     414              : //------------------------------------------------------------------------------
     415              : // posix_resolver_service_impl - concrete service implementation
     416              : //------------------------------------------------------------------------------
     417              : 
     418              : class posix_resolver_service_impl : public posix_resolver_service
     419              : {
     420              : public:
     421              :     using key_type = posix_resolver_service;
     422              : 
     423          309 :     posix_resolver_service_impl(
     424              :         capy::execution_context&,
     425              :         scheduler& sched)
     426          309 :         : sched_(&sched)
     427              :     {
     428          309 :     }
     429              : 
     430          618 :     ~posix_resolver_service_impl()
     431          309 :     {
     432          618 :     }
     433              : 
     434              :     posix_resolver_service_impl(posix_resolver_service_impl const&) = delete;
     435              :     posix_resolver_service_impl& operator=(posix_resolver_service_impl const&) = delete;
     436              : 
     437              :     void shutdown() override;
     438              :     resolver::resolver_impl& create_impl() override;
     439              :     void destroy_impl(posix_resolver_impl& impl);
     440              : 
     441              :     void post(scheduler_op* op);
     442              :     void work_started() noexcept;
     443              :     void work_finished() noexcept;
     444              : 
     445              :     // Thread tracking for safe shutdown
     446              :     void thread_started() noexcept;
     447              :     void thread_finished() noexcept;
     448              :     bool is_shutting_down() const noexcept;
     449              : 
     450              : private:
     451              :     scheduler* sched_;
     452              :     std::mutex mutex_;
     453              :     std::condition_variable cv_;
     454              :     std::atomic<bool> shutting_down_{false};
     455              :     std::size_t active_threads_ = 0;
     456              :     intrusive_list<posix_resolver_impl> resolver_list_;
     457              :     std::unordered_map<posix_resolver_impl*,
     458              :         std::shared_ptr<posix_resolver_impl>> resolver_ptrs_;
     459              : };
     460              : 
     461              : //------------------------------------------------------------------------------
     462              : // posix_resolver_impl::resolve_op implementation
     463              : //------------------------------------------------------------------------------
     464              : 
     465              : void
     466           16 : posix_resolver_impl::resolve_op::
     467              : reset() noexcept
     468              : {
     469           16 :     host.clear();
     470           16 :     service.clear();
     471           16 :     flags = resolve_flags::none;
     472           16 :     stored_results = resolver_results{};
     473           16 :     gai_error = 0;
     474           16 :     cancelled.store(false, std::memory_order_relaxed);
     475           16 :     stop_cb.reset();
     476           16 :     ec_out = nullptr;
     477           16 :     out = nullptr;
     478           16 : }
     479              : 
     480              : void
     481           16 : posix_resolver_impl::resolve_op::
     482              : operator()()
     483              : {
     484           16 :     stop_cb.reset();  // Disconnect stop callback
     485              : 
     486           16 :     bool const was_cancelled = cancelled.load(std::memory_order_acquire);
     487              : 
     488           16 :     if (ec_out)
     489              :     {
     490           16 :         if (was_cancelled)
     491            0 :             *ec_out = capy::error::canceled;
     492           16 :         else if (gai_error != 0)
     493            3 :             *ec_out = make_gai_error(gai_error);
     494              :         else
     495           13 :             *ec_out = {};  // Clear on success
     496              :     }
     497              : 
     498           16 :     if (out && !was_cancelled && gai_error == 0)
     499           13 :         *out = std::move(stored_results);
     500              : 
     501           16 :     impl->svc_.work_finished();
     502           16 :     resume_coro(ex, h);
     503           16 : }
     504              : 
     505              : void
     506            0 : posix_resolver_impl::resolve_op::
     507              : destroy()
     508              : {
     509            0 :     stop_cb.reset();
     510            0 : }
     511              : 
     512              : void
     513           34 : posix_resolver_impl::resolve_op::
     514              : request_cancel() noexcept
     515              : {
     516           34 :     cancelled.store(true, std::memory_order_release);
     517           34 : }
     518              : 
     519              : void
     520           16 : posix_resolver_impl::resolve_op::
     521              : start(std::stop_token token)
     522              : {
     523           16 :     cancelled.store(false, std::memory_order_release);
     524           16 :     stop_cb.reset();
     525              : 
     526           16 :     if (token.stop_possible())
     527            0 :         stop_cb.emplace(token, canceller{this});
     528           16 : }
     529              : 
     530              : //------------------------------------------------------------------------------
     531              : // posix_resolver_impl::reverse_resolve_op implementation
     532              : //------------------------------------------------------------------------------
     533              : 
     534              : void
     535           10 : posix_resolver_impl::reverse_resolve_op::
     536              : reset() noexcept
     537              : {
     538           10 :     ep = endpoint{};
     539           10 :     flags = reverse_flags::none;
     540           10 :     stored_host.clear();
     541           10 :     stored_service.clear();
     542           10 :     gai_error = 0;
     543           10 :     cancelled.store(false, std::memory_order_relaxed);
     544           10 :     stop_cb.reset();
     545           10 :     ec_out = nullptr;
     546           10 :     result_out = nullptr;
     547           10 : }
     548              : 
     549              : void
     550           10 : posix_resolver_impl::reverse_resolve_op::
     551              : operator()()
     552              : {
     553           10 :     stop_cb.reset();  // Disconnect stop callback
     554              : 
     555           10 :     bool const was_cancelled = cancelled.load(std::memory_order_acquire);
     556              : 
     557           10 :     if (ec_out)
     558              :     {
     559           10 :         if (was_cancelled)
     560            0 :             *ec_out = capy::error::canceled;
     561           10 :         else if (gai_error != 0)
     562            1 :             *ec_out = make_gai_error(gai_error);
     563              :         else
     564            9 :             *ec_out = {};  // Clear on success
     565              :     }
     566              : 
     567           10 :     if (result_out && !was_cancelled && gai_error == 0)
     568              :     {
     569           27 :         *result_out = reverse_resolver_result(
     570           27 :             ep, std::move(stored_host), std::move(stored_service));
     571              :     }
     572              : 
     573           10 :     impl->svc_.work_finished();
     574           10 :     resume_coro(ex, h);
     575           10 : }
     576              : 
     577              : void
     578            0 : posix_resolver_impl::reverse_resolve_op::
     579              : destroy()
     580              : {
     581            0 :     stop_cb.reset();
     582            0 : }
     583              : 
     584              : void
     585           34 : posix_resolver_impl::reverse_resolve_op::
     586              : request_cancel() noexcept
     587              : {
     588           34 :     cancelled.store(true, std::memory_order_release);
     589           34 : }
     590              : 
     591              : void
     592           10 : posix_resolver_impl::reverse_resolve_op::
     593              : start(std::stop_token token)
     594              : {
     595           10 :     cancelled.store(false, std::memory_order_release);
     596           10 :     stop_cb.reset();
     597              : 
     598           10 :     if (token.stop_possible())
     599            0 :         stop_cb.emplace(token, canceller{this});
     600           10 : }
     601              : 
     602              : //------------------------------------------------------------------------------
     603              : // posix_resolver_impl implementation
     604              : //------------------------------------------------------------------------------
     605              : 
     606              : void
     607           28 : posix_resolver_impl::
     608              : release()
     609              : {
     610           28 :     cancel();
     611           28 :     svc_.destroy_impl(*this);
     612           28 : }
     613              : 
     614              : std::coroutine_handle<>
     615           16 : posix_resolver_impl::
     616              : resolve(
     617              :     std::coroutine_handle<> h,
     618              :     capy::executor_ref ex,
     619              :     std::string_view host,
     620              :     std::string_view service,
     621              :     resolve_flags flags,
     622              :     std::stop_token token,
     623              :     std::error_code* ec,
     624              :     resolver_results* out)
     625              : {
     626           16 :     auto& op = op_;
     627           16 :     op.reset();
     628           16 :     op.h = h;
     629           16 :     op.ex = ex;
     630           16 :     op.impl = this;
     631           16 :     op.ec_out = ec;
     632           16 :     op.out = out;
     633           16 :     op.host = host;
     634           16 :     op.service = service;
     635           16 :     op.flags = flags;
     636           16 :     op.start(token);
     637              : 
     638              :     // Keep io_context alive while resolution is pending
     639           16 :     op.ex.on_work_started();
     640              : 
     641              :     // Track thread for safe shutdown
     642           16 :     svc_.thread_started();
     643              : 
     644              :     try
     645              :     {
     646              :         // Prevent impl destruction while worker thread is running
     647           16 :         auto self = this->shared_from_this();
     648           32 :         std::thread worker([this, self = std::move(self)]() {
     649           16 :             struct addrinfo hints{};
     650           16 :             hints.ai_family = AF_UNSPEC;
     651           16 :             hints.ai_socktype = SOCK_STREAM;
     652           16 :             hints.ai_flags = flags_to_hints(op_.flags);
     653              : 
     654           16 :             struct addrinfo* ai = nullptr;
     655           48 :             int result = ::getaddrinfo(
     656           32 :                 op_.host.empty() ? nullptr : op_.host.c_str(),
     657           32 :                 op_.service.empty() ? nullptr : op_.service.c_str(),
     658              :                 &hints, &ai);
     659              : 
     660           16 :             if (!op_.cancelled.load(std::memory_order_acquire))
     661              :             {
     662           16 :                 if (result == 0 && ai)
     663              :                 {
     664           13 :                     op_.stored_results = convert_results(ai, op_.host, op_.service);
     665           13 :                     op_.gai_error = 0;
     666              :                 }
     667              :                 else
     668              :                 {
     669            3 :                     op_.gai_error = result;
     670              :                 }
     671              :             }
     672              : 
     673           16 :             if (ai)
     674           13 :                 ::freeaddrinfo(ai);
     675              : 
     676              :             // Always post so the scheduler can properly drain the op
     677              :             // during shutdown via destroy().
     678           16 :             svc_.post(&op_);
     679              : 
     680              :             // Signal thread completion for shutdown synchronization
     681           16 :             svc_.thread_finished();
     682           32 :         });
     683           16 :         worker.detach();
     684           16 :     }
     685            0 :     catch (std::system_error const&)
     686              :     {
     687              :         // Thread creation failed - no thread was started
     688            0 :         svc_.thread_finished();
     689              : 
     690              :         // Set error and post completion to avoid hanging the coroutine
     691            0 :         op_.gai_error = EAI_MEMORY;  // Map to "not enough memory"
     692            0 :         svc_.post(&op_);
     693            0 :     }
     694           16 :     return std::noop_coroutine();
     695              : }
     696              : 
     697              : std::coroutine_handle<>
     698           10 : posix_resolver_impl::
     699              : reverse_resolve(
     700              :     std::coroutine_handle<> h,
     701              :     capy::executor_ref ex,
     702              :     endpoint const& ep,
     703              :     reverse_flags flags,
     704              :     std::stop_token token,
     705              :     std::error_code* ec,
     706              :     reverse_resolver_result* result_out)
     707              : {
     708           10 :     auto& op = reverse_op_;
     709           10 :     op.reset();
     710           10 :     op.h = h;
     711           10 :     op.ex = ex;
     712           10 :     op.impl = this;
     713           10 :     op.ec_out = ec;
     714           10 :     op.result_out = result_out;
     715           10 :     op.ep = ep;
     716           10 :     op.flags = flags;
     717           10 :     op.start(token);
     718              : 
     719              :     // Keep io_context alive while resolution is pending
     720           10 :     op.ex.on_work_started();
     721              : 
     722              :     // Track thread for safe shutdown
     723           10 :     svc_.thread_started();
     724              : 
     725              :     try
     726              :     {
     727              :         // Prevent impl destruction while worker thread is running
     728           10 :         auto self = this->shared_from_this();
     729           20 :         std::thread worker([this, self = std::move(self)]() {
     730              :             // Build sockaddr from endpoint
     731           10 :             sockaddr_storage ss{};
     732              :             socklen_t ss_len;
     733              : 
     734           10 :             if (reverse_op_.ep.is_v4())
     735              :             {
     736            8 :                 auto sa = to_sockaddr_in(reverse_op_.ep);
     737            8 :                 std::memcpy(&ss, &sa, sizeof(sa));
     738            8 :                 ss_len = sizeof(sockaddr_in);
     739              :             }
     740              :             else
     741              :             {
     742            2 :                 auto sa = to_sockaddr_in6(reverse_op_.ep);
     743            2 :                 std::memcpy(&ss, &sa, sizeof(sa));
     744            2 :                 ss_len = sizeof(sockaddr_in6);
     745              :             }
     746              : 
     747              :             char host[NI_MAXHOST];
     748              :             char service[NI_MAXSERV];
     749              : 
     750           10 :             int result = ::getnameinfo(
     751              :                 reinterpret_cast<sockaddr*>(&ss), ss_len,
     752              :                 host, sizeof(host),
     753              :                 service, sizeof(service),
     754              :                 flags_to_ni_flags(reverse_op_.flags));
     755              : 
     756           10 :             if (!reverse_op_.cancelled.load(std::memory_order_acquire))
     757              :             {
     758           10 :                 if (result == 0)
     759              :                 {
     760            9 :                     reverse_op_.stored_host = host;
     761            9 :                     reverse_op_.stored_service = service;
     762            9 :                     reverse_op_.gai_error = 0;
     763              :                 }
     764              :                 else
     765              :                 {
     766            1 :                     reverse_op_.gai_error = result;
     767              :                 }
     768              :             }
     769              : 
     770              :             // Always post so the scheduler can properly drain the op
     771              :             // during shutdown via destroy().
     772           10 :             svc_.post(&reverse_op_);
     773              : 
     774              :             // Signal thread completion for shutdown synchronization
     775           10 :             svc_.thread_finished();
     776           20 :         });
     777           10 :         worker.detach();
     778           10 :     }
     779            0 :     catch (std::system_error const&)
     780              :     {
     781              :         // Thread creation failed - no thread was started
     782            0 :         svc_.thread_finished();
     783              : 
     784              :         // Set error and post completion to avoid hanging the coroutine
     785            0 :         reverse_op_.gai_error = EAI_MEMORY;
     786            0 :         svc_.post(&reverse_op_);
     787            0 :     }
     788           10 :     return std::noop_coroutine();
     789              : }
     790              : 
     791              : void
     792           34 : posix_resolver_impl::
     793              : cancel() noexcept
     794              : {
     795           34 :     op_.request_cancel();
     796           34 :     reverse_op_.request_cancel();
     797           34 : }
     798              : 
     799              : //------------------------------------------------------------------------------
     800              : // posix_resolver_service_impl implementation
     801              : //------------------------------------------------------------------------------
     802              : 
     803              : void
     804          309 : posix_resolver_service_impl::
     805              : shutdown()
     806              : {
     807              :     {
     808          309 :         std::lock_guard<std::mutex> lock(mutex_);
     809              : 
     810              :         // Signal threads to not access service after getaddrinfo returns
     811          309 :         shutting_down_.store(true, std::memory_order_release);
     812              : 
     813              :         // Cancel all resolvers (sets cancelled flag checked by threads)
     814          310 :         for (auto* impl = resolver_list_.pop_front(); impl != nullptr;
     815            1 :              impl = resolver_list_.pop_front())
     816              :         {
     817            1 :             impl->cancel();
     818              :         }
     819              : 
     820              :         // Clear the map which releases shared_ptrs
     821          309 :         resolver_ptrs_.clear();
     822          309 :     }
     823              : 
     824              :     // Wait for all worker threads to finish before service is destroyed
     825              :     {
     826          309 :         std::unique_lock<std::mutex> lock(mutex_);
     827          618 :         cv_.wait(lock, [this] { return active_threads_ == 0; });
     828          309 :     }
     829          309 : }
     830              : 
     831              : resolver::resolver_impl&
     832           29 : posix_resolver_service_impl::
     833              : create_impl()
     834              : {
     835           29 :     auto ptr = std::make_shared<posix_resolver_impl>(*this);
     836           29 :     auto* impl = ptr.get();
     837              : 
     838              :     {
     839           29 :         std::lock_guard<std::mutex> lock(mutex_);
     840           29 :         resolver_list_.push_back(impl);
     841           29 :         resolver_ptrs_[impl] = std::move(ptr);
     842           29 :     }
     843              : 
     844           29 :     return *impl;
     845           29 : }
     846              : 
     847              : void
     848           28 : posix_resolver_service_impl::
     849              : destroy_impl(posix_resolver_impl& impl)
     850              : {
     851           28 :     std::lock_guard<std::mutex> lock(mutex_);
     852           28 :     resolver_list_.remove(&impl);
     853           28 :     resolver_ptrs_.erase(&impl);
     854           28 : }
     855              : 
     856              : void
     857           26 : posix_resolver_service_impl::
     858              : post(scheduler_op* op)
     859              : {
     860           26 :     sched_->post(op);
     861           26 : }
     862              : 
     863              : void
     864            0 : posix_resolver_service_impl::
     865              : work_started() noexcept
     866              : {
     867            0 :     sched_->work_started();
     868            0 : }
     869              : 
     870              : void
     871           26 : posix_resolver_service_impl::
     872              : work_finished() noexcept
     873              : {
     874           26 :     sched_->work_finished();
     875           26 : }
     876              : 
     877              : void
     878           26 : posix_resolver_service_impl::
     879              : thread_started() noexcept
     880              : {
     881           26 :     std::lock_guard<std::mutex> lock(mutex_);
     882           26 :     ++active_threads_;
     883           26 : }
     884              : 
     885              : void
     886           26 : posix_resolver_service_impl::
     887              : thread_finished() noexcept
     888              : {
     889           26 :     std::lock_guard<std::mutex> lock(mutex_);
     890           26 :     --active_threads_;
     891           26 :     cv_.notify_one();
     892           26 : }
     893              : 
     894              : bool
     895            0 : posix_resolver_service_impl::
     896              : is_shutting_down() const noexcept
     897              : {
     898            0 :     return shutting_down_.load(std::memory_order_acquire);
     899              : }
     900              : 
     901              : //------------------------------------------------------------------------------
     902              : // Free function to get/create the resolver service
     903              : //------------------------------------------------------------------------------
     904              : 
     905              : posix_resolver_service&
     906          309 : get_resolver_service(capy::execution_context& ctx, scheduler& sched)
     907              : {
     908          309 :     return ctx.make_service<posix_resolver_service_impl>(sched);
     909              : }
     910              : 
     911              : } // namespace boost::corosio::detail
     912              : 
     913              : #endif // BOOST_COROSIO_POSIX
        

Generated by: LCOV version 2.3