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_SERVICE_HPP
10  
#ifndef BOOST_COROSIO_NATIVE_DETAIL_POSIX_POSIX_RESOLVER_SERVICE_HPP
11  
#define BOOST_COROSIO_NATIVE_DETAIL_POSIX_POSIX_RESOLVER_SERVICE_HPP
11  
#define BOOST_COROSIO_NATIVE_DETAIL_POSIX_POSIX_RESOLVER_SERVICE_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/thread_pool.hpp>
 
18 -

 
19 -
#include <unordered_map>
 
20  
#include <boost/corosio/native/detail/posix/posix_resolver.hpp>
17  
#include <boost/corosio/native/detail/posix/posix_resolver.hpp>
21  

18  

22  
namespace boost::corosio::detail {
19  
namespace boost::corosio::detail {
23  

20  

24  
/** Resolver service for POSIX backends.
21  
/** Resolver service for POSIX backends.
25  

22  

26 -
    Owns all posix_resolver instances. Thread lifecycle is managed
23 +
    Owns all posix_resolver instances and tracks active worker
27 -
    by the thread_pool service.
24 +
    threads for safe shutdown synchronization.
28  
*/
25  
*/
29  
class BOOST_COROSIO_DECL posix_resolver_service final
26  
class BOOST_COROSIO_DECL posix_resolver_service final
30  
    : public capy::execution_context::service
27  
    : public capy::execution_context::service
31  
    , public io_object::io_service
28  
    , public io_object::io_service
32  
{
29  
{
33  
public:
30  
public:
34  
    using key_type = posix_resolver_service;
31  
    using key_type = posix_resolver_service;
35  

32  

36 -
    posix_resolver_service(capy::execution_context& ctx, scheduler& sched)
33 +
    posix_resolver_service(capy::execution_context&, scheduler& sched)
37 -
        , pool_(ctx.make_service<thread_pool>())
 
38  
        : sched_(&sched)
34  
        : sched_(&sched)
39  
    {
35  
    {
40  
    }
36  
    }
41  

37  

42  
    ~posix_resolver_service() override = default;
38  
    ~posix_resolver_service() override = default;
43  

39  

44  
    posix_resolver_service(posix_resolver_service const&)            = delete;
40  
    posix_resolver_service(posix_resolver_service const&)            = delete;
45  
    posix_resolver_service& operator=(posix_resolver_service const&) = delete;
41  
    posix_resolver_service& operator=(posix_resolver_service const&) = delete;
46  

42  

47  
    io_object::implementation* construct() override;
43  
    io_object::implementation* construct() override;
48  

44  

49  
    void destroy(io_object::implementation* p) override
45  
    void destroy(io_object::implementation* p) override
50  
    {
46  
    {
51  
        auto& impl = static_cast<posix_resolver&>(*p);
47  
        auto& impl = static_cast<posix_resolver&>(*p);
52  
        impl.cancel();
48  
        impl.cancel();
53  
        destroy_impl(impl);
49  
        destroy_impl(impl);
54  
    }
50  
    }
55  

51  

56  
    void shutdown() override;
52  
    void shutdown() override;
57  
    void destroy_impl(posix_resolver& impl);
53  
    void destroy_impl(posix_resolver& impl);
58  

54  

59  
    void post(scheduler_op* op);
55  
    void post(scheduler_op* op);
60  
    void work_started() noexcept;
56  
    void work_started() noexcept;
61  
    void work_finished() noexcept;
57  
    void work_finished() noexcept;
62  

58  

63 -
    /** Return the resolver thread pool. */
59 +
    void thread_started() noexcept;
64 -
    thread_pool& pool() noexcept { return pool_; }
60 +
    void thread_finished() noexcept;
 
61 +
    bool is_shutting_down() const noexcept;
65  

62  

66  
private:
63  
private:
67 -
    thread_pool& pool_;
 
68  
    scheduler* sched_;
64  
    scheduler* sched_;
69  
    std::mutex mutex_;
65  
    std::mutex mutex_;
 
66 +
    std::condition_variable cv_;
 
67 +
    std::atomic<bool> shutting_down_{false};
 
68 +
    std::size_t active_threads_ = 0;
70  
    intrusive_list<posix_resolver> resolver_list_;
69  
    intrusive_list<posix_resolver> resolver_list_;
71  
    std::unordered_map<posix_resolver*, std::shared_ptr<posix_resolver>>
70  
    std::unordered_map<posix_resolver*, std::shared_ptr<posix_resolver>>
72  
        resolver_ptrs_;
71  
        resolver_ptrs_;
73  
};
72  
};
74  

73  

75  
/** Get or create the resolver service for the given context.
74  
/** Get or create the resolver service for the given context.
76  

75  

77  
    This function is called by the concrete scheduler during initialization
76  
    This function is called by the concrete scheduler during initialization
78  
    to create the resolver service with a reference to itself.
77  
    to create the resolver service with a reference to itself.
79  

78  

80  
    @param ctx Reference to the owning execution_context.
79  
    @param ctx Reference to the owning execution_context.
81  
    @param sched Reference to the scheduler for posting completions.
80  
    @param sched Reference to the scheduler for posting completions.
82  
    @return Reference to the resolver service.
81  
    @return Reference to the resolver service.
83  
*/
82  
*/
84  
posix_resolver_service&
83  
posix_resolver_service&
85  
get_resolver_service(capy::execution_context& ctx, scheduler& sched);
84  
get_resolver_service(capy::execution_context& ctx, scheduler& sched);
86  

85  

87  
// ---------------------------------------------------------------------------
86  
// ---------------------------------------------------------------------------
88  
// Inline implementation
87  
// Inline implementation
89  
// ---------------------------------------------------------------------------
88  
// ---------------------------------------------------------------------------
90  

89  

91  
// posix_resolver_detail helpers
90  
// posix_resolver_detail helpers
92  

91  

93  
inline int
92  
inline int
94  
posix_resolver_detail::flags_to_hints(resolve_flags flags)
93  
posix_resolver_detail::flags_to_hints(resolve_flags flags)
95  
{
94  
{
96  
    int hints = 0;
95  
    int hints = 0;
97  

96  

98  
    if ((flags & resolve_flags::passive) != resolve_flags::none)
97  
    if ((flags & resolve_flags::passive) != resolve_flags::none)
99  
        hints |= AI_PASSIVE;
98  
        hints |= AI_PASSIVE;
100  
    if ((flags & resolve_flags::numeric_host) != resolve_flags::none)
99  
    if ((flags & resolve_flags::numeric_host) != resolve_flags::none)
101  
        hints |= AI_NUMERICHOST;
100  
        hints |= AI_NUMERICHOST;
102  
    if ((flags & resolve_flags::numeric_service) != resolve_flags::none)
101  
    if ((flags & resolve_flags::numeric_service) != resolve_flags::none)
103  
        hints |= AI_NUMERICSERV;
102  
        hints |= AI_NUMERICSERV;
104  
    if ((flags & resolve_flags::address_configured) != resolve_flags::none)
103  
    if ((flags & resolve_flags::address_configured) != resolve_flags::none)
105  
        hints |= AI_ADDRCONFIG;
104  
        hints |= AI_ADDRCONFIG;
106  
    if ((flags & resolve_flags::v4_mapped) != resolve_flags::none)
105  
    if ((flags & resolve_flags::v4_mapped) != resolve_flags::none)
107  
        hints |= AI_V4MAPPED;
106  
        hints |= AI_V4MAPPED;
108  
    if ((flags & resolve_flags::all_matching) != resolve_flags::none)
107  
    if ((flags & resolve_flags::all_matching) != resolve_flags::none)
109  
        hints |= AI_ALL;
108  
        hints |= AI_ALL;
110  

109  

111  
    return hints;
110  
    return hints;
112  
}
111  
}
113  

112  

114  
inline int
113  
inline int
115  
posix_resolver_detail::flags_to_ni_flags(reverse_flags flags)
114  
posix_resolver_detail::flags_to_ni_flags(reverse_flags flags)
116  
{
115  
{
117  
    int ni_flags = 0;
116  
    int ni_flags = 0;
118  

117  

119  
    if ((flags & reverse_flags::numeric_host) != reverse_flags::none)
118  
    if ((flags & reverse_flags::numeric_host) != reverse_flags::none)
120  
        ni_flags |= NI_NUMERICHOST;
119  
        ni_flags |= NI_NUMERICHOST;
121  
    if ((flags & reverse_flags::numeric_service) != reverse_flags::none)
120  
    if ((flags & reverse_flags::numeric_service) != reverse_flags::none)
122  
        ni_flags |= NI_NUMERICSERV;
121  
        ni_flags |= NI_NUMERICSERV;
123  
    if ((flags & reverse_flags::name_required) != reverse_flags::none)
122  
    if ((flags & reverse_flags::name_required) != reverse_flags::none)
124  
        ni_flags |= NI_NAMEREQD;
123  
        ni_flags |= NI_NAMEREQD;
125  
    if ((flags & reverse_flags::datagram_service) != reverse_flags::none)
124  
    if ((flags & reverse_flags::datagram_service) != reverse_flags::none)
126  
        ni_flags |= NI_DGRAM;
125  
        ni_flags |= NI_DGRAM;
127  

126  

128  
    return ni_flags;
127  
    return ni_flags;
129  
}
128  
}
130  

129  

131  
inline resolver_results
130  
inline resolver_results
132  
posix_resolver_detail::convert_results(
131  
posix_resolver_detail::convert_results(
133  
    struct addrinfo* ai, std::string_view host, std::string_view service)
132  
    struct addrinfo* ai, std::string_view host, std::string_view service)
134  
{
133  
{
135  
    std::vector<resolver_entry> entries;
134  
    std::vector<resolver_entry> entries;
136  
    entries.reserve(4); // Most lookups return 1-4 addresses
135  
    entries.reserve(4); // Most lookups return 1-4 addresses
137  

136  

138  
    for (auto* p = ai; p != nullptr; p = p->ai_next)
137  
    for (auto* p = ai; p != nullptr; p = p->ai_next)
139  
    {
138  
    {
140  
        if (p->ai_family == AF_INET)
139  
        if (p->ai_family == AF_INET)
141  
        {
140  
        {
142  
            auto* addr = reinterpret_cast<sockaddr_in*>(p->ai_addr);
141  
            auto* addr = reinterpret_cast<sockaddr_in*>(p->ai_addr);
143  
            auto ep    = from_sockaddr_in(*addr);
142  
            auto ep    = from_sockaddr_in(*addr);
144  
            entries.emplace_back(ep, host, service);
143  
            entries.emplace_back(ep, host, service);
145  
        }
144  
        }
146  
        else if (p->ai_family == AF_INET6)
145  
        else if (p->ai_family == AF_INET6)
147  
        {
146  
        {
148  
            auto* addr = reinterpret_cast<sockaddr_in6*>(p->ai_addr);
147  
            auto* addr = reinterpret_cast<sockaddr_in6*>(p->ai_addr);
149  
            auto ep    = from_sockaddr_in6(*addr);
148  
            auto ep    = from_sockaddr_in6(*addr);
150  
            entries.emplace_back(ep, host, service);
149  
            entries.emplace_back(ep, host, service);
151  
        }
150  
        }
152  
    }
151  
    }
153  

152  

154  
    return resolver_results(std::move(entries));
153  
    return resolver_results(std::move(entries));
155  
}
154  
}
156  

155  

157  
inline std::error_code
156  
inline std::error_code
158  
posix_resolver_detail::make_gai_error(int gai_err)
157  
posix_resolver_detail::make_gai_error(int gai_err)
159  
{
158  
{
160  
    // Map GAI errors to appropriate generic error codes
159  
    // Map GAI errors to appropriate generic error codes
161  
    switch (gai_err)
160  
    switch (gai_err)
162  
    {
161  
    {
163  
    case EAI_AGAIN:
162  
    case EAI_AGAIN:
164  
        // Temporary failure - try again later
163  
        // Temporary failure - try again later
165  
        return std::error_code(
164  
        return std::error_code(
166  
            static_cast<int>(std::errc::resource_unavailable_try_again),
165  
            static_cast<int>(std::errc::resource_unavailable_try_again),
167  
            std::generic_category());
166  
            std::generic_category());
168  

167  

169  
    case EAI_BADFLAGS:
168  
    case EAI_BADFLAGS:
170  
        // Invalid flags
169  
        // Invalid flags
171  
        return std::error_code(
170  
        return std::error_code(
172  
            static_cast<int>(std::errc::invalid_argument),
171  
            static_cast<int>(std::errc::invalid_argument),
173  
            std::generic_category());
172  
            std::generic_category());
174  

173  

175  
    case EAI_FAIL:
174  
    case EAI_FAIL:
176  
        // Non-recoverable failure
175  
        // Non-recoverable failure
177  
        return std::error_code(
176  
        return std::error_code(
178  
            static_cast<int>(std::errc::io_error), std::generic_category());
177  
            static_cast<int>(std::errc::io_error), std::generic_category());
179  

178  

180  
    case EAI_FAMILY:
179  
    case EAI_FAMILY:
181  
        // Address family not supported
180  
        // Address family not supported
182  
        return std::error_code(
181  
        return std::error_code(
183  
            static_cast<int>(std::errc::address_family_not_supported),
182  
            static_cast<int>(std::errc::address_family_not_supported),
184  
            std::generic_category());
183  
            std::generic_category());
185  

184  

186  
    case EAI_MEMORY:
185  
    case EAI_MEMORY:
187  
        // Memory allocation failure
186  
        // Memory allocation failure
188  
        return std::error_code(
187  
        return std::error_code(
189  
            static_cast<int>(std::errc::not_enough_memory),
188  
            static_cast<int>(std::errc::not_enough_memory),
190  
            std::generic_category());
189  
            std::generic_category());
191  

190  

192  
    case EAI_NONAME:
191  
    case EAI_NONAME:
193  
        // Host or service not found
192  
        // Host or service not found
194  
        return std::error_code(
193  
        return std::error_code(
195  
            static_cast<int>(std::errc::no_such_device_or_address),
194  
            static_cast<int>(std::errc::no_such_device_or_address),
196  
            std::generic_category());
195  
            std::generic_category());
197  

196  

198  
    case EAI_SERVICE:
197  
    case EAI_SERVICE:
199  
        // Service not supported for socket type
198  
        // Service not supported for socket type
200  
        return std::error_code(
199  
        return std::error_code(
201  
            static_cast<int>(std::errc::invalid_argument),
200  
            static_cast<int>(std::errc::invalid_argument),
202  
            std::generic_category());
201  
            std::generic_category());
203  

202  

204  
    case EAI_SOCKTYPE:
203  
    case EAI_SOCKTYPE:
205  
        // Socket type not supported
204  
        // Socket type not supported
206  
        return std::error_code(
205  
        return std::error_code(
207  
            static_cast<int>(std::errc::not_supported),
206  
            static_cast<int>(std::errc::not_supported),
208  
            std::generic_category());
207  
            std::generic_category());
209  

208  

210  
    case EAI_SYSTEM:
209  
    case EAI_SYSTEM:
211  
        // System error - use errno
210  
        // System error - use errno
212  
        return std::error_code(errno, std::generic_category());
211  
        return std::error_code(errno, std::generic_category());
213  

212  

214  
    default:
213  
    default:
215  
        // Unknown error
214  
        // Unknown error
216  
        return std::error_code(
215  
        return std::error_code(
217  
            static_cast<int>(std::errc::io_error), std::generic_category());
216  
            static_cast<int>(std::errc::io_error), std::generic_category());
218  
    }
217  
    }
219  
}
218  
}
220  

219  

221  
// posix_resolver
220  
// posix_resolver
222  

221  

223  
inline posix_resolver::posix_resolver(posix_resolver_service& svc) noexcept
222  
inline posix_resolver::posix_resolver(posix_resolver_service& svc) noexcept
224  
    : svc_(svc)
223  
    : svc_(svc)
225  
{
224  
{
226  
}
225  
}
227  

226  

228  
// posix_resolver::resolve_op implementation
227  
// posix_resolver::resolve_op implementation
229  

228  

230  
inline void
229  
inline void
231  
posix_resolver::resolve_op::reset() noexcept
230  
posix_resolver::resolve_op::reset() noexcept
232  
{
231  
{
233  
    host.clear();
232  
    host.clear();
234  
    service.clear();
233  
    service.clear();
235  
    flags          = resolve_flags::none;
234  
    flags          = resolve_flags::none;
236  
    stored_results = resolver_results{};
235  
    stored_results = resolver_results{};
237  
    gai_error      = 0;
236  
    gai_error      = 0;
238  
    cancelled.store(false, std::memory_order_relaxed);
237  
    cancelled.store(false, std::memory_order_relaxed);
239  
    stop_cb.reset();
238  
    stop_cb.reset();
240  
    ec_out = nullptr;
239  
    ec_out = nullptr;
241  
    out    = nullptr;
240  
    out    = nullptr;
242  
}
241  
}
243  

242  

244  
inline void
243  
inline void
245  
posix_resolver::resolve_op::operator()()
244  
posix_resolver::resolve_op::operator()()
246  
{
245  
{
247  
    stop_cb.reset(); // Disconnect stop callback
246  
    stop_cb.reset(); // Disconnect stop callback
248  

247  

249  
    bool const was_cancelled = cancelled.load(std::memory_order_acquire);
248  
    bool const was_cancelled = cancelled.load(std::memory_order_acquire);
250  

249  

251  
    if (ec_out)
250  
    if (ec_out)
252  
    {
251  
    {
253  
        if (was_cancelled)
252  
        if (was_cancelled)
254  
            *ec_out = capy::error::canceled;
253  
            *ec_out = capy::error::canceled;
255  
        else if (gai_error != 0)
254  
        else if (gai_error != 0)
256  
            *ec_out = posix_resolver_detail::make_gai_error(gai_error);
255  
            *ec_out = posix_resolver_detail::make_gai_error(gai_error);
257  
        else
256  
        else
258  
            *ec_out = {}; // Clear on success
257  
            *ec_out = {}; // Clear on success
259  
    }
258  
    }
260  

259  

261  
    if (out && !was_cancelled && gai_error == 0)
260  
    if (out && !was_cancelled && gai_error == 0)
262  
        *out = std::move(stored_results);
261  
        *out = std::move(stored_results);
263  

262  

264  
    impl->svc_.work_finished();
263  
    impl->svc_.work_finished();
265  
    dispatch_coro(ex, h).resume();
264  
    dispatch_coro(ex, h).resume();
266  
}
265  
}
267  

266  

268  
inline void
267  
inline void
269  
posix_resolver::resolve_op::destroy()
268  
posix_resolver::resolve_op::destroy()
270  
{
269  
{
271  
    stop_cb.reset();
270  
    stop_cb.reset();
272  
}
271  
}
273  

272  

274  
inline void
273  
inline void
275  
posix_resolver::resolve_op::request_cancel() noexcept
274  
posix_resolver::resolve_op::request_cancel() noexcept
276  
{
275  
{
277  
    cancelled.store(true, std::memory_order_release);
276  
    cancelled.store(true, std::memory_order_release);
278  
}
277  
}
279  

278  

280  
inline void
279  
inline void
281  
posix_resolver::resolve_op::start(std::stop_token const& token)
280  
posix_resolver::resolve_op::start(std::stop_token const& token)
282  
{
281  
{
283  
    cancelled.store(false, std::memory_order_release);
282  
    cancelled.store(false, std::memory_order_release);
284  
    stop_cb.reset();
283  
    stop_cb.reset();
285  

284  

286  
    if (token.stop_possible())
285  
    if (token.stop_possible())
287  
        stop_cb.emplace(token, canceller{this});
286  
        stop_cb.emplace(token, canceller{this});
288  
}
287  
}
289  

288  

290  
// posix_resolver::reverse_resolve_op implementation
289  
// posix_resolver::reverse_resolve_op implementation
291  

290  

292  
inline void
291  
inline void
293  
posix_resolver::reverse_resolve_op::reset() noexcept
292  
posix_resolver::reverse_resolve_op::reset() noexcept
294  
{
293  
{
295  
    ep    = endpoint{};
294  
    ep    = endpoint{};
296  
    flags = reverse_flags::none;
295  
    flags = reverse_flags::none;
297  
    stored_host.clear();
296  
    stored_host.clear();
298  
    stored_service.clear();
297  
    stored_service.clear();
299  
    gai_error = 0;
298  
    gai_error = 0;
300  
    cancelled.store(false, std::memory_order_relaxed);
299  
    cancelled.store(false, std::memory_order_relaxed);
301  
    stop_cb.reset();
300  
    stop_cb.reset();
302  
    ec_out     = nullptr;
301  
    ec_out     = nullptr;
303  
    result_out = nullptr;
302  
    result_out = nullptr;
304  
}
303  
}
305  

304  

306  
inline void
305  
inline void
307  
posix_resolver::reverse_resolve_op::operator()()
306  
posix_resolver::reverse_resolve_op::operator()()
308  
{
307  
{
309  
    stop_cb.reset(); // Disconnect stop callback
308  
    stop_cb.reset(); // Disconnect stop callback
310  

309  

311  
    bool const was_cancelled = cancelled.load(std::memory_order_acquire);
310  
    bool const was_cancelled = cancelled.load(std::memory_order_acquire);
312  

311  

313  
    if (ec_out)
312  
    if (ec_out)
314  
    {
313  
    {
315  
        if (was_cancelled)
314  
        if (was_cancelled)
316  
            *ec_out = capy::error::canceled;
315  
            *ec_out = capy::error::canceled;
317  
        else if (gai_error != 0)
316  
        else if (gai_error != 0)
318  
            *ec_out = posix_resolver_detail::make_gai_error(gai_error);
317  
            *ec_out = posix_resolver_detail::make_gai_error(gai_error);
319  
        else
318  
        else
320  
            *ec_out = {}; // Clear on success
319  
            *ec_out = {}; // Clear on success
321  
    }
320  
    }
322  

321  

323  
    if (result_out && !was_cancelled && gai_error == 0)
322  
    if (result_out && !was_cancelled && gai_error == 0)
324  
    {
323  
    {
325  
        *result_out = reverse_resolver_result(
324  
        *result_out = reverse_resolver_result(
326  
            ep, std::move(stored_host), std::move(stored_service));
325  
            ep, std::move(stored_host), std::move(stored_service));
327  
    }
326  
    }
328  

327  

329  
    impl->svc_.work_finished();
328  
    impl->svc_.work_finished();
330  
    dispatch_coro(ex, h).resume();
329  
    dispatch_coro(ex, h).resume();
331  
}
330  
}
332  

331  

333  
inline void
332  
inline void
334  
posix_resolver::reverse_resolve_op::destroy()
333  
posix_resolver::reverse_resolve_op::destroy()
335  
{
334  
{
336  
    stop_cb.reset();
335  
    stop_cb.reset();
337  
}
336  
}
338  

337  

339  
inline void
338  
inline void
340  
posix_resolver::reverse_resolve_op::request_cancel() noexcept
339  
posix_resolver::reverse_resolve_op::request_cancel() noexcept
341  
{
340  
{
342  
    cancelled.store(true, std::memory_order_release);
341  
    cancelled.store(true, std::memory_order_release);
343  
}
342  
}
344  

343  

345  
inline void
344  
inline void
346  
posix_resolver::reverse_resolve_op::start(std::stop_token const& token)
345  
posix_resolver::reverse_resolve_op::start(std::stop_token const& token)
347  
{
346  
{
348  
    cancelled.store(false, std::memory_order_release);
347  
    cancelled.store(false, std::memory_order_release);
349  
    stop_cb.reset();
348  
    stop_cb.reset();
350  

349  

351  
    if (token.stop_possible())
350  
    if (token.stop_possible())
352  
        stop_cb.emplace(token, canceller{this});
351  
        stop_cb.emplace(token, canceller{this});
353  
}
352  
}
354  

353  

355  
// posix_resolver implementation
354  
// posix_resolver implementation
356  

355  

357  
inline std::coroutine_handle<>
356  
inline std::coroutine_handle<>
358  
posix_resolver::resolve(
357  
posix_resolver::resolve(
359  
    std::coroutine_handle<> h,
358  
    std::coroutine_handle<> h,
360  
    capy::executor_ref ex,
359  
    capy::executor_ref ex,
361  
    std::string_view host,
360  
    std::string_view host,
362  
    std::string_view service,
361  
    std::string_view service,
363  
    resolve_flags flags,
362  
    resolve_flags flags,
364  
    std::stop_token token,
363  
    std::stop_token token,
365  
    std::error_code* ec,
364  
    std::error_code* ec,
366  
    resolver_results* out)
365  
    resolver_results* out)
367  
{
366  
{
368  
    auto& op = op_;
367  
    auto& op = op_;
369  
    op.reset();
368  
    op.reset();
370  
    op.h       = h;
369  
    op.h       = h;
371  
    op.ex      = ex;
370  
    op.ex      = ex;
372  
    op.impl    = this;
371  
    op.impl    = this;
373  
    op.ec_out  = ec;
372  
    op.ec_out  = ec;
374  
    op.out     = out;
373  
    op.out     = out;
375  
    op.host    = host;
374  
    op.host    = host;
376  
    op.service = service;
375  
    op.service = service;
377  
    op.flags   = flags;
376  
    op.flags   = flags;
378  
    op.start(token);
377  
    op.start(token);
379  

378  

380  
    // Keep io_context alive while resolution is pending
379  
    // Keep io_context alive while resolution is pending
381  
    op.ex.on_work_started();
380  
    op.ex.on_work_started();
382  

381  

383 -
    // Prevent impl destruction while work is in flight
382 +
    // Track thread for safe shutdown
384 -
    resolve_pool_op_.resolver_ = this;
383 +
    svc_.thread_started();
385 -
    resolve_pool_op_.ref_      = this->shared_from_this();
384 +

386 -
    resolve_pool_op_.func_     = &posix_resolver::do_resolve_work;
385 +
    try
387 -
    if (!svc_.pool().post(&resolve_pool_op_))
 
388  
    {
386  
    {
389 -
        // Pool shut down — complete with cancellation
387 +
        // Prevent impl destruction while worker thread is running
390 -
        resolve_pool_op_.ref_.reset();
388 +
        auto self = this->shared_from_this();
391 -
        op.cancelled.store(true, std::memory_order_release);
389 +
        std::thread worker([this, self = std::move(self)]() {
 
390 +
            struct addrinfo hints{};
 
391 +
            hints.ai_family   = AF_UNSPEC;
 
392 +
            hints.ai_socktype = SOCK_STREAM;
 
393 +
            hints.ai_flags = posix_resolver_detail::flags_to_hints(op_.flags);
 
394 +

 
395 +
            struct addrinfo* ai = nullptr;
 
396 +
            int result          = ::getaddrinfo(
 
397 +
                op_.host.empty() ? nullptr : op_.host.c_str(),
 
398 +
                op_.service.empty() ? nullptr : op_.service.c_str(), &hints,
 
399 +
                &ai);
 
400 +

 
401 +
            if (!op_.cancelled.load(std::memory_order_acquire))
 
402 +
            {
 
403 +
                if (result == 0 && ai)
 
404 +
                {
 
405 +
                    op_.stored_results = posix_resolver_detail::convert_results(
 
406 +
                        ai, op_.host, op_.service);
 
407 +
                    op_.gai_error = 0;
 
408 +
                }
 
409 +
                else
 
410 +
                {
 
411 +
                    op_.gai_error = result;
 
412 +
                }
 
413 +
            }
 
414 +

 
415 +
            if (ai)
 
416 +
                ::freeaddrinfo(ai);
 
417 +

 
418 +
            // Always post so the scheduler can properly drain the op
 
419 +
            // during shutdown via destroy().
 
420 +
            svc_.post(&op_);
 
421 +

 
422 +
            // Signal thread completion for shutdown synchronization
 
423 +
            svc_.thread_finished();
 
424 +
        });
 
425 +
        worker.detach();
 
426 +
    }
 
427 +
    catch (std::system_error const&)
 
428 +
    {
 
429 +
        // Thread creation failed - no thread was started
 
430 +
        svc_.thread_finished();
 
431 +

 
432 +
        // Set error and post completion to avoid hanging the coroutine
 
433 +
        op_.gai_error = EAI_MEMORY; // Map to "not enough memory"
392  
        svc_.post(&op_);
434  
        svc_.post(&op_);
393  
    }
435  
    }
394  
    return std::noop_coroutine();
436  
    return std::noop_coroutine();
395  
}
437  
}
396  

438  

397  
inline std::coroutine_handle<>
439  
inline std::coroutine_handle<>
398  
posix_resolver::reverse_resolve(
440  
posix_resolver::reverse_resolve(
399  
    std::coroutine_handle<> h,
441  
    std::coroutine_handle<> h,
400  
    capy::executor_ref ex,
442  
    capy::executor_ref ex,
401  
    endpoint const& ep,
443  
    endpoint const& ep,
402  
    reverse_flags flags,
444  
    reverse_flags flags,
403  
    std::stop_token token,
445  
    std::stop_token token,
404  
    std::error_code* ec,
446  
    std::error_code* ec,
405  
    reverse_resolver_result* result_out)
447  
    reverse_resolver_result* result_out)
406  
{
448  
{
407  
    auto& op = reverse_op_;
449  
    auto& op = reverse_op_;
408  
    op.reset();
450  
    op.reset();
409  
    op.h          = h;
451  
    op.h          = h;
410  
    op.ex         = ex;
452  
    op.ex         = ex;
411  
    op.impl       = this;
453  
    op.impl       = this;
412  
    op.ec_out     = ec;
454  
    op.ec_out     = ec;
413  
    op.result_out = result_out;
455  
    op.result_out = result_out;
414  
    op.ep         = ep;
456  
    op.ep         = ep;
415  
    op.flags      = flags;
457  
    op.flags      = flags;
416  
    op.start(token);
458  
    op.start(token);
417  

459  

418  
    // Keep io_context alive while resolution is pending
460  
    // Keep io_context alive while resolution is pending
419  
    op.ex.on_work_started();
461  
    op.ex.on_work_started();
420  

462  

421 -
    // Prevent impl destruction while work is in flight
463 +
    // Track thread for safe shutdown
422 -
    reverse_pool_op_.resolver_ = this;
464 +
    svc_.thread_started();
423 -
    reverse_pool_op_.ref_      = this->shared_from_this();
465 +

424 -
    reverse_pool_op_.func_ = &posix_resolver::do_reverse_resolve_work;
466 +
    try
425 -
    if (!svc_.pool().post(&reverse_pool_op_))
 
426  
    {
467  
    {
427 -
        // Pool shut down — complete with cancellation
468 +
        // Prevent impl destruction while worker thread is running
428 -
        reverse_pool_op_.ref_.reset();
469 +
        auto self = this->shared_from_this();
429 -
        op.cancelled.store(true, std::memory_order_release);
470 +
        std::thread worker([this, self = std::move(self)]() {
 
471 +
            // Build sockaddr from endpoint
 
472 +
            sockaddr_storage ss{};
 
473 +
            socklen_t ss_len;
 
474 +

 
475 +
            if (reverse_op_.ep.is_v4())
 
476 +
            {
 
477 +
                auto sa = to_sockaddr_in(reverse_op_.ep);
 
478 +
                std::memcpy(&ss, &sa, sizeof(sa));
 
479 +
                ss_len = sizeof(sockaddr_in);
 
480 +
            }
 
481 +
            else
 
482 +
            {
 
483 +
                auto sa = to_sockaddr_in6(reverse_op_.ep);
 
484 +
                std::memcpy(&ss, &sa, sizeof(sa));
 
485 +
                ss_len = sizeof(sockaddr_in6);
 
486 +
            }
 
487 +

 
488 +
            char host[NI_MAXHOST];
 
489 +
            char service[NI_MAXSERV];
 
490 +

 
491 +
            int result = ::getnameinfo(
 
492 +
                reinterpret_cast<sockaddr*>(&ss), ss_len, host, sizeof(host),
 
493 +
                service, sizeof(service),
 
494 +
                posix_resolver_detail::flags_to_ni_flags(reverse_op_.flags));
 
495 +

 
496 +
            if (!reverse_op_.cancelled.load(std::memory_order_acquire))
 
497 +
            {
 
498 +
                if (result == 0)
 
499 +
                {
 
500 +
                    reverse_op_.stored_host    = host;
 
501 +
                    reverse_op_.stored_service = service;
 
502 +
                    reverse_op_.gai_error      = 0;
 
503 +
                }
 
504 +
                else
 
505 +
                {
 
506 +
                    reverse_op_.gai_error = result;
 
507 +
                }
 
508 +
            }
 
509 +

 
510 +
            // Always post so the scheduler can properly drain the op
 
511 +
            // during shutdown via destroy().
 
512 +
            svc_.post(&reverse_op_);
 
513 +

 
514 +
            // Signal thread completion for shutdown synchronization
 
515 +
            svc_.thread_finished();
 
516 +
        });
 
517 +
        worker.detach();
 
518 +
    }
 
519 +
    catch (std::system_error const&)
 
520 +
    {
 
521 +
        // Thread creation failed - no thread was started
 
522 +
        svc_.thread_finished();
 
523 +

 
524 +
        // Set error and post completion to avoid hanging the coroutine
 
525 +
        reverse_op_.gai_error = EAI_MEMORY;
430  
        svc_.post(&reverse_op_);
526  
        svc_.post(&reverse_op_);
431  
    }
527  
    }
432  
    return std::noop_coroutine();
528  
    return std::noop_coroutine();
433  
}
529  
}
434  

530  

435  
inline void
531  
inline void
436  
posix_resolver::cancel() noexcept
532  
posix_resolver::cancel() noexcept
437  
{
533  
{
438  
    op_.request_cancel();
534  
    op_.request_cancel();
439  
    reverse_op_.request_cancel();
535  
    reverse_op_.request_cancel();
440  
}
536  
}
441  

537  

442 -
inline void
538 +
// posix_resolver_service implementation
443 -
posix_resolver::do_resolve_work(pool_work_item* w) noexcept
 
444 -
{
 
445 -
    auto* pw   = static_cast<pool_op*>(w);
 
446 -
    auto* self = pw->resolver_;
 
447 -

 
448 -
    struct addrinfo hints{};
 
449 -
    hints.ai_family   = AF_UNSPEC;
 
450 -
    hints.ai_socktype = SOCK_STREAM;
 
451 -
    hints.ai_flags = posix_resolver_detail::flags_to_hints(self->op_.flags);
 
452 -

 
453 -
    struct addrinfo* ai = nullptr;
 
454 -
    int result          = ::getaddrinfo(
 
455 -
        self->op_.host.empty() ? nullptr : self->op_.host.c_str(),
 
456 -
        self->op_.service.empty() ? nullptr : self->op_.service.c_str(),
 
457 -
        &hints, &ai);
 
458 -

 
459 -
    if (!self->op_.cancelled.load(std::memory_order_acquire))
 
460 -
    {
 
461 -
        if (result == 0 && ai)
 
462 -
        {
 
463 -
            self->op_.stored_results =
 
464 -
                posix_resolver_detail::convert_results(
 
465 -
                    ai, self->op_.host, self->op_.service);
 
466 -
            self->op_.gai_error = 0;
 
467 -
        }
 
468 -
        else
 
469 -
        {
 
470 -
            self->op_.gai_error = result;
 
471 -
        }
 
472 -
    }
 
473 -

 
474 -
    if (ai)
 
475 -
        ::freeaddrinfo(ai);
 
476 -

 
477 -
    // Move ref to stack before post — post may trigger destroy_impl
 
478 -
    // which erases the last shared_ptr, destroying *self (and *pw)
 
479 -
    auto ref = std::move(pw->ref_);
 
480 -
    self->svc_.post(&self->op_);
 
481 -
}
 
482  

539  

483  
inline void
540  
inline void
484 -
posix_resolver::do_reverse_resolve_work(pool_work_item* w) noexcept
541 +
posix_resolver_service::shutdown()
485 -
    auto* pw   = static_cast<pool_op*>(w);
 
486 -
    auto* self = pw->resolver_;
 
487 -

 
488 -
    sockaddr_storage ss{};
 
489 -
    socklen_t ss_len;
 
490 -

 
491 -
    if (self->reverse_op_.ep.is_v4())
 
492  
{
542  
{
493  
    {
543  
    {
494 -
        auto sa = to_sockaddr_in(self->reverse_op_.ep);
544 +
        std::lock_guard<std::mutex> lock(mutex_);
495 -
        std::memcpy(&ss, &sa, sizeof(sa));
 
496 -
        ss_len = sizeof(sockaddr_in);
 
497 -
    }
 
498 -
    else
 
499 -
    {
 
500 -
        auto sa = to_sockaddr_in6(self->reverse_op_.ep);
 
501 -
        std::memcpy(&ss, &sa, sizeof(sa));
 
502 -
        ss_len = sizeof(sockaddr_in6);
 
503 -
    }
 
504 -

 
505 -
    char host[NI_MAXHOST];
 
506 -
    char service[NI_MAXSERV];
 
507  

545  

508 -
    int result = ::getnameinfo(
546 +
        // Signal threads to not access service after getaddrinfo returns
509 -
        reinterpret_cast<sockaddr*>(&ss), ss_len, host, sizeof(host),
547 +
        shutting_down_.store(true, std::memory_order_release);
510 -
        service, sizeof(service),
 
511 -
        posix_resolver_detail::flags_to_ni_flags(self->reverse_op_.flags));
 
512  

548  

513 -
    if (!self->reverse_op_.cancelled.load(std::memory_order_acquire))
549 +
        // Cancel all resolvers (sets cancelled flag checked by threads)
514 -
    {
550 +
        for (auto* impl = resolver_list_.pop_front(); impl != nullptr;
515 -
        if (result == 0)
551 +
             impl       = resolver_list_.pop_front())
516 -
        {
 
517 -
            self->reverse_op_.stored_host    = host;
 
518 -
            self->reverse_op_.stored_service = service;
 
519 -
            self->reverse_op_.gai_error      = 0;
 
520 -
        }
 
521 -
        else
 
522  
        {
552  
        {
523 -
            self->reverse_op_.gai_error = result;
553 +
            impl->cancel();
524 -
    }
 
525  
        }
554  
        }
526  

555  

527 -
    // Move ref to stack before post — post may trigger destroy_impl
556 +
        // Clear the map which releases shared_ptrs
528 -
    // which erases the last shared_ptr, destroying *self (and *pw)
557 +
        resolver_ptrs_.clear();
529 -
    auto ref = std::move(pw->ref_);
558 +
    }
530 -
    self->svc_.post(&self->reverse_op_);
 
531 -
}
 
532 -

 
533 -
// posix_resolver_service implementation
 
534 -

 
535 -
inline void
 
536 -
posix_resolver_service::shutdown()
 
537 -
{
 
538 -
    std::lock_guard<std::mutex> lock(mutex_);
 
539  

559  

540 -
    // Cancel all resolvers (sets cancelled flag checked by pool threads)
560 +
    // Wait for all worker threads to finish before service is destroyed
541 -
    for (auto* impl = resolver_list_.pop_front(); impl != nullptr;
 
542 -
         impl       = resolver_list_.pop_front())
 
543  
    {
561  
    {
544 -
        impl->cancel();
562 +
        std::unique_lock<std::mutex> lock(mutex_);
 
563 +
        cv_.wait(lock, [this] { return active_threads_ == 0; });
545 -

 
546 -
    // Clear the map which releases shared_ptrs.
 
547 -
    // The thread pool service shuts down separately via
 
548 -
    // execution_context service ordering.
 
549 -
    resolver_ptrs_.clear();
 
550  
    }
564  
    }
551  
}
565  
}
552  

566  

553  
inline io_object::implementation*
567  
inline io_object::implementation*
554  
posix_resolver_service::construct()
568  
posix_resolver_service::construct()
555  
{
569  
{
556  
    auto ptr   = std::make_shared<posix_resolver>(*this);
570  
    auto ptr   = std::make_shared<posix_resolver>(*this);
557  
    auto* impl = ptr.get();
571  
    auto* impl = ptr.get();
558  

572  

559  
    {
573  
    {
560  
        std::lock_guard<std::mutex> lock(mutex_);
574  
        std::lock_guard<std::mutex> lock(mutex_);
561  
        resolver_list_.push_back(impl);
575  
        resolver_list_.push_back(impl);
562  
        resolver_ptrs_[impl] = std::move(ptr);
576  
        resolver_ptrs_[impl] = std::move(ptr);
563  
    }
577  
    }
564  

578  

565  
    return impl;
579  
    return impl;
566  
}
580  
}
567  

581  

568  
inline void
582  
inline void
569  
posix_resolver_service::destroy_impl(posix_resolver& impl)
583  
posix_resolver_service::destroy_impl(posix_resolver& impl)
570  
{
584  
{
571  
    std::lock_guard<std::mutex> lock(mutex_);
585  
    std::lock_guard<std::mutex> lock(mutex_);
572  
    resolver_list_.remove(&impl);
586  
    resolver_list_.remove(&impl);
573  
    resolver_ptrs_.erase(&impl);
587  
    resolver_ptrs_.erase(&impl);
574  
}
588  
}
575  

589  

576  
inline void
590  
inline void
577  
posix_resolver_service::post(scheduler_op* op)
591  
posix_resolver_service::post(scheduler_op* op)
578  
{
592  
{
579  
    sched_->post(op);
593  
    sched_->post(op);
580  
}
594  
}
581  

595  

582  
inline void
596  
inline void
583  
posix_resolver_service::work_started() noexcept
597  
posix_resolver_service::work_started() noexcept
584  
{
598  
{
585  
    sched_->work_started();
599  
    sched_->work_started();
586  
}
600  
}
587  

601  

588  
inline void
602  
inline void
589  
posix_resolver_service::work_finished() noexcept
603  
posix_resolver_service::work_finished() noexcept
590  
{
604  
{
591  
    sched_->work_finished();
605  
    sched_->work_finished();
 
606 +
}
 
607 +

 
608 +
inline void
 
609 +
posix_resolver_service::thread_started() noexcept
 
610 +
{
 
611 +
    std::lock_guard<std::mutex> lock(mutex_);
 
612 +
    ++active_threads_;
 
613 +
}
 
614 +

 
615 +
inline void
 
616 +
posix_resolver_service::thread_finished() noexcept
 
617 +
{
 
618 +
    std::lock_guard<std::mutex> lock(mutex_);
 
619 +
    --active_threads_;
 
620 +
    cv_.notify_one();
 
621 +
}
 
622 +

 
623 +
inline bool
 
624 +
posix_resolver_service::is_shutting_down() const noexcept
 
625 +
{
 
626 +
    return shutting_down_.load(std::memory_order_acquire);
592  
}
627  
}
593  

628  

594  
// Free function to get/create the resolver service
629  
// Free function to get/create the resolver service
595  

630  

596  
inline posix_resolver_service&
631  
inline posix_resolver_service&
597  
get_resolver_service(capy::execution_context& ctx, scheduler& sched)
632  
get_resolver_service(capy::execution_context& ctx, scheduler& sched)
598  
{
633  
{
599  
    return ctx.make_service<posix_resolver_service>(sched);
634  
    return ctx.make_service<posix_resolver_service>(sched);
600  
}
635  
}
601  

636  

602  
} // namespace boost::corosio::detail
637  
} // namespace boost::corosio::detail
603  

638  

604  
#endif // BOOST_COROSIO_POSIX
639  
#endif // BOOST_COROSIO_POSIX
605  

640  

606  
#endif // BOOST_COROSIO_NATIVE_DETAIL_POSIX_POSIX_RESOLVER_SERVICE_HPP
641  
#endif // BOOST_COROSIO_NATIVE_DETAIL_POSIX_POSIX_RESOLVER_SERVICE_HPP