1 +
//
 
2 +
// Copyright (c) 2026 Steve Gerbino
 
3 +
//
 
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)
 
6 +
//
 
7 +
// Official repository: https://github.com/cppalliance/corosio
 
8 +
//
 
9 +

 
10 +
#ifndef BOOST_COROSIO_NATIVE_DETAIL_POSIX_POSIX_RESOLVER_SERVICE_HPP
 
11 +
#define BOOST_COROSIO_NATIVE_DETAIL_POSIX_POSIX_RESOLVER_SERVICE_HPP
 
12 +

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

 
15 +
#if BOOST_COROSIO_POSIX
 
16 +

 
17 +
#include <boost/corosio/native/detail/posix/posix_resolver.hpp>
 
18 +

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

 
90 +
// posix_resolver_detail helpers
 
91 +

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

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

 
110 +
    return hints;
 
111 +
}
 
112 +

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

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

 
127 +
    return ni_flags;
 
128 +
}
 
129 +

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

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

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

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

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

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

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

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

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

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

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

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

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

 
220 +
// posix_resolver
 
221 +

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

 
227 +
// posix_resolver::resolve_op implementation
 
228 +

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

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

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

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

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

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

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

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

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

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

 
290 +
// posix_resolver::reverse_resolve_op implementation
 
291 +

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

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

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

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

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

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

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

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

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

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

 
356 +
// posix_resolver implementation
 
357 +

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

 
540 +
// posix_resolver_service implementation
 
541 +

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

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

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

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

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

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

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

 
581 +
    return impl;
 
582 +
}
 
583 +

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

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

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

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

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

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

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

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

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

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

 
641 +
#endif // BOOST_COROSIO_POSIX
 
642 +

 
643 +
#endif // BOOST_COROSIO_NATIVE_DETAIL_POSIX_POSIX_RESOLVER_SERVICE_HPP