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

Generated by: LCOV version 2.3