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