1  
//
1  
//
2  
// Copyright (c) 2026 Steve Gerbino
2  
// Copyright (c) 2026 Steve Gerbino
3  
//
3  
//
4  
// Distributed under the Boost Software License, Version 1.0. (See accompanying
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)
5  
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
6  
//
6  
//
7  
// Official repository: https://github.com/cppalliance/corosio
7  
// Official repository: https://github.com/cppalliance/corosio
8  
//
8  
//
9  

9  

10  
#ifndef BOOST_COROSIO_NATIVE_DETAIL_POSIX_POSIX_RESOLVER_HPP
10  
#ifndef BOOST_COROSIO_NATIVE_DETAIL_POSIX_POSIX_RESOLVER_HPP
11  
#define BOOST_COROSIO_NATIVE_DETAIL_POSIX_POSIX_RESOLVER_HPP
11  
#define BOOST_COROSIO_NATIVE_DETAIL_POSIX_POSIX_RESOLVER_HPP
12  

12  

13  
#include <boost/corosio/detail/platform.hpp>
13  
#include <boost/corosio/detail/platform.hpp>
14  

14  

15  
#if BOOST_COROSIO_POSIX
15  
#if BOOST_COROSIO_POSIX
16  

16  

17  
#include <boost/corosio/detail/config.hpp>
17  
#include <boost/corosio/detail/config.hpp>
18  
#include <boost/corosio/resolver.hpp>
18  
#include <boost/corosio/resolver.hpp>
19  
#include <boost/capy/ex/execution_context.hpp>
19  
#include <boost/capy/ex/execution_context.hpp>
20  

20  

21  
#include <boost/corosio/native/detail/endpoint_convert.hpp>
21  
#include <boost/corosio/native/detail/endpoint_convert.hpp>
22  
#include <boost/corosio/detail/intrusive.hpp>
22  
#include <boost/corosio/detail/intrusive.hpp>
23  
#include <boost/corosio/detail/dispatch_coro.hpp>
23  
#include <boost/corosio/detail/dispatch_coro.hpp>
24  
#include <boost/corosio/detail/scheduler_op.hpp>
24  
#include <boost/corosio/detail/scheduler_op.hpp>
 
25 +
#include <boost/corosio/detail/thread_pool.hpp>
25  

26  

26  
#include <boost/corosio/detail/scheduler.hpp>
27  
#include <boost/corosio/detail/scheduler.hpp>
27  
#include <boost/corosio/resolver_results.hpp>
28  
#include <boost/corosio/resolver_results.hpp>
28  
#include <boost/capy/ex/executor_ref.hpp>
29  
#include <boost/capy/ex/executor_ref.hpp>
29  
#include <coroutine>
30  
#include <coroutine>
30  
#include <boost/capy/error.hpp>
31  
#include <boost/capy/error.hpp>
31  

32  

32  
#include <netdb.h>
33  
#include <netdb.h>
33  
#include <netinet/in.h>
34  
#include <netinet/in.h>
34  
#include <sys/socket.h>
35  
#include <sys/socket.h>
35  

36  

36 -
#include <cassert>
 
37 -
#include <condition_variable>
 
38 -
#include <cstring>
 
39  
#include <atomic>
37  
#include <atomic>
40 -
#include <mutex>
 
41  
#include <memory>
38  
#include <memory>
42  
#include <optional>
39  
#include <optional>
43  
#include <stop_token>
40  
#include <stop_token>
44 -
#include <thread>
 
45 -
#include <unordered_map>
 
46 -
#include <vector>
 
47  
#include <string>
41  
#include <string>
48  

42  

49  
/*
43  
/*
50  
    POSIX Resolver Service
44  
    POSIX Resolver Service
51  
    ======================
45  
    ======================
52  

46  

53  
    POSIX getaddrinfo() is a blocking call that cannot be monitored with
47  
    POSIX getaddrinfo() is a blocking call that cannot be monitored with
54 -
    epoll/kqueue/io_uring. We use a worker thread approach: each resolution
48 +
    epoll/kqueue/io_uring. Blocking calls are dispatched to a shared
55 -
    spawns a dedicated thread that runs the blocking call and posts completion
49 +
    resolver_thread_pool service which reuses threads across operations.
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  

50  

64  
    Cancellation
51  
    Cancellation
65  
    ------------
52  
    ------------
66  
    getaddrinfo() cannot be interrupted mid-call. We use an atomic flag to
53  
    getaddrinfo() cannot be interrupted mid-call. We use an atomic flag to
67  
    indicate cancellation was requested. The worker thread checks this flag
54  
    indicate cancellation was requested. The worker thread checks this flag
68  
    after getaddrinfo() returns and reports the appropriate error.
55  
    after getaddrinfo() returns and reports the appropriate error.
69  

56  

70  
    Class Hierarchy
57  
    Class Hierarchy
71  
    ---------------
58  
    ---------------
72  
    - posix_resolver_service (execution_context service, one per context)
59  
    - posix_resolver_service (execution_context service, one per context)
73  
        - Owns all posix_resolver instances via shared_ptr
60  
        - Owns all posix_resolver instances via shared_ptr
74  
        - Stores scheduler* for posting completions
61  
        - Stores scheduler* for posting completions
75  
    - posix_resolver (one per resolver object)
62  
    - posix_resolver (one per resolver object)
76  
        - Contains embedded resolve_op and reverse_resolve_op for reuse
63  
        - Contains embedded resolve_op and reverse_resolve_op for reuse
77  
        - Uses shared_from_this to prevent premature destruction
64  
        - Uses shared_from_this to prevent premature destruction
78  
    - resolve_op (forward resolution state)
65  
    - resolve_op (forward resolution state)
79  
        - Uses getaddrinfo() to resolve host/service to endpoints
66  
        - Uses getaddrinfo() to resolve host/service to endpoints
80  
    - reverse_resolve_op (reverse resolution state)
67  
    - reverse_resolve_op (reverse resolution state)
81  
        - Uses getnameinfo() to resolve endpoint to host/service
68  
        - Uses getnameinfo() to resolve endpoint to host/service
82 -
    Worker Thread Lifetime
 
83 -
    ----------------------
 
84 -
    Each resolve() spawns a detached thread. The thread captures a shared_ptr
 
85 -
    to posix_resolver, ensuring the impl (and its embedded op_) stays
 
86 -
    alive until the thread completes, even if the resolver is destroyed.
 
87 -

 
88  

69  

89  
    Completion Flow
70  
    Completion Flow
90  
    ---------------
71  
    ---------------
91  
    Forward resolution:
72  
    Forward resolution:
92 -
    1. resolve() sets up op_, spawns worker thread
73 +
    1. resolve() sets up op_, posts work to the thread pool
93 -
    2. Worker runs getaddrinfo() (blocking)
74 +
    2. Pool thread runs getaddrinfo() (blocking)
94 -
    3. Worker stores results in op_.stored_results
75 +
    3. Pool thread stores results in op_.stored_results
95 -
    4. Worker calls svc_.post(&op_) to queue completion
76 +
    4. Pool thread calls svc_.post(&op_) to queue completion
96  
    5. Scheduler invokes op_() which resumes the coroutine
77  
    5. Scheduler invokes op_() which resumes the coroutine
97  

78  

98  
    Reverse resolution follows the same pattern using getnameinfo().
79  
    Reverse resolution follows the same pattern using getnameinfo().
99  

80  

100  
    Single-Inflight Constraint
81  
    Single-Inflight Constraint
101  
    --------------------------
82  
    --------------------------
102  
    Each resolver has ONE embedded op_ for forward and ONE reverse_op_ for
83  
    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
84  
    reverse resolution. Concurrent operations of the same type on the same
104  
    resolver would corrupt state. Users must serialize operations per-resolver.
85  
    resolver would corrupt state. Users must serialize operations per-resolver.
105  

86  

106 -
    Shutdown Synchronization
87 +
    Shutdown
107 -
    ------------------------
88 +
    --------
108 -
    The service tracks active worker threads via thread_started()/thread_finished().
89 +
    The resolver service cancels all resolvers and clears the impl map.
109 -
    During shutdown(), the service sets shutting_down_ flag and waits for all
90 +
    The thread pool service shuts down separately via execution_context
110 -
    threads to complete before destroying resources.
91 +
    service ordering, joining all worker threads.
111  
*/
92  
*/
112  

93  

113  
namespace boost::corosio::detail {
94  
namespace boost::corosio::detail {
114  

95  

115  
struct scheduler;
96  
struct scheduler;
116  

97  

117  
namespace posix_resolver_detail {
98  
namespace posix_resolver_detail {
118  

99  

119  
// Convert resolve_flags to addrinfo ai_flags
100  
// Convert resolve_flags to addrinfo ai_flags
120  
int flags_to_hints(resolve_flags flags);
101  
int flags_to_hints(resolve_flags flags);
121  

102  

122  
// Convert reverse_flags to getnameinfo NI_* flags
103  
// Convert reverse_flags to getnameinfo NI_* flags
123  
int flags_to_ni_flags(reverse_flags flags);
104  
int flags_to_ni_flags(reverse_flags flags);
124  

105  

125  
// Convert addrinfo results to resolver_results
106  
// Convert addrinfo results to resolver_results
126  
resolver_results convert_results(
107  
resolver_results convert_results(
127  
    struct addrinfo* ai, std::string_view host, std::string_view service);
108  
    struct addrinfo* ai, std::string_view host, std::string_view service);
128  

109  

129  
// Convert getaddrinfo error codes to std::error_code
110  
// Convert getaddrinfo error codes to std::error_code
130  
std::error_code make_gai_error(int gai_err);
111  
std::error_code make_gai_error(int gai_err);
131  

112  

132  
} // namespace posix_resolver_detail
113  
} // namespace posix_resolver_detail
133  

114  

134  
class posix_resolver_service;
115  
class posix_resolver_service;
135  

116  

136  
/** Resolver implementation for POSIX backends.
117  
/** Resolver implementation for POSIX backends.
137  

118  

138  
    Each resolver instance contains a single embedded operation object (op_)
119  
    Each resolver instance contains a single embedded operation object (op_)
139  
    that is reused for each resolve() call. This design avoids per-operation
120  
    that is reused for each resolve() call. This design avoids per-operation
140  
    heap allocation but imposes a critical constraint:
121  
    heap allocation but imposes a critical constraint:
141  

122  

142  
    @par Single-Inflight Contract
123  
    @par Single-Inflight Contract
143  

124  

144  
    Only ONE resolve operation may be in progress at a time per resolver
125  
    Only ONE resolve operation may be in progress at a time per resolver
145  
    instance. Calling resolve() while a previous resolve() is still pending
126  
    instance. Calling resolve() while a previous resolve() is still pending
146  
    results in undefined behavior:
127  
    results in undefined behavior:
147  

128  

148  
    - The new call overwrites op_ fields (host, service, coroutine handle)
129  
    - The new call overwrites op_ fields (host, service, coroutine handle)
149  
    - The worker thread from the first call reads corrupted state
130  
    - The worker thread from the first call reads corrupted state
150  
    - The wrong coroutine may be resumed, or resumed multiple times
131  
    - The wrong coroutine may be resumed, or resumed multiple times
151  
    - Data races occur on non-atomic op_ members
132  
    - Data races occur on non-atomic op_ members
152  

133  

153  
    @par Safe Usage Patterns
134  
    @par Safe Usage Patterns
154  

135  

155  
    @code
136  
    @code
156  
    // CORRECT: Sequential resolves
137  
    // CORRECT: Sequential resolves
157  
    auto [ec1, r1] = co_await resolver.resolve("host1", "80");
138  
    auto [ec1, r1] = co_await resolver.resolve("host1", "80");
158  
    auto [ec2, r2] = co_await resolver.resolve("host2", "80");
139  
    auto [ec2, r2] = co_await resolver.resolve("host2", "80");
159  

140  

160  
    // CORRECT: Parallel resolves with separate resolver instances
141  
    // CORRECT: Parallel resolves with separate resolver instances
161  
    resolver r1(ctx), r2(ctx);
142  
    resolver r1(ctx), r2(ctx);
162  
    auto [ec1, res1] = co_await r1.resolve("host1", "80");  // in one coroutine
143  
    auto [ec1, res1] = co_await r1.resolve("host1", "80");  // in one coroutine
163  
    auto [ec2, res2] = co_await r2.resolve("host2", "80");  // in another
144  
    auto [ec2, res2] = co_await r2.resolve("host2", "80");  // in another
164  

145  

165  
    // WRONG: Concurrent resolves on same resolver
146  
    // WRONG: Concurrent resolves on same resolver
166  
    // These may run concurrently if launched in parallel - UNDEFINED BEHAVIOR
147  
    // These may run concurrently if launched in parallel - UNDEFINED BEHAVIOR
167  
    auto f1 = resolver.resolve("host1", "80");
148  
    auto f1 = resolver.resolve("host1", "80");
168  
    auto f2 = resolver.resolve("host2", "80");  // BAD: overlaps with f1
149  
    auto f2 = resolver.resolve("host2", "80");  // BAD: overlaps with f1
169  
    @endcode
150  
    @endcode
170  

151  

171  
    @par Thread Safety
152  
    @par Thread Safety
172  
    Distinct objects: Safe.
153  
    Distinct objects: Safe.
173  
    Shared objects: Unsafe. See single-inflight contract above.
154  
    Shared objects: Unsafe. See single-inflight contract above.
174  
*/
155  
*/
175  
class posix_resolver final
156  
class posix_resolver final
176  
    : public resolver::implementation
157  
    : public resolver::implementation
177  
    , public std::enable_shared_from_this<posix_resolver>
158  
    , public std::enable_shared_from_this<posix_resolver>
178  
    , public intrusive_list<posix_resolver>::node
159  
    , public intrusive_list<posix_resolver>::node
179  
{
160  
{
180  
    friend class posix_resolver_service;
161  
    friend class posix_resolver_service;
181  

162  

182  
public:
163  
public:
183  
    // resolve_op - operation state for a single DNS resolution
164  
    // resolve_op - operation state for a single DNS resolution
184  

165  

185  
    struct resolve_op : scheduler_op
166  
    struct resolve_op : scheduler_op
186  
    {
167  
    {
187  
        struct canceller
168  
        struct canceller
188  
        {
169  
        {
189  
            resolve_op* op;
170  
            resolve_op* op;
190  
            void operator()() const noexcept
171  
            void operator()() const noexcept
191  
            {
172  
            {
192  
                op->request_cancel();
173  
                op->request_cancel();
193  
            }
174  
            }
194  
        };
175  
        };
195  

176  

196  
        // Coroutine state
177  
        // Coroutine state
197  
        std::coroutine_handle<> h;
178  
        std::coroutine_handle<> h;
198  
        capy::executor_ref ex;
179  
        capy::executor_ref ex;
199  
        posix_resolver* impl = nullptr;
180  
        posix_resolver* impl = nullptr;
200  

181  

201  
        // Output parameters
182  
        // Output parameters
202  
        std::error_code* ec_out = nullptr;
183  
        std::error_code* ec_out = nullptr;
203  
        resolver_results* out   = nullptr;
184  
        resolver_results* out   = nullptr;
204  

185  

205  
        // Input parameters (owned copies for thread safety)
186  
        // Input parameters (owned copies for thread safety)
206  
        std::string host;
187  
        std::string host;
207  
        std::string service;
188  
        std::string service;
208  
        resolve_flags flags = resolve_flags::none;
189  
        resolve_flags flags = resolve_flags::none;
209  

190  

210  
        // Result storage (populated by worker thread)
191  
        // Result storage (populated by worker thread)
211  
        resolver_results stored_results;
192  
        resolver_results stored_results;
212  
        int gai_error = 0;
193  
        int gai_error = 0;
213  

194  

214  
        // Thread coordination
195  
        // Thread coordination
215  
        std::atomic<bool> cancelled{false};
196  
        std::atomic<bool> cancelled{false};
216  
        std::optional<std::stop_callback<canceller>> stop_cb;
197  
        std::optional<std::stop_callback<canceller>> stop_cb;
217  

198  

218  
        resolve_op() = default;
199  
        resolve_op() = default;
219  

200  

220  
        void reset() noexcept;
201  
        void reset() noexcept;
221  
        void operator()() override;
202  
        void operator()() override;
222  
        void destroy() override;
203  
        void destroy() override;
223  
        void request_cancel() noexcept;
204  
        void request_cancel() noexcept;
224  
        void start(std::stop_token const& token);
205  
        void start(std::stop_token const& token);
225  
    };
206  
    };
226  

207  

227  
    // reverse_resolve_op - operation state for reverse DNS resolution
208  
    // reverse_resolve_op - operation state for reverse DNS resolution
228  

209  

229  
    struct reverse_resolve_op : scheduler_op
210  
    struct reverse_resolve_op : scheduler_op
230  
    {
211  
    {
231  
        struct canceller
212  
        struct canceller
232  
        {
213  
        {
233  
            reverse_resolve_op* op;
214  
            reverse_resolve_op* op;
234  
            void operator()() const noexcept
215  
            void operator()() const noexcept
235  
            {
216  
            {
236  
                op->request_cancel();
217  
                op->request_cancel();
237  
            }
218  
            }
238  
        };
219  
        };
239  

220  

240  
        // Coroutine state
221  
        // Coroutine state
241  
        std::coroutine_handle<> h;
222  
        std::coroutine_handle<> h;
242  
        capy::executor_ref ex;
223  
        capy::executor_ref ex;
243  
        posix_resolver* impl = nullptr;
224  
        posix_resolver* impl = nullptr;
244  

225  

245  
        // Output parameters
226  
        // Output parameters
246  
        std::error_code* ec_out             = nullptr;
227  
        std::error_code* ec_out             = nullptr;
247  
        reverse_resolver_result* result_out = nullptr;
228  
        reverse_resolver_result* result_out = nullptr;
248  

229  

249  
        // Input parameters
230  
        // Input parameters
250  
        endpoint ep;
231  
        endpoint ep;
251  
        reverse_flags flags = reverse_flags::none;
232  
        reverse_flags flags = reverse_flags::none;
252  

233  

253  
        // Result storage (populated by worker thread)
234  
        // Result storage (populated by worker thread)
254  
        std::string stored_host;
235  
        std::string stored_host;
255  
        std::string stored_service;
236  
        std::string stored_service;
256  
        int gai_error = 0;
237  
        int gai_error = 0;
257  

238  

258  
        // Thread coordination
239  
        // Thread coordination
259  
        std::atomic<bool> cancelled{false};
240  
        std::atomic<bool> cancelled{false};
260  
        std::optional<std::stop_callback<canceller>> stop_cb;
241  
        std::optional<std::stop_callback<canceller>> stop_cb;
261  

242  

262  
        reverse_resolve_op() = default;
243  
        reverse_resolve_op() = default;
263  

244  

264  
        void reset() noexcept;
245  
        void reset() noexcept;
265  
        void operator()() override;
246  
        void operator()() override;
266  
        void destroy() override;
247  
        void destroy() override;
267  
        void request_cancel() noexcept;
248  
        void request_cancel() noexcept;
268  
        void start(std::stop_token const& token);
249  
        void start(std::stop_token const& token);
269  
    };
250  
    };
270  

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 +

271  
    explicit posix_resolver(posix_resolver_service& svc) noexcept;
262  
    explicit posix_resolver(posix_resolver_service& svc) noexcept;
272  

263  

273  
    std::coroutine_handle<> resolve(
264  
    std::coroutine_handle<> resolve(
274  
        std::coroutine_handle<>,
265  
        std::coroutine_handle<>,
275  
        capy::executor_ref,
266  
        capy::executor_ref,
276  
        std::string_view host,
267  
        std::string_view host,
277  
        std::string_view service,
268  
        std::string_view service,
278  
        resolve_flags flags,
269  
        resolve_flags flags,
279  
        std::stop_token,
270  
        std::stop_token,
280  
        std::error_code*,
271  
        std::error_code*,
281  
        resolver_results*) override;
272  
        resolver_results*) override;
282  

273  

283  
    std::coroutine_handle<> reverse_resolve(
274  
    std::coroutine_handle<> reverse_resolve(
284  
        std::coroutine_handle<>,
275  
        std::coroutine_handle<>,
285  
        capy::executor_ref,
276  
        capy::executor_ref,
286  
        endpoint const& ep,
277  
        endpoint const& ep,
287  
        reverse_flags flags,
278  
        reverse_flags flags,
288  
        std::stop_token,
279  
        std::stop_token,
289  
        std::error_code*,
280  
        std::error_code*,
290  
        reverse_resolver_result*) override;
281  
        reverse_resolver_result*) override;
291  

282  

292  
    void cancel() noexcept override;
283  
    void cancel() noexcept override;
293  

284  

294  
    resolve_op op_;
285  
    resolve_op op_;
295  
    reverse_resolve_op reverse_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;
296  

299  

297  
private:
300  
private:
298  
    posix_resolver_service& svc_;
301  
    posix_resolver_service& svc_;
299  
};
302  
};
300  

303  

301  
} // namespace boost::corosio::detail
304  
} // namespace boost::corosio::detail
302  

305  

303  
#endif // BOOST_COROSIO_POSIX
306  
#endif // BOOST_COROSIO_POSIX
304  

307  

305  
#endif // BOOST_COROSIO_NATIVE_DETAIL_POSIX_POSIX_RESOLVER_HPP
308  
#endif // BOOST_COROSIO_NATIVE_DETAIL_POSIX_POSIX_RESOLVER_HPP