src/corosio/src/detail/posix/resolver_service.cpp

78.8% Lines (238/302) 84.2% Functions (32/38)
src/corosio/src/detail/posix/resolver_service.cpp
Line Hits Source Code
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 #include <boost/corosio/detail/platform.hpp>
11
12 #if BOOST_COROSIO_POSIX
13
14 #include "src/detail/posix/resolver_service.hpp"
15 #include "src/detail/endpoint_convert.hpp"
16 #include "src/detail/intrusive.hpp"
17 #include "src/detail/dispatch_coro.hpp"
18 #include "src/detail/scheduler_op.hpp"
19
20 #include <boost/corosio/detail/scheduler.hpp>
21 #include <boost/corosio/resolver_results.hpp>
22 #include <boost/capy/ex/executor_ref.hpp>
23 #include <coroutine>
24 #include <boost/capy/error.hpp>
25
26 #include <netdb.h>
27 #include <netinet/in.h>
28 #include <sys/socket.h>
29
30 #include <atomic>
31 #include <cassert>
32 #include <condition_variable>
33 #include <cstring>
34 #include <memory>
35 #include <mutex>
36 #include <optional>
37 #include <stop_token>
38 #include <string>
39 #include <thread>
40 #include <unordered_map>
41 #include <vector>
42
43 /*
44 POSIX Resolver Implementation
45 =============================
46
47 This file implements async DNS resolution for POSIX backends using a
48 thread-per-resolution approach. See resolver_service.hpp for the design
49 rationale.
50
51 Class Hierarchy
52 ---------------
53 - posix_resolver_service (abstract base in header)
54 - posix_resolver_service_impl (concrete, defined here)
55 - Owns all posix_resolver_impl instances via shared_ptr
56 - Stores scheduler* for posting completions
57 - posix_resolver_impl (one per resolver object)
58 - Contains embedded resolve_op and reverse_resolve_op for reuse
59 - Uses shared_from_this to prevent premature destruction
60 - resolve_op (forward resolution state)
61 - Uses getaddrinfo() to resolve host/service to endpoints
62 - reverse_resolve_op (reverse resolution state)
63 - Uses getnameinfo() to resolve endpoint to host/service
64
65 Worker Thread Lifetime
66 ----------------------
67 Each resolve() spawns a detached thread. The thread captures a shared_ptr
68 to posix_resolver_impl, ensuring the impl (and its embedded op_) stays
69 alive until the thread completes, even if the resolver is destroyed.
70
71 Completion Flow
72 ---------------
73 Forward resolution:
74 1. resolve() sets up op_, spawns worker thread
75 2. Worker runs getaddrinfo() (blocking)
76 3. Worker stores results in op_.stored_results
77 4. Worker calls svc_.post(&op_) to queue completion
78 5. Scheduler invokes op_() which resumes the coroutine
79
80 Reverse resolution follows the same pattern using getnameinfo().
81
82 Single-Inflight Constraint
83 --------------------------
84 Each resolver has ONE embedded op_ for forward and ONE reverse_op_ for
85 reverse resolution. Concurrent operations of the same type on the same
86 resolver would corrupt state. Users must serialize operations per-resolver.
87
88 Shutdown Synchronization
89 ------------------------
90 The service tracks active worker threads via thread_started()/thread_finished().
91 During shutdown(), the service sets shutting_down_ flag and waits for all
92 threads to complete before destroying resources.
93 */
94
95 namespace boost::corosio::detail {
96
97 namespace {
98
99 // Convert resolve_flags to addrinfo ai_flags
100 int
101 16 flags_to_hints(resolve_flags flags)
102 {
103 16 int hints = 0;
104
105 16 if ((flags & resolve_flags::passive) != resolve_flags::none)
106 hints |= AI_PASSIVE;
107 16 if ((flags & resolve_flags::numeric_host) != resolve_flags::none)
108 11 hints |= AI_NUMERICHOST;
109 16 if ((flags & resolve_flags::numeric_service) != resolve_flags::none)
110 8 hints |= AI_NUMERICSERV;
111 16 if ((flags & resolve_flags::address_configured) != resolve_flags::none)
112 hints |= AI_ADDRCONFIG;
113 16 if ((flags & resolve_flags::v4_mapped) != resolve_flags::none)
114 hints |= AI_V4MAPPED;
115 16 if ((flags & resolve_flags::all_matching) != resolve_flags::none)
116 hints |= AI_ALL;
117
118 16 return hints;
119 }
120
121 // Convert reverse_flags to getnameinfo NI_* flags
122 int
123 10 flags_to_ni_flags(reverse_flags flags)
124 {
125 10 int ni_flags = 0;
126
127 10 if ((flags & reverse_flags::numeric_host) != reverse_flags::none)
128 5 ni_flags |= NI_NUMERICHOST;
129 10 if ((flags & reverse_flags::numeric_service) != reverse_flags::none)
130 5 ni_flags |= NI_NUMERICSERV;
131 10 if ((flags & reverse_flags::name_required) != reverse_flags::none)
132 1 ni_flags |= NI_NAMEREQD;
133 10 if ((flags & reverse_flags::datagram_service) != reverse_flags::none)
134 ni_flags |= NI_DGRAM;
135
136 10 return ni_flags;
137 }
138
139 // Convert addrinfo results to resolver_results
140 resolver_results
141 13 convert_results(
142 struct addrinfo* ai, std::string_view host, std::string_view service)
143 {
144 13 std::vector<resolver_entry> entries;
145 13 entries.reserve(4); // Most lookups return 1-4 addresses
146
147 26 for (auto* p = ai; p != nullptr; p = p->ai_next)
148 {
149 13 if (p->ai_family == AF_INET)
150 {
151 11 auto* addr = reinterpret_cast<sockaddr_in*>(p->ai_addr);
152 11 auto ep = from_sockaddr_in(*addr);
153 11 entries.emplace_back(ep, host, service);
154 }
155 2 else if (p->ai_family == AF_INET6)
156 {
157 2 auto* addr = reinterpret_cast<sockaddr_in6*>(p->ai_addr);
158 2 auto ep = from_sockaddr_in6(*addr);
159 2 entries.emplace_back(ep, host, service);
160 }
161 }
162
163 26 return resolver_results(std::move(entries));
164 13 }
165
166 // Convert getaddrinfo error codes to std::error_code
167 std::error_code
168 4 make_gai_error(int gai_err)
169 {
170 // Map GAI errors to appropriate generic error codes
171 4 switch (gai_err)
172 {
173 case EAI_AGAIN:
174 // Temporary failure - try again later
175 return std::error_code(
176 static_cast<int>(std::errc::resource_unavailable_try_again),
177 std::generic_category());
178
179 case EAI_BADFLAGS:
180 // Invalid flags
181 return std::error_code(
182 static_cast<int>(std::errc::invalid_argument),
183 std::generic_category());
184
185 case EAI_FAIL:
186 // Non-recoverable failure
187 return std::error_code(
188 static_cast<int>(std::errc::io_error), std::generic_category());
189
190 case EAI_FAMILY:
191 // Address family not supported
192 return std::error_code(
193 static_cast<int>(std::errc::address_family_not_supported),
194 std::generic_category());
195
196 case EAI_MEMORY:
197 // Memory allocation failure
198 return std::error_code(
199 static_cast<int>(std::errc::not_enough_memory),
200 std::generic_category());
201
202 4 case EAI_NONAME:
203 // Host or service not found
204 4 return std::error_code(
205 static_cast<int>(std::errc::no_such_device_or_address),
206 4 std::generic_category());
207
208 case EAI_SERVICE:
209 // Service not supported for socket type
210 return std::error_code(
211 static_cast<int>(std::errc::invalid_argument),
212 std::generic_category());
213
214 case EAI_SOCKTYPE:
215 // Socket type not supported
216 return std::error_code(
217 static_cast<int>(std::errc::not_supported),
218 std::generic_category());
219
220 case EAI_SYSTEM:
221 // System error - use errno
222 return std::error_code(errno, std::generic_category());
223
224 default:
225 // Unknown error
226 return std::error_code(
227 static_cast<int>(std::errc::io_error), std::generic_category());
228 }
229 }
230
231 } // anonymous namespace
232
233
234 class posix_resolver_impl;
235 class posix_resolver_service_impl;
236
237 // posix_resolver_impl - per-resolver implementation
238
239 /** Resolver implementation for POSIX backends.
240
241 Each resolver instance contains a single embedded operation object (op_)
242 that is reused for each resolve() call. This design avoids per-operation
243 heap allocation but imposes a critical constraint:
244
245 @par Single-Inflight Contract
246
247 Only ONE resolve operation may be in progress at a time per resolver
248 instance. Calling resolve() while a previous resolve() is still pending
249 results in undefined behavior:
250
251 - The new call overwrites op_ fields (host, service, coroutine handle)
252 - The worker thread from the first call reads corrupted state
253 - The wrong coroutine may be resumed, or resumed multiple times
254 - Data races occur on non-atomic op_ members
255
256 @par Safe Usage Patterns
257
258 @code
259 // CORRECT: Sequential resolves
260 auto [ec1, r1] = co_await resolver.resolve("host1", "80");
261 auto [ec2, r2] = co_await resolver.resolve("host2", "80");
262
263 // CORRECT: Parallel resolves with separate resolver instances
264 resolver r1(ctx), r2(ctx);
265 auto [ec1, res1] = co_await r1.resolve("host1", "80"); // in one coroutine
266 auto [ec2, res2] = co_await r2.resolve("host2", "80"); // in another
267
268 // WRONG: Concurrent resolves on same resolver
269 // These may run concurrently if launched in parallel - UNDEFINED BEHAVIOR
270 auto f1 = resolver.resolve("host1", "80");
271 auto f2 = resolver.resolve("host2", "80"); // BAD: overlaps with f1
272 @endcode
273
274 @par Thread Safety
275 Distinct objects: Safe.
276 Shared objects: Unsafe. See single-inflight contract above.
277 */
278 class posix_resolver_impl final
279 : public resolver::implementation
280 , public std::enable_shared_from_this<posix_resolver_impl>
281 , public intrusive_list<posix_resolver_impl>::node
282 {
283 friend class posix_resolver_service_impl;
284
285 public:
286 // resolve_op - operation state for a single DNS resolution
287
288 struct resolve_op : scheduler_op
289 {
290 struct canceller
291 {
292 resolve_op* op;
293 void operator()() const noexcept
294 {
295 op->request_cancel();
296 }
297 };
298
299 // Coroutine state
300 std::coroutine_handle<> h;
301 capy::executor_ref ex;
302 posix_resolver_impl* impl = nullptr;
303
304 // Output parameters
305 std::error_code* ec_out = nullptr;
306 resolver_results* out = nullptr;
307
308 // Input parameters (owned copies for thread safety)
309 std::string host;
310 std::string service;
311 resolve_flags flags = resolve_flags::none;
312
313 // Result storage (populated by worker thread)
314 resolver_results stored_results;
315 int gai_error = 0;
316
317 // Thread coordination
318 std::atomic<bool> cancelled{false};
319 std::optional<std::stop_callback<canceller>> stop_cb;
320
321 29 resolve_op() = default;
322
323 void reset() noexcept;
324 void operator()() override;
325 void destroy() override;
326 void request_cancel() noexcept;
327 void start(std::stop_token token);
328 };
329
330 // reverse_resolve_op - operation state for reverse DNS resolution
331
332 struct reverse_resolve_op : scheduler_op
333 {
334 struct canceller
335 {
336 reverse_resolve_op* op;
337 void operator()() const noexcept
338 {
339 op->request_cancel();
340 }
341 };
342
343 // Coroutine state
344 std::coroutine_handle<> h;
345 capy::executor_ref ex;
346 posix_resolver_impl* impl = nullptr;
347
348 // Output parameters
349 std::error_code* ec_out = nullptr;
350 reverse_resolver_result* result_out = nullptr;
351
352 // Input parameters
353 endpoint ep;
354 reverse_flags flags = reverse_flags::none;
355
356 // Result storage (populated by worker thread)
357 std::string stored_host;
358 std::string stored_service;
359 int gai_error = 0;
360
361 // Thread coordination
362 std::atomic<bool> cancelled{false};
363 std::optional<std::stop_callback<canceller>> stop_cb;
364
365 29 reverse_resolve_op() = default;
366
367 void reset() noexcept;
368 void operator()() override;
369 void destroy() override;
370 void request_cancel() noexcept;
371 void start(std::stop_token token);
372 };
373
374 29 explicit posix_resolver_impl(posix_resolver_service_impl& svc) noexcept
375 29 : svc_(svc)
376 {
377 29 }
378
379 std::coroutine_handle<> resolve(
380 std::coroutine_handle<>,
381 capy::executor_ref,
382 std::string_view host,
383 std::string_view service,
384 resolve_flags flags,
385 std::stop_token,
386 std::error_code*,
387 resolver_results*) override;
388
389 std::coroutine_handle<> reverse_resolve(
390 std::coroutine_handle<>,
391 capy::executor_ref,
392 endpoint const& ep,
393 reverse_flags flags,
394 std::stop_token,
395 std::error_code*,
396 reverse_resolver_result*) override;
397
398 void cancel() noexcept override;
399
400 resolve_op op_;
401 reverse_resolve_op reverse_op_;
402
403 private:
404 posix_resolver_service_impl& svc_;
405 };
406
407 // posix_resolver_service_impl - concrete service implementation
408
409 class posix_resolver_service_impl final : public posix_resolver_service
410 {
411 public:
412 using key_type = posix_resolver_service;
413
414 336 posix_resolver_service_impl(capy::execution_context&, scheduler& sched)
415 336 : sched_(&sched)
416 {
417 336 }
418
419 672 ~posix_resolver_service_impl() override {}
420
421 posix_resolver_service_impl(posix_resolver_service_impl const&) = delete;
422 posix_resolver_service_impl&
423 operator=(posix_resolver_service_impl const&) = delete;
424
425 io_object::implementation* construct() override;
426
427 29 void destroy(io_object::implementation* p) override
428 {
429 29 auto& impl = static_cast<posix_resolver_impl&>(*p);
430 29 impl.cancel();
431 29 destroy_impl(impl);
432 29 }
433
434 void shutdown() override;
435 void destroy_impl(posix_resolver_impl& impl);
436
437 void post(scheduler_op* op);
438 void work_started() noexcept;
439 void work_finished() noexcept;
440
441 // Thread tracking for safe shutdown
442 void thread_started() noexcept;
443 void thread_finished() noexcept;
444 bool is_shutting_down() const noexcept;
445
446 private:
447 scheduler* sched_;
448 std::mutex mutex_;
449 std::condition_variable cv_;
450 std::atomic<bool> shutting_down_{false};
451 std::size_t active_threads_ = 0;
452 intrusive_list<posix_resolver_impl> resolver_list_;
453 std::unordered_map<
454 posix_resolver_impl*,
455 std::shared_ptr<posix_resolver_impl>>
456 resolver_ptrs_;
457 };
458
459 // posix_resolver_impl::resolve_op implementation
460
461 void
462 16 posix_resolver_impl::resolve_op::reset() noexcept
463 {
464 16 host.clear();
465 16 service.clear();
466 16 flags = resolve_flags::none;
467 16 stored_results = resolver_results{};
468 16 gai_error = 0;
469 16 cancelled.store(false, std::memory_order_relaxed);
470 16 stop_cb.reset();
471 16 ec_out = nullptr;
472 16 out = nullptr;
473 16 }
474
475 void
476 16 posix_resolver_impl::resolve_op::operator()()
477 {
478 16 stop_cb.reset(); // Disconnect stop callback
479
480 16 bool const was_cancelled = cancelled.load(std::memory_order_acquire);
481
482 16 if (ec_out)
483 {
484 16 if (was_cancelled)
485 *ec_out = capy::error::canceled;
486 16 else if (gai_error != 0)
487 3 *ec_out = make_gai_error(gai_error);
488 else
489 13 *ec_out = {}; // Clear on success
490 }
491
492 16 if (out && !was_cancelled && gai_error == 0)
493 13 *out = std::move(stored_results);
494
495 16 impl->svc_.work_finished();
496 16 dispatch_coro(ex, h).resume();
497 16 }
498
499 void
500 posix_resolver_impl::resolve_op::destroy()
501 {
502 stop_cb.reset();
503 }
504
505 void
506 33 posix_resolver_impl::resolve_op::request_cancel() noexcept
507 {
508 33 cancelled.store(true, std::memory_order_release);
509 33 }
510
511 void
512 // NOLINTNEXTLINE(performance-unnecessary-value-param)
513 16 posix_resolver_impl::resolve_op::start(std::stop_token token)
514 {
515 16 cancelled.store(false, std::memory_order_release);
516 16 stop_cb.reset();
517
518 16 if (token.stop_possible())
519 stop_cb.emplace(token, canceller{this});
520 16 }
521
522 // posix_resolver_impl::reverse_resolve_op implementation
523
524 void
525 10 posix_resolver_impl::reverse_resolve_op::reset() noexcept
526 {
527 10 ep = endpoint{};
528 10 flags = reverse_flags::none;
529 10 stored_host.clear();
530 10 stored_service.clear();
531 10 gai_error = 0;
532 10 cancelled.store(false, std::memory_order_relaxed);
533 10 stop_cb.reset();
534 10 ec_out = nullptr;
535 10 result_out = nullptr;
536 10 }
537
538 void
539 10 posix_resolver_impl::reverse_resolve_op::operator()()
540 {
541 10 stop_cb.reset(); // Disconnect stop callback
542
543 10 bool const was_cancelled = cancelled.load(std::memory_order_acquire);
544
545 10 if (ec_out)
546 {
547 10 if (was_cancelled)
548 *ec_out = capy::error::canceled;
549 10 else if (gai_error != 0)
550 1 *ec_out = make_gai_error(gai_error);
551 else
552 9 *ec_out = {}; // Clear on success
553 }
554
555 10 if (result_out && !was_cancelled && gai_error == 0)
556 {
557 27 *result_out = reverse_resolver_result(
558 27 ep, std::move(stored_host), std::move(stored_service));
559 }
560
561 10 impl->svc_.work_finished();
562 10 dispatch_coro(ex, h).resume();
563 10 }
564
565 void
566 posix_resolver_impl::reverse_resolve_op::destroy()
567 {
568 stop_cb.reset();
569 }
570
571 void
572 33 posix_resolver_impl::reverse_resolve_op::request_cancel() noexcept
573 {
574 33 cancelled.store(true, std::memory_order_release);
575 33 }
576
577 void
578 // NOLINTNEXTLINE(performance-unnecessary-value-param)
579 10 posix_resolver_impl::reverse_resolve_op::start(std::stop_token token)
580 {
581 10 cancelled.store(false, std::memory_order_release);
582 10 stop_cb.reset();
583
584 10 if (token.stop_possible())
585 stop_cb.emplace(token, canceller{this});
586 10 }
587
588 // posix_resolver_impl implementation
589
590 std::coroutine_handle<>
591 16 posix_resolver_impl::resolve(
592 std::coroutine_handle<> h,
593 capy::executor_ref ex,
594 std::string_view host,
595 std::string_view service,
596 resolve_flags flags,
597 std::stop_token token,
598 std::error_code* ec,
599 resolver_results* out)
600 {
601 16 auto& op = op_;
602 16 op.reset();
603 16 op.h = h;
604 16 op.ex = ex;
605 16 op.impl = this;
606 16 op.ec_out = ec;
607 16 op.out = out;
608 16 op.host = host;
609 16 op.service = service;
610 16 op.flags = flags;
611 16 op.start(token);
612
613 // Keep io_context alive while resolution is pending
614 16 op.ex.on_work_started();
615
616 // Track thread for safe shutdown
617 16 svc_.thread_started();
618
619 try
620 {
621 // Prevent impl destruction while worker thread is running
622 16 auto self = this->shared_from_this();
623 32 std::thread worker([this, self = std::move(self)]() {
624 16 struct addrinfo hints{};
625 16 hints.ai_family = AF_UNSPEC;
626 16 hints.ai_socktype = SOCK_STREAM;
627 16 hints.ai_flags = flags_to_hints(op_.flags);
628
629 16 struct addrinfo* ai = nullptr;
630 48 int result = ::getaddrinfo(
631 32 op_.host.empty() ? nullptr : op_.host.c_str(),
632 32 op_.service.empty() ? nullptr : op_.service.c_str(), &hints,
633 &ai);
634
635 16 if (!op_.cancelled.load(std::memory_order_acquire))
636 {
637 16 if (result == 0 && ai)
638 {
639 13 op_.stored_results =
640 13 convert_results(ai, op_.host, op_.service);
641 13 op_.gai_error = 0;
642 }
643 else
644 {
645 3 op_.gai_error = result;
646 }
647 }
648
649 16 if (ai)
650 13 ::freeaddrinfo(ai);
651
652 // Always post so the scheduler can properly drain the op
653 // during shutdown via destroy().
654 16 svc_.post(&op_);
655
656 // Signal thread completion for shutdown synchronization
657 16 svc_.thread_finished();
658 32 });
659 16 worker.detach();
660 16 }
661 catch (std::system_error const&)
662 {
663 // Thread creation failed - no thread was started
664 svc_.thread_finished();
665
666 // Set error and post completion to avoid hanging the coroutine
667 op_.gai_error = EAI_MEMORY; // Map to "not enough memory"
668 svc_.post(&op_);
669 }
670 16 return std::noop_coroutine();
671 }
672
673 std::coroutine_handle<>
674 10 posix_resolver_impl::reverse_resolve(
675 std::coroutine_handle<> h,
676 capy::executor_ref ex,
677 endpoint const& ep,
678 reverse_flags flags,
679 std::stop_token token,
680 std::error_code* ec,
681 reverse_resolver_result* result_out)
682 {
683 10 auto& op = reverse_op_;
684 10 op.reset();
685 10 op.h = h;
686 10 op.ex = ex;
687 10 op.impl = this;
688 10 op.ec_out = ec;
689 10 op.result_out = result_out;
690 10 op.ep = ep;
691 10 op.flags = flags;
692 10 op.start(token);
693
694 // Keep io_context alive while resolution is pending
695 10 op.ex.on_work_started();
696
697 // Track thread for safe shutdown
698 10 svc_.thread_started();
699
700 try
701 {
702 // Prevent impl destruction while worker thread is running
703 10 auto self = this->shared_from_this();
704 20 std::thread worker([this, self = std::move(self)]() {
705 // Build sockaddr from endpoint
706 10 sockaddr_storage ss{};
707 socklen_t ss_len;
708
709 10 if (reverse_op_.ep.is_v4())
710 {
711 8 auto sa = to_sockaddr_in(reverse_op_.ep);
712 8 std::memcpy(&ss, &sa, sizeof(sa));
713 8 ss_len = sizeof(sockaddr_in);
714 }
715 else
716 {
717 2 auto sa = to_sockaddr_in6(reverse_op_.ep);
718 2 std::memcpy(&ss, &sa, sizeof(sa));
719 2 ss_len = sizeof(sockaddr_in6);
720 }
721
722 char host[NI_MAXHOST];
723 char service[NI_MAXSERV];
724
725 10 int result = ::getnameinfo(
726 reinterpret_cast<sockaddr*>(&ss), ss_len, host, sizeof(host),
727 service, sizeof(service), flags_to_ni_flags(reverse_op_.flags));
728
729 10 if (!reverse_op_.cancelled.load(std::memory_order_acquire))
730 {
731 10 if (result == 0)
732 {
733 9 reverse_op_.stored_host = host;
734 9 reverse_op_.stored_service = service;
735 9 reverse_op_.gai_error = 0;
736 }
737 else
738 {
739 1 reverse_op_.gai_error = result;
740 }
741 }
742
743 // Always post so the scheduler can properly drain the op
744 // during shutdown via destroy().
745 10 svc_.post(&reverse_op_);
746
747 // Signal thread completion for shutdown synchronization
748 10 svc_.thread_finished();
749 20 });
750 10 worker.detach();
751 10 }
752 catch (std::system_error const&)
753 {
754 // Thread creation failed - no thread was started
755 svc_.thread_finished();
756
757 // Set error and post completion to avoid hanging the coroutine
758 reverse_op_.gai_error = EAI_MEMORY;
759 svc_.post(&reverse_op_);
760 }
761 10 return std::noop_coroutine();
762 }
763
764 void
765 33 posix_resolver_impl::cancel() noexcept
766 {
767 33 op_.request_cancel();
768 33 reverse_op_.request_cancel();
769 33 }
770
771 // posix_resolver_service_impl implementation
772
773 void
774 336 posix_resolver_service_impl::shutdown()
775 {
776 {
777 336 std::lock_guard<std::mutex> lock(mutex_);
778
779 // Signal threads to not access service after getaddrinfo returns
780 336 shutting_down_.store(true, std::memory_order_release);
781
782 // Cancel all resolvers (sets cancelled flag checked by threads)
783 336 for (auto* impl = resolver_list_.pop_front(); impl != nullptr;
784 impl = resolver_list_.pop_front())
785 {
786 impl->cancel();
787 }
788
789 // Clear the map which releases shared_ptrs
790 336 resolver_ptrs_.clear();
791 336 }
792
793 // Wait for all worker threads to finish before service is destroyed
794 {
795 336 std::unique_lock<std::mutex> lock(mutex_);
796 672 cv_.wait(lock, [this] { return active_threads_ == 0; });
797 336 }
798 336 }
799
800 io_object::implementation*
801 29 posix_resolver_service_impl::construct()
802 {
803 29 auto ptr = std::make_shared<posix_resolver_impl>(*this);
804 29 auto* impl = ptr.get();
805
806 {
807 29 std::lock_guard<std::mutex> lock(mutex_);
808 29 resolver_list_.push_back(impl);
809 29 resolver_ptrs_[impl] = std::move(ptr);
810 29 }
811
812 29 return impl;
813 29 }
814
815 void
816 29 posix_resolver_service_impl::destroy_impl(posix_resolver_impl& impl)
817 {
818 29 std::lock_guard<std::mutex> lock(mutex_);
819 29 resolver_list_.remove(&impl);
820 29 resolver_ptrs_.erase(&impl);
821 29 }
822
823 void
824 26 posix_resolver_service_impl::post(scheduler_op* op)
825 {
826 26 sched_->post(op);
827 26 }
828
829 void
830 posix_resolver_service_impl::work_started() noexcept
831 {
832 sched_->work_started();
833 }
834
835 void
836 26 posix_resolver_service_impl::work_finished() noexcept
837 {
838 26 sched_->work_finished();
839 26 }
840
841 void
842 26 posix_resolver_service_impl::thread_started() noexcept
843 {
844 26 std::lock_guard<std::mutex> lock(mutex_);
845 26 ++active_threads_;
846 26 }
847
848 void
849 26 posix_resolver_service_impl::thread_finished() noexcept
850 {
851 26 std::lock_guard<std::mutex> lock(mutex_);
852 26 --active_threads_;
853 26 cv_.notify_one();
854 26 }
855
856 bool
857 posix_resolver_service_impl::is_shutting_down() const noexcept
858 {
859 return shutting_down_.load(std::memory_order_acquire);
860 }
861
862 // Free function to get/create the resolver service
863
864 posix_resolver_service&
865 336 get_resolver_service(capy::execution_context& ctx, scheduler& sched)
866 {
867 336 return ctx.make_service<posix_resolver_service_impl>(sched);
868 }
869
870 } // namespace boost::corosio::detail
871
872 #endif // BOOST_COROSIO_POSIX
873