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/thread_pool.hpp>
 
25  
#include <boost/corosio/detail/scheduler_op.hpp>
24  
#include <boost/corosio/detail/scheduler_op.hpp>
26  

25  

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

31  

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

35  

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

48  

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

52  

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

63  

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

69  

57  
    Class Hierarchy
70  
    Class Hierarchy
58  
    ---------------
71  
    ---------------
59  
    - posix_resolver_service (execution_context service, one per context)
72  
    - posix_resolver_service (execution_context service, one per context)
60  
        - Owns all posix_resolver instances via shared_ptr
73  
        - Owns all posix_resolver instances via shared_ptr
61  
        - Stores scheduler* for posting completions
74  
        - Stores scheduler* for posting completions
62  
    - posix_resolver (one per resolver object)
75  
    - posix_resolver (one per resolver object)
63  
        - Contains embedded resolve_op and reverse_resolve_op for reuse
76  
        - Contains embedded resolve_op and reverse_resolve_op for reuse
64  
        - Uses shared_from_this to prevent premature destruction
77  
        - Uses shared_from_this to prevent premature destruction
65  
    - resolve_op (forward resolution state)
78  
    - resolve_op (forward resolution state)
66  
        - Uses getaddrinfo() to resolve host/service to endpoints
79  
        - Uses getaddrinfo() to resolve host/service to endpoints
67  
    - reverse_resolve_op (reverse resolution state)
80  
    - reverse_resolve_op (reverse resolution state)
68  
        - Uses getnameinfo() to resolve endpoint to host/service
81  
        - Uses getnameinfo() to resolve endpoint to host/service
69  

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 +

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

97  

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

99  

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

105  

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

112  

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

114  

96  
struct scheduler;
115  
struct scheduler;
97  

116  

98  
namespace posix_resolver_detail {
117  
namespace posix_resolver_detail {
99  

118  

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

121  

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

124  

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

128  

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

131  

113  
} // namespace posix_resolver_detail
132  
} // namespace posix_resolver_detail
114  

133  

115  
class posix_resolver_service;
134  
class posix_resolver_service;
116  

135  

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

137  

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

141  

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

143  

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

147  

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

152  

134  
    @par Safe Usage Patterns
153  
    @par Safe Usage Patterns
135  

154  

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

159  

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

164  

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

170  

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

181  

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

184  

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

195  

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

200  

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

204  

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

209  

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

213  

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

217  

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

219  

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

226  

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

228  

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

239  

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

244  

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

248  

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

252  

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

257  

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

261  

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

263  

245  
        void reset() noexcept;
264  
        void reset() noexcept;
246  
        void operator()() override;
265  
        void operator()() override;
247  
        void destroy() override;
266  
        void destroy() override;
248  
        void request_cancel() noexcept;
267  
        void request_cancel() noexcept;
249  
        void start(std::stop_token const& token);
268  
        void start(std::stop_token const& token);
250  
    };
269  
    };
251 -
    /// Embedded pool work item for thread pool dispatch.
 
252 -
    struct pool_op : pool_work_item
 
253 -
    {
 
254 -
        /// Resolver that owns this work item.
 
255 -
        posix_resolver* resolver_ = nullptr;
 
256 -

 
257 -
        /// Prevent impl destruction while work is in flight.
 
258 -
        std::shared_ptr<posix_resolver> ref_;
 
259 -
    };
 
260 -

 
261  

270  

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

272  

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

282  

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

291  

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

293  

285  
    resolve_op op_;
294  
    resolve_op op_;
286 -

 
287 -
    /// Pool work item for forward resolution.
 
288 -
    pool_op resolve_pool_op_;
 
289 -

 
290 -
    /// Pool work item for reverse resolution.
 
291 -
    pool_op reverse_pool_op_;
 
292 -

 
293 -
    /// Execute blocking `getaddrinfo()` on a pool thread.
 
294 -
    static void do_resolve_work(pool_work_item*) noexcept;
 
295 -

 
296 -
    /// Execute blocking `getnameinfo()` on a pool thread.
 
297 -
    static void do_reverse_resolve_work(pool_work_item*) noexcept;
 
298  
    reverse_resolve_op reverse_op_;
295  
    reverse_resolve_op reverse_op_;
299  

296  

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

300  

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

302  

306  
#endif // BOOST_COROSIO_POSIX
303  
#endif // BOOST_COROSIO_POSIX
307  

304  

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