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

80.9% Lines (246/304) 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()
328 29 {
329 29 data_ = this;
330 29 }
331
332 void reset() noexcept;
333 void operator()() override;
334 void destroy() override;
335 void request_cancel() noexcept;
336 void start(std::stop_token token);
337 };
338
339 //--------------------------------------------------------------------------
340 // reverse_resolve_op - operation state for reverse DNS resolution
341 //--------------------------------------------------------------------------
342
343 struct reverse_resolve_op : scheduler_op
344 {
345 struct canceller
346 {
347 reverse_resolve_op* op;
348 void operator()() const noexcept { op->request_cancel(); }
349 };
350
351 // Coroutine state
352 capy::coro h;
353 capy::executor_ref ex;
354 posix_resolver_impl* impl = nullptr;
355
356 // Output parameters
357 std::error_code* ec_out = nullptr;
358 reverse_resolver_result* result_out = nullptr;
359
360 // Input parameters
361 endpoint ep;
362 reverse_flags flags = reverse_flags::none;
363
364 // Result storage (populated by worker thread)
365 std::string stored_host;
366 std::string stored_service;
367 int gai_error = 0;
368
369 // Thread coordination
370 std::atomic<bool> cancelled{false};
371 std::optional<std::stop_callback<canceller>> stop_cb;
372
373 29 reverse_resolve_op()
374 29 {
375 29 data_ = this;
376 29 }
377
378 void reset() noexcept;
379 void operator()() override;
380 void destroy() override;
381 void request_cancel() noexcept;
382 void start(std::stop_token token);
383 };
384
385 29 explicit posix_resolver_impl(posix_resolver_service_impl& svc) noexcept
386 29 : svc_(svc)
387 {
388 29 }
389
390 void release() override;
391
392 void resolve(
393 std::coroutine_handle<>,
394 capy::executor_ref,
395 std::string_view host,
396 std::string_view service,
397 resolve_flags flags,
398 std::stop_token,
399 std::error_code*,
400 resolver_results*) override;
401
402 void reverse_resolve(
403 std::coroutine_handle<>,
404 capy::executor_ref,
405 endpoint const& ep,
406 reverse_flags flags,
407 std::stop_token,
408 std::error_code*,
409 reverse_resolver_result*) override;
410
411 void cancel() noexcept override;
412
413 resolve_op op_;
414 reverse_resolve_op reverse_op_;
415
416 private:
417 posix_resolver_service_impl& svc_;
418 };
419
420 //------------------------------------------------------------------------------
421 // posix_resolver_service_impl - concrete service implementation
422 //------------------------------------------------------------------------------
423
424 class posix_resolver_service_impl : public posix_resolver_service
425 {
426 public:
427 using key_type = posix_resolver_service;
428
429 304 posix_resolver_service_impl(
430 capy::execution_context&,
431 scheduler& sched)
432 304 : sched_(&sched)
433 {
434 304 }
435
436 608 ~posix_resolver_service_impl()
437 304 {
438 608 }
439
440 posix_resolver_service_impl(posix_resolver_service_impl const&) = delete;
441 posix_resolver_service_impl& operator=(posix_resolver_service_impl const&) = delete;
442
443 void shutdown() override;
444 resolver::resolver_impl& create_impl() override;
445 void destroy_impl(posix_resolver_impl& impl);
446
447 void post(scheduler_op* op);
448 void work_started() noexcept;
449 void work_finished() noexcept;
450
451 // Thread tracking for safe shutdown
452 void thread_started() noexcept;
453 void thread_finished() noexcept;
454 bool is_shutting_down() const noexcept;
455
456 private:
457 scheduler* sched_;
458 std::mutex mutex_;
459 std::condition_variable cv_;
460 std::atomic<bool> shutting_down_{false};
461 std::size_t active_threads_ = 0;
462 intrusive_list<posix_resolver_impl> resolver_list_;
463 std::unordered_map<posix_resolver_impl*,
464 std::shared_ptr<posix_resolver_impl>> resolver_ptrs_;
465 };
466
467 //------------------------------------------------------------------------------
468 // posix_resolver_impl::resolve_op implementation
469 //------------------------------------------------------------------------------
470
471 void
472 16 posix_resolver_impl::resolve_op::
473 reset() noexcept
474 {
475 16 host.clear();
476 16 service.clear();
477 16 flags = resolve_flags::none;
478 16 stored_results = resolver_results{};
479 16 gai_error = 0;
480 16 cancelled.store(false, std::memory_order_relaxed);
481 16 stop_cb.reset();
482 16 ec_out = nullptr;
483 16 out = nullptr;
484 16 }
485
486 void
487 16 posix_resolver_impl::resolve_op::
488 operator()()
489 {
490 16 stop_cb.reset(); // Disconnect stop callback
491
492 16 bool const was_cancelled = cancelled.load(std::memory_order_acquire);
493
494
1/2
✓ Branch 0 taken 16 times.
✗ Branch 1 not taken.
16 if (ec_out)
495 {
496
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 16 times.
16 if (was_cancelled)
497 *ec_out = capy::error::canceled;
498
2/2
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 13 times.
16 else if (gai_error != 0)
499 3 *ec_out = make_gai_error(gai_error);
500 else
501 13 *ec_out = {}; // Clear on success
502 }
503
504
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)
505 13 *out = std::move(stored_results);
506
507 16 impl->svc_.work_finished();
508 16 resume_coro(ex, h);
509 16 }
510
511 void
512 posix_resolver_impl::resolve_op::
513 destroy()
514 {
515 stop_cb.reset();
516 }
517
518 void
519 34 posix_resolver_impl::resolve_op::
520 request_cancel() noexcept
521 {
522 34 cancelled.store(true, std::memory_order_release);
523 34 }
524
525 void
526 16 posix_resolver_impl::resolve_op::
527 start(std::stop_token token)
528 {
529 16 cancelled.store(false, std::memory_order_release);
530 16 stop_cb.reset();
531
532
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 16 times.
16 if (token.stop_possible())
533 stop_cb.emplace(token, canceller{this});
534 16 }
535
536 //------------------------------------------------------------------------------
537 // posix_resolver_impl::reverse_resolve_op implementation
538 //------------------------------------------------------------------------------
539
540 void
541 10 posix_resolver_impl::reverse_resolve_op::
542 reset() noexcept
543 {
544 10 ep = endpoint{};
545 10 flags = reverse_flags::none;
546 10 stored_host.clear();
547 10 stored_service.clear();
548 10 gai_error = 0;
549 10 cancelled.store(false, std::memory_order_relaxed);
550 10 stop_cb.reset();
551 10 ec_out = nullptr;
552 10 result_out = nullptr;
553 10 }
554
555 void
556 10 posix_resolver_impl::reverse_resolve_op::
557 operator()()
558 {
559 10 stop_cb.reset(); // Disconnect stop callback
560
561 10 bool const was_cancelled = cancelled.load(std::memory_order_acquire);
562
563
1/2
✓ Branch 0 taken 10 times.
✗ Branch 1 not taken.
10 if (ec_out)
564 {
565
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 10 times.
10 if (was_cancelled)
566 *ec_out = capy::error::canceled;
567
2/2
✓ Branch 0 taken 1 time.
✓ Branch 1 taken 9 times.
10 else if (gai_error != 0)
568 1 *ec_out = make_gai_error(gai_error);
569 else
570 9 *ec_out = {}; // Clear on success
571 }
572
573
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)
574 {
575 27 *result_out = reverse_resolver_result(
576 27 ep, std::move(stored_host), std::move(stored_service));
577 }
578
579 10 impl->svc_.work_finished();
580 10 resume_coro(ex, h);
581 10 }
582
583 void
584 posix_resolver_impl::reverse_resolve_op::
585 destroy()
586 {
587 stop_cb.reset();
588 }
589
590 void
591 34 posix_resolver_impl::reverse_resolve_op::
592 request_cancel() noexcept
593 {
594 34 cancelled.store(true, std::memory_order_release);
595 34 }
596
597 void
598 10 posix_resolver_impl::reverse_resolve_op::
599 start(std::stop_token token)
600 {
601 10 cancelled.store(false, std::memory_order_release);
602 10 stop_cb.reset();
603
604
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 10 times.
10 if (token.stop_possible())
605 stop_cb.emplace(token, canceller{this});
606 10 }
607
608 //------------------------------------------------------------------------------
609 // posix_resolver_impl implementation
610 //------------------------------------------------------------------------------
611
612 void
613 28 posix_resolver_impl::
614 release()
615 {
616 28 cancel();
617 28 svc_.destroy_impl(*this);
618 28 }
619
620 void
621 16 posix_resolver_impl::
622 resolve(
623 std::coroutine_handle<> h,
624 capy::executor_ref ex,
625 std::string_view host,
626 std::string_view service,
627 resolve_flags flags,
628 std::stop_token token,
629 std::error_code* ec,
630 resolver_results* out)
631 {
632 16 auto& op = op_;
633 16 op.reset();
634 16 op.h = h;
635 16 op.ex = ex;
636 16 op.impl = this;
637 16 op.ec_out = ec;
638 16 op.out = out;
639 16 op.host = host;
640 16 op.service = service;
641 16 op.flags = flags;
642 16 op.start(token);
643
644 // Keep io_context alive while resolution is pending
645 16 op.ex.on_work_started();
646
647 // Track thread for safe shutdown
648 16 svc_.thread_started();
649
650 try
651 {
652 // Prevent impl destruction while worker thread is running
653
1/1
✓ Branch 1 taken 16 times.
16 auto self = this->shared_from_this();
654 32 std::thread worker([this, self = std::move(self)]() {
655 16 struct addrinfo hints{};
656 16 hints.ai_family = AF_UNSPEC;
657 16 hints.ai_socktype = SOCK_STREAM;
658 16 hints.ai_flags = flags_to_hints(op_.flags);
659
660 16 struct addrinfo* ai = nullptr;
661
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(
662 32 op_.host.empty() ? nullptr : op_.host.c_str(),
663 32 op_.service.empty() ? nullptr : op_.service.c_str(),
664 &hints, &ai);
665
666
1/2
✓ Branch 1 taken 16 times.
✗ Branch 2 not taken.
16 if (!op_.cancelled.load(std::memory_order_acquire))
667 {
668
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)
669 {
670
1/1
✓ Branch 3 taken 13 times.
13 op_.stored_results = convert_results(ai, op_.host, op_.service);
671 13 op_.gai_error = 0;
672 }
673 else
674 {
675 3 op_.gai_error = result;
676 }
677 }
678
679
2/2
✓ Branch 0 taken 13 times.
✓ Branch 1 taken 3 times.
16 if (ai)
680 13 ::freeaddrinfo(ai);
681
682 // Always post so the scheduler can properly drain the op
683 // during shutdown via destroy().
684
1/1
✓ Branch 1 taken 16 times.
16 svc_.post(&op_);
685
686 // Signal thread completion for shutdown synchronization
687 16 svc_.thread_finished();
688
1/1
✓ Branch 1 taken 16 times.
32 });
689
1/1
✓ Branch 1 taken 16 times.
16 worker.detach();
690 16 }
691 catch (std::system_error const&)
692 {
693 // Thread creation failed - no thread was started
694 svc_.thread_finished();
695
696 // Set error and post completion to avoid hanging the coroutine
697 op_.gai_error = EAI_MEMORY; // Map to "not enough memory"
698 svc_.post(&op_);
699 }
700 16 }
701
702 void
703 10 posix_resolver_impl::
704 reverse_resolve(
705 std::coroutine_handle<> h,
706 capy::executor_ref ex,
707 endpoint const& ep,
708 reverse_flags flags,
709 std::stop_token token,
710 std::error_code* ec,
711 reverse_resolver_result* result_out)
712 {
713 10 auto& op = reverse_op_;
714 10 op.reset();
715 10 op.h = h;
716 10 op.ex = ex;
717 10 op.impl = this;
718 10 op.ec_out = ec;
719 10 op.result_out = result_out;
720 10 op.ep = ep;
721 10 op.flags = flags;
722 10 op.start(token);
723
724 // Keep io_context alive while resolution is pending
725 10 op.ex.on_work_started();
726
727 // Track thread for safe shutdown
728 10 svc_.thread_started();
729
730 try
731 {
732 // Prevent impl destruction while worker thread is running
733
1/1
✓ Branch 1 taken 10 times.
10 auto self = this->shared_from_this();
734 20 std::thread worker([this, self = std::move(self)]() {
735 // Build sockaddr from endpoint
736 10 sockaddr_storage ss{};
737 socklen_t ss_len;
738
739
2/2
✓ Branch 1 taken 8 times.
✓ Branch 2 taken 2 times.
10 if (reverse_op_.ep.is_v4())
740 {
741 8 auto sa = to_sockaddr_in(reverse_op_.ep);
742 8 std::memcpy(&ss, &sa, sizeof(sa));
743 8 ss_len = sizeof(sockaddr_in);
744 }
745 else
746 {
747 2 auto sa = to_sockaddr_in6(reverse_op_.ep);
748 2 std::memcpy(&ss, &sa, sizeof(sa));
749 2 ss_len = sizeof(sockaddr_in6);
750 }
751
752 char host[NI_MAXHOST];
753 char service[NI_MAXSERV];
754
755
1/1
✓ Branch 2 taken 10 times.
10 int result = ::getnameinfo(
756 reinterpret_cast<sockaddr*>(&ss), ss_len,
757 host, sizeof(host),
758 service, sizeof(service),
759 flags_to_ni_flags(reverse_op_.flags));
760
761
1/2
✓ Branch 1 taken 10 times.
✗ Branch 2 not taken.
10 if (!reverse_op_.cancelled.load(std::memory_order_acquire))
762 {
763
2/2
✓ Branch 0 taken 9 times.
✓ Branch 1 taken 1 time.
10 if (result == 0)
764 {
765
1/1
✓ Branch 1 taken 9 times.
9 reverse_op_.stored_host = host;
766
1/1
✓ Branch 1 taken 9 times.
9 reverse_op_.stored_service = service;
767 9 reverse_op_.gai_error = 0;
768 }
769 else
770 {
771 1 reverse_op_.gai_error = result;
772 }
773 }
774
775 // Always post so the scheduler can properly drain the op
776 // during shutdown via destroy().
777
1/1
✓ Branch 1 taken 10 times.
10 svc_.post(&reverse_op_);
778
779 // Signal thread completion for shutdown synchronization
780 10 svc_.thread_finished();
781
1/1
✓ Branch 1 taken 10 times.
20 });
782
1/1
✓ Branch 1 taken 10 times.
10 worker.detach();
783 10 }
784 catch (std::system_error const&)
785 {
786 // Thread creation failed - no thread was started
787 svc_.thread_finished();
788
789 // Set error and post completion to avoid hanging the coroutine
790 reverse_op_.gai_error = EAI_MEMORY;
791 svc_.post(&reverse_op_);
792 }
793 10 }
794
795 void
796 34 posix_resolver_impl::
797 cancel() noexcept
798 {
799 34 op_.request_cancel();
800 34 reverse_op_.request_cancel();
801 34 }
802
803 //------------------------------------------------------------------------------
804 // posix_resolver_service_impl implementation
805 //------------------------------------------------------------------------------
806
807 void
808 304 posix_resolver_service_impl::
809 shutdown()
810 {
811 {
812
1/1
✓ Branch 1 taken 304 times.
304 std::lock_guard<std::mutex> lock(mutex_);
813
814 // Signal threads to not access service after getaddrinfo returns
815 304 shutting_down_.store(true, std::memory_order_release);
816
817 // Cancel all resolvers (sets cancelled flag checked by threads)
818
2/2
✓ Branch 1 taken 1 time.
✓ Branch 2 taken 304 times.
305 for (auto* impl = resolver_list_.pop_front(); impl != nullptr;
819 1 impl = resolver_list_.pop_front())
820 {
821 1 impl->cancel();
822 }
823
824 // Clear the map which releases shared_ptrs
825 304 resolver_ptrs_.clear();
826 304 }
827
828 // Wait for all worker threads to finish before service is destroyed
829 {
830
1/1
✓ Branch 1 taken 304 times.
304 std::unique_lock<std::mutex> lock(mutex_);
831
1/1
✓ Branch 1 taken 304 times.
608 cv_.wait(lock, [this] { return active_threads_ == 0; });
832 304 }
833 304 }
834
835 resolver::resolver_impl&
836 29 posix_resolver_service_impl::
837 create_impl()
838 {
839
1/1
✓ Branch 1 taken 29 times.
29 auto ptr = std::make_shared<posix_resolver_impl>(*this);
840 29 auto* impl = ptr.get();
841
842 {
843
1/1
✓ Branch 1 taken 29 times.
29 std::lock_guard<std::mutex> lock(mutex_);
844 29 resolver_list_.push_back(impl);
845
1/1
✓ Branch 2 taken 29 times.
29 resolver_ptrs_[impl] = std::move(ptr);
846 29 }
847
848 29 return *impl;
849 29 }
850
851 void
852 28 posix_resolver_service_impl::
853 destroy_impl(posix_resolver_impl& impl)
854 {
855
1/1
✓ Branch 1 taken 28 times.
28 std::lock_guard<std::mutex> lock(mutex_);
856 28 resolver_list_.remove(&impl);
857
1/1
✓ Branch 1 taken 28 times.
28 resolver_ptrs_.erase(&impl);
858 28 }
859
860 void
861 26 posix_resolver_service_impl::
862 post(scheduler_op* op)
863 {
864 26 sched_->post(op);
865 26 }
866
867 void
868 posix_resolver_service_impl::
869 work_started() noexcept
870 {
871 sched_->work_started();
872 }
873
874 void
875 26 posix_resolver_service_impl::
876 work_finished() noexcept
877 {
878 26 sched_->work_finished();
879 26 }
880
881 void
882 26 posix_resolver_service_impl::
883 thread_started() noexcept
884 {
885 26 std::lock_guard<std::mutex> lock(mutex_);
886 26 ++active_threads_;
887 26 }
888
889 void
890 26 posix_resolver_service_impl::
891 thread_finished() noexcept
892 {
893 26 std::lock_guard<std::mutex> lock(mutex_);
894 26 --active_threads_;
895 26 cv_.notify_one();
896 26 }
897
898 bool
899 posix_resolver_service_impl::
900 is_shutting_down() const noexcept
901 {
902 return shutting_down_.load(std::memory_order_acquire);
903 }
904
905 //------------------------------------------------------------------------------
906 // Free function to get/create the resolver service
907 //------------------------------------------------------------------------------
908
909 posix_resolver_service&
910 304 get_resolver_service(capy::execution_context& ctx, scheduler& sched)
911 {
912 304 return ctx.make_service<posix_resolver_service_impl>(sched);
913 }
914
915 } // namespace boost::corosio::detail
916
917 #endif // BOOST_COROSIO_POSIX
918