LCOV - code coverage report
Current view: top level - corosio/native/detail/posix - posix_resolver.hpp (source / functions) Coverage Total Hit Missed
Test: coverage_remapped.info Lines: 25.0 % 8 2 6
Test Date: 2026-02-17 21:31:10 Functions: 50.0 % 4 2 2

           TLA  Line data    Source code
       1                 : //
       2                 : // Copyright (c) 2026 Steve Gerbino
       3                 : //
       4                 : // Distributed under the Boost Software License, Version 1.0. (See accompanying
       5                 : // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
       6                 : //
       7                 : // Official repository: https://github.com/cppalliance/corosio
       8                 : //
       9                 : 
      10                 : #ifndef BOOST_COROSIO_NATIVE_DETAIL_POSIX_POSIX_RESOLVER_HPP
      11                 : #define BOOST_COROSIO_NATIVE_DETAIL_POSIX_POSIX_RESOLVER_HPP
      12                 : 
      13                 : #include <boost/corosio/detail/platform.hpp>
      14                 : 
      15                 : #if BOOST_COROSIO_POSIX
      16                 : 
      17                 : #include <boost/corosio/detail/config.hpp>
      18                 : #include <boost/corosio/resolver.hpp>
      19                 : #include <boost/capy/ex/execution_context.hpp>
      20                 : 
      21                 : #include <boost/corosio/detail/endpoint_convert.hpp>
      22                 : #include <boost/corosio/detail/intrusive.hpp>
      23                 : #include <boost/corosio/detail/dispatch_coro.hpp>
      24                 : #include <boost/corosio/detail/scheduler_op.hpp>
      25                 : 
      26                 : #include <boost/corosio/detail/scheduler.hpp>
      27                 : #include <boost/corosio/resolver_results.hpp>
      28                 : #include <boost/capy/ex/executor_ref.hpp>
      29                 : #include <coroutine>
      30                 : #include <boost/capy/error.hpp>
      31                 : 
      32                 : #include <netdb.h>
      33                 : #include <netinet/in.h>
      34                 : #include <sys/socket.h>
      35                 : 
      36                 : #include <atomic>
      37                 : #include <cassert>
      38                 : #include <condition_variable>
      39                 : #include <cstring>
      40                 : #include <memory>
      41                 : #include <mutex>
      42                 : #include <optional>
      43                 : #include <stop_token>
      44                 : #include <string>
      45                 : #include <thread>
      46                 : #include <unordered_map>
      47                 : #include <vector>
      48                 : 
      49                 : /*
      50                 :     POSIX Resolver Service
      51                 :     ======================
      52                 : 
      53                 :     POSIX getaddrinfo() is a blocking call that cannot be monitored with
      54                 :     epoll/kqueue/io_uring. We use a worker thread approach: each resolution
      55                 :     spawns a dedicated thread that runs the blocking call and posts completion
      56                 :     back to the scheduler.
      57                 : 
      58                 :     Thread-per-resolution Design
      59                 :     ----------------------------
      60                 :     Simple, no thread pool complexity. DNS lookups are infrequent enough that
      61                 :     thread creation overhead is acceptable. Detached threads self-manage;
      62                 :     shared_ptr capture keeps impl alive until completion.
      63                 : 
      64                 :     Cancellation
      65                 :     ------------
      66                 :     getaddrinfo() cannot be interrupted mid-call. We use an atomic flag to
      67                 :     indicate cancellation was requested. The worker thread checks this flag
      68                 :     after getaddrinfo() returns and reports the appropriate error.
      69                 : 
      70                 :     Class Hierarchy
      71                 :     ---------------
      72                 :     - posix_resolver_service (execution_context service, one per context)
      73                 :         - Owns all posix_resolver instances via shared_ptr
      74                 :         - Stores scheduler* for posting completions
      75                 :     - posix_resolver (one per resolver object)
      76                 :         - Contains embedded resolve_op and reverse_resolve_op for reuse
      77                 :         - Uses shared_from_this to prevent premature destruction
      78                 :     - resolve_op (forward resolution state)
      79                 :         - Uses getaddrinfo() to resolve host/service to endpoints
      80                 :     - reverse_resolve_op (reverse resolution state)
      81                 :         - Uses getnameinfo() to resolve endpoint to host/service
      82                 : 
      83                 :     Worker Thread Lifetime
      84                 :     ----------------------
      85                 :     Each resolve() spawns a detached thread. The thread captures a shared_ptr
      86                 :     to posix_resolver, ensuring the impl (and its embedded op_) stays
      87                 :     alive until the thread completes, even if the resolver is destroyed.
      88                 : 
      89                 :     Completion Flow
      90                 :     ---------------
      91                 :     Forward resolution:
      92                 :     1. resolve() sets up op_, spawns worker thread
      93                 :     2. Worker runs getaddrinfo() (blocking)
      94                 :     3. Worker stores results in op_.stored_results
      95                 :     4. Worker calls svc_.post(&op_) to queue completion
      96                 :     5. Scheduler invokes op_() which resumes the coroutine
      97                 : 
      98                 :     Reverse resolution follows the same pattern using getnameinfo().
      99                 : 
     100                 :     Single-Inflight Constraint
     101                 :     --------------------------
     102                 :     Each resolver has ONE embedded op_ for forward and ONE reverse_op_ for
     103                 :     reverse resolution. Concurrent operations of the same type on the same
     104                 :     resolver would corrupt state. Users must serialize operations per-resolver.
     105                 : 
     106                 :     Shutdown Synchronization
     107                 :     ------------------------
     108                 :     The service tracks active worker threads via thread_started()/thread_finished().
     109                 :     During shutdown(), the service sets shutting_down_ flag and waits for all
     110                 :     threads to complete before destroying resources.
     111                 : */
     112                 : 
     113                 : namespace boost::corosio::detail {
     114                 : 
     115                 : struct scheduler;
     116                 : 
     117                 : namespace posix_resolver_detail {
     118                 : 
     119                 : // Convert resolve_flags to addrinfo ai_flags
     120                 : int flags_to_hints(resolve_flags flags);
     121                 : 
     122                 : // Convert reverse_flags to getnameinfo NI_* flags
     123                 : int flags_to_ni_flags(reverse_flags flags);
     124                 : 
     125                 : // Convert addrinfo results to resolver_results
     126                 : resolver_results convert_results(
     127                 :     struct addrinfo* ai, std::string_view host, std::string_view service);
     128                 : 
     129                 : // Convert getaddrinfo error codes to std::error_code
     130                 : std::error_code make_gai_error(int gai_err);
     131                 : 
     132                 : } // namespace posix_resolver_detail
     133                 : 
     134                 : class posix_resolver_service;
     135                 : 
     136                 : /** Resolver implementation for POSIX backends.
     137                 : 
     138                 :     Each resolver instance contains a single embedded operation object (op_)
     139                 :     that is reused for each resolve() call. This design avoids per-operation
     140                 :     heap allocation but imposes a critical constraint:
     141                 : 
     142                 :     @par Single-Inflight Contract
     143                 : 
     144                 :     Only ONE resolve operation may be in progress at a time per resolver
     145                 :     instance. Calling resolve() while a previous resolve() is still pending
     146                 :     results in undefined behavior:
     147                 : 
     148                 :     - The new call overwrites op_ fields (host, service, coroutine handle)
     149                 :     - The worker thread from the first call reads corrupted state
     150                 :     - The wrong coroutine may be resumed, or resumed multiple times
     151                 :     - Data races occur on non-atomic op_ members
     152                 : 
     153                 :     @par Safe Usage Patterns
     154                 : 
     155                 :     @code
     156                 :     // CORRECT: Sequential resolves
     157                 :     auto [ec1, r1] = co_await resolver.resolve("host1", "80");
     158                 :     auto [ec2, r2] = co_await resolver.resolve("host2", "80");
     159                 : 
     160                 :     // CORRECT: Parallel resolves with separate resolver instances
     161                 :     resolver r1(ctx), r2(ctx);
     162                 :     auto [ec1, res1] = co_await r1.resolve("host1", "80");  // in one coroutine
     163                 :     auto [ec2, res2] = co_await r2.resolve("host2", "80");  // in another
     164                 : 
     165                 :     // WRONG: Concurrent resolves on same resolver
     166                 :     // These may run concurrently if launched in parallel - UNDEFINED BEHAVIOR
     167                 :     auto f1 = resolver.resolve("host1", "80");
     168                 :     auto f2 = resolver.resolve("host2", "80");  // BAD: overlaps with f1
     169                 :     @endcode
     170                 : 
     171                 :     @par Thread Safety
     172                 :     Distinct objects: Safe.
     173                 :     Shared objects: Unsafe. See single-inflight contract above.
     174                 : */
     175                 : class posix_resolver final
     176                 :     : public resolver::implementation
     177                 :     , public std::enable_shared_from_this<posix_resolver>
     178                 :     , public intrusive_list<posix_resolver>::node
     179                 : {
     180                 :     friend class posix_resolver_service;
     181                 : 
     182                 : public:
     183                 :     // resolve_op - operation state for a single DNS resolution
     184                 : 
     185                 :     struct resolve_op : scheduler_op
     186                 :     {
     187                 :         struct canceller
     188                 :         {
     189                 :             resolve_op* op;
     190 MIS           0 :             void operator()() const noexcept
     191                 :             {
     192               0 :                 op->request_cancel();
     193               0 :             }
     194                 :         };
     195                 : 
     196                 :         // Coroutine state
     197                 :         std::coroutine_handle<> h;
     198                 :         capy::executor_ref ex;
     199                 :         posix_resolver* impl = nullptr;
     200                 : 
     201                 :         // Output parameters
     202                 :         std::error_code* ec_out = nullptr;
     203                 :         resolver_results* out   = nullptr;
     204                 : 
     205                 :         // Input parameters (owned copies for thread safety)
     206                 :         std::string host;
     207                 :         std::string service;
     208                 :         resolve_flags flags = resolve_flags::none;
     209                 : 
     210                 :         // Result storage (populated by worker thread)
     211                 :         resolver_results stored_results;
     212                 :         int gai_error = 0;
     213                 : 
     214                 :         // Thread coordination
     215                 :         std::atomic<bool> cancelled{false};
     216                 :         std::optional<std::stop_callback<canceller>> stop_cb;
     217                 : 
     218 HIT          29 :         resolve_op() = default;
     219                 : 
     220                 :         void reset() noexcept;
     221                 :         void operator()() override;
     222                 :         void destroy() override;
     223                 :         void request_cancel() noexcept;
     224                 :         void start(std::stop_token token);
     225                 :     };
     226                 : 
     227                 :     // reverse_resolve_op - operation state for reverse DNS resolution
     228                 : 
     229                 :     struct reverse_resolve_op : scheduler_op
     230                 :     {
     231                 :         struct canceller
     232                 :         {
     233                 :             reverse_resolve_op* op;
     234 MIS           0 :             void operator()() const noexcept
     235                 :             {
     236               0 :                 op->request_cancel();
     237               0 :             }
     238                 :         };
     239                 : 
     240                 :         // Coroutine state
     241                 :         std::coroutine_handle<> h;
     242                 :         capy::executor_ref ex;
     243                 :         posix_resolver* impl = nullptr;
     244                 : 
     245                 :         // Output parameters
     246                 :         std::error_code* ec_out             = nullptr;
     247                 :         reverse_resolver_result* result_out = nullptr;
     248                 : 
     249                 :         // Input parameters
     250                 :         endpoint ep;
     251                 :         reverse_flags flags = reverse_flags::none;
     252                 : 
     253                 :         // Result storage (populated by worker thread)
     254                 :         std::string stored_host;
     255                 :         std::string stored_service;
     256                 :         int gai_error = 0;
     257                 : 
     258                 :         // Thread coordination
     259                 :         std::atomic<bool> cancelled{false};
     260                 :         std::optional<std::stop_callback<canceller>> stop_cb;
     261                 : 
     262 HIT          29 :         reverse_resolve_op() = default;
     263                 : 
     264                 :         void reset() noexcept;
     265                 :         void operator()() override;
     266                 :         void destroy() override;
     267                 :         void request_cancel() noexcept;
     268                 :         void start(std::stop_token token);
     269                 :     };
     270                 : 
     271                 :     explicit posix_resolver(posix_resolver_service& svc) noexcept;
     272                 : 
     273                 :     std::coroutine_handle<> resolve(
     274                 :         std::coroutine_handle<>,
     275                 :         capy::executor_ref,
     276                 :         std::string_view host,
     277                 :         std::string_view service,
     278                 :         resolve_flags flags,
     279                 :         std::stop_token,
     280                 :         std::error_code*,
     281                 :         resolver_results*) override;
     282                 : 
     283                 :     std::coroutine_handle<> reverse_resolve(
     284                 :         std::coroutine_handle<>,
     285                 :         capy::executor_ref,
     286                 :         endpoint const& ep,
     287                 :         reverse_flags flags,
     288                 :         std::stop_token,
     289                 :         std::error_code*,
     290                 :         reverse_resolver_result*) override;
     291                 : 
     292                 :     void cancel() noexcept override;
     293                 : 
     294                 :     resolve_op op_;
     295                 :     reverse_resolve_op reverse_op_;
     296                 : 
     297                 : private:
     298                 :     posix_resolver_service& svc_;
     299                 : };
     300                 : 
     301                 : } // namespace boost::corosio::detail
     302                 : 
     303                 : #endif // BOOST_COROSIO_POSIX
     304                 : 
     305                 : #endif // BOOST_COROSIO_NATIVE_DETAIL_POSIX_POSIX_RESOLVER_HPP
        

Generated by: LCOV version 2.3