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  

18  

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

20  

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

22  

23  
    Owns all posix_resolver instances and tracks active worker
23  
    Owns all posix_resolver instances and tracks active worker
24  
    threads for safe shutdown synchronization.
24  
    threads for safe shutdown synchronization.
25  
*/
25  
*/
26  
class BOOST_COROSIO_DECL posix_resolver_service final
26  
class BOOST_COROSIO_DECL posix_resolver_service final
27  
    : public capy::execution_context::service
27  
    : public capy::execution_context::service
28  
    , public io_object::io_service
28  
    , public io_object::io_service
29  
{
29  
{
30  
public:
30  
public:
31  
    using key_type = posix_resolver_service;
31  
    using key_type = posix_resolver_service;
32  

32  

33  
    posix_resolver_service(capy::execution_context&, scheduler& sched)
33  
    posix_resolver_service(capy::execution_context&, scheduler& sched)
34  
        : sched_(&sched)
34  
        : sched_(&sched)
35  
    {
35  
    {
36  
    }
36  
    }
37  

37  

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

39  

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

42  

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

44  

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

51  

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

54  

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

58  

59  
    void thread_started() noexcept;
59  
    void thread_started() noexcept;
60  
    void thread_finished() noexcept;
60  
    void thread_finished() noexcept;
61  
    bool is_shutting_down() const noexcept;
61  
    bool is_shutting_down() const noexcept;
62  

62  

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

73  

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

75  

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

78  

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

85  

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

89  

90  
// posix_resolver_detail helpers
90  
// posix_resolver_detail helpers
91  

91  

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

96  

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

109  

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

112  

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

117  

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

126  

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

129  

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

136  

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

152  

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

155  

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

167  

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

173  

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

178  

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

184  

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

190  

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

196  

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

202  

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

208  

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

212  

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

219  

220  
// posix_resolver
220  
// posix_resolver
221  

221  

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

226  

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

228  

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

242  

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

247  

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

249  

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

259  

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

262  

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

266  

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

272  

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

278  

279  
inline void
279  
inline void
280  
// NOLINTNEXTLINE(performance-unnecessary-value-param)
280  
// NOLINTNEXTLINE(performance-unnecessary-value-param)
281  
posix_resolver::resolve_op::start(std::stop_token token)
281  
posix_resolver::resolve_op::start(std::stop_token token)
282  
{
282  
{
283  
    cancelled.store(false, std::memory_order_release);
283  
    cancelled.store(false, std::memory_order_release);
284  
    stop_cb.reset();
284  
    stop_cb.reset();
285  

285  

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

289  

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

291  

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

305  

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

310  

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

312  

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

322  

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

328  

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

332  

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

338  

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

344  

345  
inline void
345  
inline void
346  
// NOLINTNEXTLINE(performance-unnecessary-value-param)
346  
// NOLINTNEXTLINE(performance-unnecessary-value-param)
347  
posix_resolver::reverse_resolve_op::start(std::stop_token token)
347  
posix_resolver::reverse_resolve_op::start(std::stop_token token)
348  
{
348  
{
349  
    cancelled.store(false, std::memory_order_release);
349  
    cancelled.store(false, std::memory_order_release);
350  
    stop_cb.reset();
350  
    stop_cb.reset();
351  

351  

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

355  

356  
// posix_resolver implementation
356  
// posix_resolver implementation
357  

357  

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

380  

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

383  

384  
    // Track thread for safe shutdown
384  
    // Track thread for safe shutdown
385  
    svc_.thread_started();
385  
    svc_.thread_started();
386  

386  

387  
    try
387  
    try
388  
    {
388  
    {
389  
        // Prevent impl destruction while worker thread is running
389  
        // Prevent impl destruction while worker thread is running
390  
        auto self = this->shared_from_this();
390  
        auto self = this->shared_from_this();
391  
        std::thread worker([this, self = std::move(self)]() {
391  
        std::thread worker([this, self = std::move(self)]() {
392  
            struct addrinfo hints{};
392  
            struct addrinfo hints{};
393  
            hints.ai_family   = AF_UNSPEC;
393  
            hints.ai_family   = AF_UNSPEC;
394  
            hints.ai_socktype = SOCK_STREAM;
394  
            hints.ai_socktype = SOCK_STREAM;
395  
            hints.ai_flags = posix_resolver_detail::flags_to_hints(op_.flags);
395  
            hints.ai_flags = posix_resolver_detail::flags_to_hints(op_.flags);
396  

396  

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

402  

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

416  

417  
            if (ai)
417  
            if (ai)
418  
                ::freeaddrinfo(ai);
418  
                ::freeaddrinfo(ai);
419  

419  

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

423  

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

433  

434  
        // Set error and post completion to avoid hanging the coroutine
434  
        // Set error and post completion to avoid hanging the coroutine
435  
        op_.gai_error = EAI_MEMORY; // Map to "not enough memory"
435  
        op_.gai_error = EAI_MEMORY; // Map to "not enough memory"
436  
        svc_.post(&op_);
436  
        svc_.post(&op_);
437  
    }
437  
    }
438  
    return std::noop_coroutine();
438  
    return std::noop_coroutine();
439  
}
439  
}
440  

440  

441  
inline std::coroutine_handle<>
441  
inline std::coroutine_handle<>
442  
posix_resolver::reverse_resolve(
442  
posix_resolver::reverse_resolve(
443  
    std::coroutine_handle<> h,
443  
    std::coroutine_handle<> h,
444  
    capy::executor_ref ex,
444  
    capy::executor_ref ex,
445  
    endpoint const& ep,
445  
    endpoint const& ep,
446  
    reverse_flags flags,
446  
    reverse_flags flags,
447  
    std::stop_token token,
447  
    std::stop_token token,
448  
    std::error_code* ec,
448  
    std::error_code* ec,
449  
    reverse_resolver_result* result_out)
449  
    reverse_resolver_result* result_out)
450  
{
450  
{
451  
    auto& op = reverse_op_;
451  
    auto& op = reverse_op_;
452  
    op.reset();
452  
    op.reset();
453  
    op.h          = h;
453  
    op.h          = h;
454  
    op.ex         = ex;
454  
    op.ex         = ex;
455  
    op.impl       = this;
455  
    op.impl       = this;
456  
    op.ec_out     = ec;
456  
    op.ec_out     = ec;
457  
    op.result_out = result_out;
457  
    op.result_out = result_out;
458  
    op.ep         = ep;
458  
    op.ep         = ep;
459  
    op.flags      = flags;
459  
    op.flags      = flags;
460  
    op.start(token);
460  
    op.start(token);
461  

461  

462  
    // Keep io_context alive while resolution is pending
462  
    // Keep io_context alive while resolution is pending
463  
    op.ex.on_work_started();
463  
    op.ex.on_work_started();
464  

464  

465  
    // Track thread for safe shutdown
465  
    // Track thread for safe shutdown
466  
    svc_.thread_started();
466  
    svc_.thread_started();
467  

467  

468  
    try
468  
    try
469  
    {
469  
    {
470  
        // Prevent impl destruction while worker thread is running
470  
        // Prevent impl destruction while worker thread is running
471  
        auto self = this->shared_from_this();
471  
        auto self = this->shared_from_this();
472  
        std::thread worker([this, self = std::move(self)]() {
472  
        std::thread worker([this, self = std::move(self)]() {
473  
            // Build sockaddr from endpoint
473  
            // Build sockaddr from endpoint
474  
            sockaddr_storage ss{};
474  
            sockaddr_storage ss{};
475  
            socklen_t ss_len;
475  
            socklen_t ss_len;
476  

476  

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

489  

490  
            char host[NI_MAXHOST];
490  
            char host[NI_MAXHOST];
491  
            char service[NI_MAXSERV];
491  
            char service[NI_MAXSERV];
492  

492  

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

497  

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

511  

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

515  

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

525  

526  
        // Set error and post completion to avoid hanging the coroutine
526  
        // Set error and post completion to avoid hanging the coroutine
527  
        reverse_op_.gai_error = EAI_MEMORY;
527  
        reverse_op_.gai_error = EAI_MEMORY;
528  
        svc_.post(&reverse_op_);
528  
        svc_.post(&reverse_op_);
529  
    }
529  
    }
530  
    return std::noop_coroutine();
530  
    return std::noop_coroutine();
531  
}
531  
}
532  

532  

533  
inline void
533  
inline void
534  
posix_resolver::cancel() noexcept
534  
posix_resolver::cancel() noexcept
535  
{
535  
{
536  
    op_.request_cancel();
536  
    op_.request_cancel();
537  
    reverse_op_.request_cancel();
537  
    reverse_op_.request_cancel();
538  
}
538  
}
539  

539  

540  
// posix_resolver_service implementation
540  
// posix_resolver_service implementation
541  

541  

542  
inline void
542  
inline void
543  
posix_resolver_service::shutdown()
543  
posix_resolver_service::shutdown()
544  
{
544  
{
545  
    {
545  
    {
546  
        std::lock_guard<std::mutex> lock(mutex_);
546  
        std::lock_guard<std::mutex> lock(mutex_);
547  

547  

548  
        // Signal threads to not access service after getaddrinfo returns
548  
        // Signal threads to not access service after getaddrinfo returns
549  
        shutting_down_.store(true, std::memory_order_release);
549  
        shutting_down_.store(true, std::memory_order_release);
550  

550  

551  
        // Cancel all resolvers (sets cancelled flag checked by threads)
551  
        // Cancel all resolvers (sets cancelled flag checked by threads)
552  
        for (auto* impl = resolver_list_.pop_front(); impl != nullptr;
552  
        for (auto* impl = resolver_list_.pop_front(); impl != nullptr;
553  
             impl       = resolver_list_.pop_front())
553  
             impl       = resolver_list_.pop_front())
554  
        {
554  
        {
555  
            impl->cancel();
555  
            impl->cancel();
556  
        }
556  
        }
557  

557  

558  
        // Clear the map which releases shared_ptrs
558  
        // Clear the map which releases shared_ptrs
559  
        resolver_ptrs_.clear();
559  
        resolver_ptrs_.clear();
560  
    }
560  
    }
561  

561  

562  
    // Wait for all worker threads to finish before service is destroyed
562  
    // Wait for all worker threads to finish before service is destroyed
563  
    {
563  
    {
564  
        std::unique_lock<std::mutex> lock(mutex_);
564  
        std::unique_lock<std::mutex> lock(mutex_);
565  
        cv_.wait(lock, [this] { return active_threads_ == 0; });
565  
        cv_.wait(lock, [this] { return active_threads_ == 0; });
566  
    }
566  
    }
567  
}
567  
}
568  

568  

569  
inline io_object::implementation*
569  
inline io_object::implementation*
570  
posix_resolver_service::construct()
570  
posix_resolver_service::construct()
571  
{
571  
{
572  
    auto ptr   = std::make_shared<posix_resolver>(*this);
572  
    auto ptr   = std::make_shared<posix_resolver>(*this);
573  
    auto* impl = ptr.get();
573  
    auto* impl = ptr.get();
574  

574  

575  
    {
575  
    {
576  
        std::lock_guard<std::mutex> lock(mutex_);
576  
        std::lock_guard<std::mutex> lock(mutex_);
577  
        resolver_list_.push_back(impl);
577  
        resolver_list_.push_back(impl);
578  
        resolver_ptrs_[impl] = std::move(ptr);
578  
        resolver_ptrs_[impl] = std::move(ptr);
579  
    }
579  
    }
580  

580  

581  
    return impl;
581  
    return impl;
582  
}
582  
}
583  

583  

584  
inline void
584  
inline void
585  
posix_resolver_service::destroy_impl(posix_resolver& impl)
585  
posix_resolver_service::destroy_impl(posix_resolver& impl)
586  
{
586  
{
587  
    std::lock_guard<std::mutex> lock(mutex_);
587  
    std::lock_guard<std::mutex> lock(mutex_);
588  
    resolver_list_.remove(&impl);
588  
    resolver_list_.remove(&impl);
589  
    resolver_ptrs_.erase(&impl);
589  
    resolver_ptrs_.erase(&impl);
590  
}
590  
}
591  

591  

592  
inline void
592  
inline void
593  
posix_resolver_service::post(scheduler_op* op)
593  
posix_resolver_service::post(scheduler_op* op)
594  
{
594  
{
595  
    sched_->post(op);
595  
    sched_->post(op);
596  
}
596  
}
597  

597  

598  
inline void
598  
inline void
599  
posix_resolver_service::work_started() noexcept
599  
posix_resolver_service::work_started() noexcept
600  
{
600  
{
601  
    sched_->work_started();
601  
    sched_->work_started();
602  
}
602  
}
603  

603  

604  
inline void
604  
inline void
605  
posix_resolver_service::work_finished() noexcept
605  
posix_resolver_service::work_finished() noexcept
606  
{
606  
{
607  
    sched_->work_finished();
607  
    sched_->work_finished();
608  
}
608  
}
609  

609  

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

616  

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

624  

625  
inline bool
625  
inline bool
626  
posix_resolver_service::is_shutting_down() const noexcept
626  
posix_resolver_service::is_shutting_down() const noexcept
627  
{
627  
{
628  
    return shutting_down_.load(std::memory_order_acquire);
628  
    return shutting_down_.load(std::memory_order_acquire);
629  
}
629  
}
630  

630  

631  
// Free function to get/create the resolver service
631  
// Free function to get/create the resolver service
632  

632  

633  
inline posix_resolver_service&
633  
inline posix_resolver_service&
634  
get_resolver_service(capy::execution_context& ctx, scheduler& sched)
634  
get_resolver_service(capy::execution_context& ctx, scheduler& sched)
635  
{
635  
{
636  
    return ctx.make_service<posix_resolver_service>(sched);
636  
    return ctx.make_service<posix_resolver_service>(sched);
637  
}
637  
}
638  

638  

639  
} // namespace boost::corosio::detail
639  
} // namespace boost::corosio::detail
640  

640  

641  
#endif // BOOST_COROSIO_POSIX
641  
#endif // BOOST_COROSIO_POSIX
642  

642  

643  
#endif // BOOST_COROSIO_NATIVE_DETAIL_POSIX_POSIX_RESOLVER_SERVICE_HPP
643  
#endif // BOOST_COROSIO_NATIVE_DETAIL_POSIX_POSIX_RESOLVER_SERVICE_HPP