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/native/detail/posix/posix_resolver.hpp>
17  
#include <boost/corosio/native/detail/posix/posix_resolver.hpp>
 
18 +
#include <boost/corosio/detail/thread_pool.hpp>
 
19 +

 
20 +
#include <unordered_map>
18  

21  

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

23  

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

25  

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

35  

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

41  

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

43  

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

46  

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

48  

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

55  

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

58  

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

62  

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

65  

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

74  

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

76  

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

79  

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

86  

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

90  

90  
// posix_resolver_detail helpers
91  
// posix_resolver_detail helpers
91  

92  

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

97  

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

110  

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

113  

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

118  

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

127  

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

130  

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

137  

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

153  

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

156  

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

168  

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

174  

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

179  

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

185  

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

191  

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

197  

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

203  

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

209  

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

213  

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

220  

220  
// posix_resolver
221  
// posix_resolver
221  

222  

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

227  

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

229  

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

243  

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

248  

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

250  

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

260  

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

263  

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

267  

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

273  

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

279  

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

285  

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

289  

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

291  

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

305  

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

310  

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

312  

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

322  

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

328  

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

332  

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

338  

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

344  

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

350  

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

354  

354  
// posix_resolver implementation
355  
// posix_resolver implementation
355  

356  

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

379  

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

382  

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

385 +
    resolve_pool_op_.ref_      = this->shared_from_this();
385 -
    try
386 +
    resolve_pool_op_.func_     = &posix_resolver::do_resolve_work;
386 -
    {
387 +
    if (!svc_.pool().post(&resolve_pool_op_))
387 -
        // Prevent impl destruction while worker thread is running
 
388 -
        auto self = this->shared_from_this();
 
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  
    {
388  
    {
429 -
        // Thread creation failed - no thread was started
389 +
        // Pool shut down — complete with cancellation
430 -
        svc_.thread_finished();
390 +
        resolve_pool_op_.ref_.reset();
431 -

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

396  

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

417  

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

420  

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

423 +
    reverse_pool_op_.ref_      = this->shared_from_this();
466 -
    try
424 +
    reverse_pool_op_.func_ = &posix_resolver::do_reverse_resolve_work;
467 -
    {
425 +
    if (!svc_.pool().post(&reverse_pool_op_))
468 -
        // Prevent impl destruction while worker thread is running
 
469 -
        auto self = this->shared_from_this();
 
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  
    {
426  
    {
521 -
        // Thread creation failed - no thread was started
427 +
        // Pool shut down — complete with cancellation
522 -
        svc_.thread_finished();
428 +
        reverse_pool_op_.ref_.reset();
523 -

429 +
        op.cancelled.store(true, std::memory_order_release);
524 -
        // Set error and post completion to avoid hanging the coroutine
 
525 -
        reverse_op_.gai_error = EAI_MEMORY;
 
526  
        svc_.post(&reverse_op_);
430  
        svc_.post(&reverse_op_);
527  
    }
431  
    }
528  
    return std::noop_coroutine();
432  
    return std::noop_coroutine();
529  
}
433  
}
530  

434  

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

441  

538 -
// posix_resolver_service implementation
442 +
inline void
 
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 +
}
539  

482  

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

 
489 +
    sockaddr_storage ss{};
 
490 +
    socklen_t ss_len;
 
491 +

 
492 +
    if (self->reverse_op_.ep.is_v4())
543  
    {
493  
    {
544 -
        std::lock_guard<std::mutex> lock(mutex_);
494 +
        auto sa = to_sockaddr_in(self->reverse_op_.ep);
 
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 +
    }
545  

504  

546 -
        // Signal threads to not access service after getaddrinfo returns
505 +
    char host[NI_MAXHOST];
547 -
        shutting_down_.store(true, std::memory_order_release);
506 +
    char service[NI_MAXSERV];
548  

507  

549 -
        // Cancel all resolvers (sets cancelled flag checked by threads)
508 +
    int result = ::getnameinfo(
550 -
        for (auto* impl = resolver_list_.pop_front(); impl != nullptr;
509 +
        reinterpret_cast<sockaddr*>(&ss), ss_len, host, sizeof(host),
551 -
             impl       = resolver_list_.pop_front())
510 +
        service, sizeof(service),
 
511 +
        posix_resolver_detail::flags_to_ni_flags(self->reverse_op_.flags));
 
512 +

 
513 +
    if (!self->reverse_op_.cancelled.load(std::memory_order_acquire))
 
514 +
    {
 
515 +
        if (result == 0)
552  
        {
516  
        {
553 -
            impl->cancel();
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 +
        {
 
523 +
            self->reverse_op_.gai_error = result;
554 -

 
555 -
        // Clear the map which releases shared_ptrs
 
556 -
        resolver_ptrs_.clear();
 
557  
        }
524  
        }
558  
    }
525  
    }
559  

526  

560 -
    // Wait for all worker threads to finish before service is destroyed
527 +
    // Move ref to stack before post — post may trigger destroy_impl
 
528 +
    // which erases the last shared_ptr, destroying *self (and *pw)
 
529 +
    auto ref = std::move(pw->ref_);
 
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 +

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

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

552  

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

558  

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

564  

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

567  

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

575  

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

581  

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

587  

602  
inline void
588  
inline void
603  
posix_resolver_service::work_finished() noexcept
589  
posix_resolver_service::work_finished() noexcept
604  
{
590  
{
605 -
}
 
606 -

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

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

 
622 -
inline bool
 
623 -
posix_resolver_service::is_shutting_down() const noexcept
 
624 -
{
 
625 -
    return shutting_down_.load(std::memory_order_acquire);
 
626  
    sched_->work_finished();
591  
    sched_->work_finished();
627  
}
592  
}
628  

593  

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

595  

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

601  

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

603  

639  
#endif // BOOST_COROSIO_POSIX
604  
#endif // BOOST_COROSIO_POSIX
640  

605  

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