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

80.5% Lines (240/298) 84.2% Functions (32/38) 68.7% Branches (79/115)
libs/corosio/src/corosio/src/detail/posix/resolver_service.cpp
Line Branch 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/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
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 16 times.
16 if ((flags & resolve_flags::passive) != resolve_flags::none)
106 hints |= AI_PASSIVE;
107
2/2
✓ Branch 1 taken 11 times.
✓ Branch 2 taken 5 times.
16 if ((flags & resolve_flags::numeric_host) != resolve_flags::none)
108 11 hints |= AI_NUMERICHOST;
109
2/2
✓ Branch 1 taken 8 times.
✓ Branch 2 taken 8 times.
16 if ((flags & resolve_flags::numeric_service) != resolve_flags::none)
110 8 hints |= AI_NUMERICSERV;
111
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 16 times.
16 if ((flags & resolve_flags::address_configured) != resolve_flags::none)
112 hints |= AI_ADDRCONFIG;
113
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 16 times.
16 if ((flags & resolve_flags::v4_mapped) != resolve_flags::none)
114 hints |= AI_V4MAPPED;
115
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 16 times.
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
2/2
✓ Branch 1 taken 5 times.
✓ Branch 2 taken 5 times.
10 if ((flags & reverse_flags::numeric_host) != reverse_flags::none)
128 5 ni_flags |= NI_NUMERICHOST;
129
2/2
✓ Branch 1 taken 5 times.
✓ Branch 2 taken 5 times.
10 if ((flags & reverse_flags::numeric_service) != reverse_flags::none)
130 5 ni_flags |= NI_NUMERICSERV;
131
2/2
✓ Branch 1 taken 1 time.
✓ Branch 2 taken 9 times.
10 if ((flags & reverse_flags::name_required) != reverse_flags::none)
132 1 ni_flags |= NI_NAMEREQD;
133
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 10 times.
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,
143 std::string_view host,
144 std::string_view service)
145 {
146 13 std::vector<resolver_entry> entries;
147
1/1
✓ Branch 1 taken 13 times.
13 entries.reserve(4); // Most lookups return 1-4 addresses
148
149
2/2
✓ Branch 0 taken 13 times.
✓ Branch 1 taken 13 times.
26 for (auto* p = ai; p != nullptr; p = p->ai_next)
150 {
151
2/2
✓ Branch 0 taken 11 times.
✓ Branch 1 taken 2 times.
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
1/1
✓ Branch 1 taken 11 times.
11 entries.emplace_back(ep, host, service);
156 }
157
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
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
1/1
✓ Branch 1 taken 2 times.
2 entries.emplace_back(ep, host, service);
162 }
163 }
164
165
1/1
✓ Branch 3 taken 13 times.
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
1/10
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 4 times.
✗ Branch 6 not taken.
✗ Branch 7 not taken.
✗ Branch 8 not taken.
✗ Branch 9 not taken.
4 switch (gai_err)
174 {
175 case EAI_AGAIN:
176 // Temporary failure - try again later
177 return std::error_code(
178 static_cast<int>(std::errc::resource_unavailable_try_again),
179 std::generic_category());
180
181 case EAI_BADFLAGS:
182 // Invalid flags
183 return std::error_code(
184 static_cast<int>(std::errc::invalid_argument),
185 std::generic_category());
186
187 case EAI_FAIL:
188 // Non-recoverable failure
189 return std::error_code(
190 static_cast<int>(std::errc::io_error),
191 std::generic_category());
192
193 case EAI_FAMILY:
194 // Address family not supported
195 return std::error_code(
196 static_cast<int>(std::errc::address_family_not_supported),
197 std::generic_category());
198
199 case EAI_MEMORY:
200 // Memory allocation failure
201 return std::error_code(
202 static_cast<int>(std::errc::not_enough_memory),
203 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 case EAI_SERVICE:
212 // Service not supported for socket type
213 return std::error_code(
214 static_cast<int>(std::errc::invalid_argument),
215 std::generic_category());
216
217 case EAI_SOCKTYPE:
218 // Socket type not supported
219 return std::error_code(
220 static_cast<int>(std::errc::not_supported),
221 std::generic_category());
222
223 case EAI_SYSTEM:
224 // System error - use errno
225 return std::error_code(errno, std::generic_category());
226
227 default:
228 // Unknown error
229 return std::error_code(
230 static_cast<int>(std::errc::io_error),
231 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 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 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
1/2
✓ Branch 0 taken 16 times.
✗ Branch 1 not taken.
16 if (ec_out)
489 {
490
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 16 times.
16 if (was_cancelled)
491 *ec_out = capy::error::canceled;
492
2/2
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 13 times.
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
4/6
✓ Branch 0 taken 16 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 16 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 13 times.
✓ Branch 5 taken 3 times.
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 posix_resolver_impl::resolve_op::
507 destroy()
508 {
509 stop_cb.reset();
510 }
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
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 16 times.
16 if (token.stop_possible())
527 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
1/2
✓ Branch 0 taken 10 times.
✗ Branch 1 not taken.
10 if (ec_out)
558 {
559
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 10 times.
10 if (was_cancelled)
560 *ec_out = capy::error::canceled;
561
2/2
✓ Branch 0 taken 1 time.
✓ Branch 1 taken 9 times.
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
4/6
✓ Branch 0 taken 10 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 10 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 9 times.
✓ Branch 5 taken 1 time.
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 posix_resolver_impl::reverse_resolve_op::
579 destroy()
580 {
581 stop_cb.reset();
582 }
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
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 10 times.
10 if (token.stop_possible())
599 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
1/1
✓ Branch 1 taken 16 times.
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
3/5
✗ Branch 0 not taken.
✓ Branch 1 taken 16 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 16 times.
✓ Branch 5 taken 16 times.
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
1/2
✓ Branch 1 taken 16 times.
✗ Branch 2 not taken.
16 if (!op_.cancelled.load(std::memory_order_acquire))
661 {
662
3/4
✓ Branch 0 taken 13 times.
✓ Branch 1 taken 3 times.
✓ Branch 2 taken 13 times.
✗ Branch 3 not taken.
16 if (result == 0 && ai)
663 {
664
1/1
✓ Branch 3 taken 13 times.
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
2/2
✓ Branch 0 taken 13 times.
✓ Branch 1 taken 3 times.
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
1/1
✓ Branch 1 taken 16 times.
16 svc_.post(&op_);
679
680 // Signal thread completion for shutdown synchronization
681 16 svc_.thread_finished();
682
1/1
✓ Branch 1 taken 16 times.
32 });
683
1/1
✓ Branch 1 taken 16 times.
16 worker.detach();
684 16 }
685 catch (std::system_error const&)
686 {
687 // Thread creation failed - no thread was started
688 svc_.thread_finished();
689
690 // Set error and post completion to avoid hanging the coroutine
691 op_.gai_error = EAI_MEMORY; // Map to "not enough memory"
692 svc_.post(&op_);
693 }
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
1/1
✓ Branch 1 taken 10 times.
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
2/2
✓ Branch 1 taken 8 times.
✓ Branch 2 taken 2 times.
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
1/1
✓ Branch 2 taken 10 times.
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
1/2
✓ Branch 1 taken 10 times.
✗ Branch 2 not taken.
10 if (!reverse_op_.cancelled.load(std::memory_order_acquire))
757 {
758
2/2
✓ Branch 0 taken 9 times.
✓ Branch 1 taken 1 time.
10 if (result == 0)
759 {
760
1/1
✓ Branch 1 taken 9 times.
9 reverse_op_.stored_host = host;
761
1/1
✓ Branch 1 taken 9 times.
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
1/1
✓ Branch 1 taken 10 times.
10 svc_.post(&reverse_op_);
773
774 // Signal thread completion for shutdown synchronization
775 10 svc_.thread_finished();
776
1/1
✓ Branch 1 taken 10 times.
20 });
777
1/1
✓ Branch 1 taken 10 times.
10 worker.detach();
778 10 }
779 catch (std::system_error const&)
780 {
781 // Thread creation failed - no thread was started
782 svc_.thread_finished();
783
784 // Set error and post completion to avoid hanging the coroutine
785 reverse_op_.gai_error = EAI_MEMORY;
786 svc_.post(&reverse_op_);
787 }
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
1/1
✓ Branch 1 taken 309 times.
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
2/2
✓ Branch 1 taken 1 time.
✓ Branch 2 taken 309 times.
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
1/1
✓ Branch 1 taken 309 times.
309 std::unique_lock<std::mutex> lock(mutex_);
827
1/1
✓ Branch 1 taken 309 times.
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
1/1
✓ Branch 1 taken 29 times.
29 auto ptr = std::make_shared<posix_resolver_impl>(*this);
836 29 auto* impl = ptr.get();
837
838 {
839
1/1
✓ Branch 1 taken 29 times.
29 std::lock_guard<std::mutex> lock(mutex_);
840 29 resolver_list_.push_back(impl);
841
1/1
✓ Branch 2 taken 29 times.
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
1/1
✓ Branch 1 taken 28 times.
28 std::lock_guard<std::mutex> lock(mutex_);
852 28 resolver_list_.remove(&impl);
853
1/1
✓ Branch 1 taken 28 times.
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 posix_resolver_service_impl::
865 work_started() noexcept
866 {
867 sched_->work_started();
868 }
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 posix_resolver_service_impl::
896 is_shutting_down() const noexcept
897 {
898 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
914