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

79.9% Lines (239/299) 84.2% Functions (32/38) 68.9% Branches (82/119)
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/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
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::implementation
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 std::coroutine_handle<> 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 std::coroutine_handle<> 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 std::coroutine_handle<> resolve(
385 std::coroutine_handle<>,
386 capy::executor_ref,
387 std::string_view host,
388 std::string_view service,
389 resolve_flags flags,
390 std::stop_token,
391 std::error_code*,
392 resolver_results*) override;
393
394 std::coroutine_handle<> reverse_resolve(
395 std::coroutine_handle<>,
396 capy::executor_ref,
397 endpoint const& ep,
398 reverse_flags flags,
399 std::stop_token,
400 std::error_code*,
401 reverse_resolver_result*) override;
402
403 void cancel() noexcept override;
404
405 resolve_op op_;
406 reverse_resolve_op reverse_op_;
407
408 private:
409 posix_resolver_service_impl& svc_;
410 };
411
412 //------------------------------------------------------------------------------
413 // posix_resolver_service_impl - concrete service implementation
414 //------------------------------------------------------------------------------
415
416 class posix_resolver_service_impl : public posix_resolver_service
417 {
418 public:
419 using key_type = posix_resolver_service;
420
421 336 posix_resolver_service_impl(
422 capy::execution_context&,
423 scheduler& sched)
424 336 : sched_(&sched)
425 {
426 336 }
427
428 672 ~posix_resolver_service_impl()
429 336 {
430 672 }
431
432 posix_resolver_service_impl(posix_resolver_service_impl const&) = delete;
433 posix_resolver_service_impl& operator=(posix_resolver_service_impl const&) = delete;
434
435 io_object::implementation* construct() override;
436
437 29 void destroy(io_object::implementation* p) override
438 {
439 29 auto& impl = static_cast<posix_resolver_impl&>(*p);
440 29 impl.cancel();
441 29 destroy_impl(impl);
442 29 }
443
444 void shutdown() 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
2/2
✓ Branch 1 taken 16 times.
✓ Branch 4 taken 16 times.
16 dispatch_coro(ex, h).resume();
509 16 }
510
511 void
512 posix_resolver_impl::resolve_op::
513 destroy()
514 {
515 stop_cb.reset();
516 }
517
518 void
519 33 posix_resolver_impl::resolve_op::
520 request_cancel() noexcept
521 {
522 33 cancelled.store(true, std::memory_order_release);
523 33 }
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
2/2
✓ Branch 1 taken 10 times.
✓ Branch 4 taken 10 times.
10 dispatch_coro(ex, h).resume();
581 10 }
582
583 void
584 posix_resolver_impl::reverse_resolve_op::
585 destroy()
586 {
587 stop_cb.reset();
588 }
589
590 void
591 33 posix_resolver_impl::reverse_resolve_op::
592 request_cancel() noexcept
593 {
594 33 cancelled.store(true, std::memory_order_release);
595 33 }
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 std::coroutine_handle<>
613 16 posix_resolver_impl::
614 resolve(
615 std::coroutine_handle<> h,
616 capy::executor_ref ex,
617 std::string_view host,
618 std::string_view service,
619 resolve_flags flags,
620 std::stop_token token,
621 std::error_code* ec,
622 resolver_results* out)
623 {
624 16 auto& op = op_;
625 16 op.reset();
626 16 op.h = h;
627 16 op.ex = ex;
628 16 op.impl = this;
629 16 op.ec_out = ec;
630 16 op.out = out;
631 16 op.host = host;
632 16 op.service = service;
633 16 op.flags = flags;
634 16 op.start(token);
635
636 // Keep io_context alive while resolution is pending
637 16 op.ex.on_work_started();
638
639 // Track thread for safe shutdown
640 16 svc_.thread_started();
641
642 try
643 {
644 // Prevent impl destruction while worker thread is running
645
1/1
✓ Branch 1 taken 16 times.
16 auto self = this->shared_from_this();
646 32 std::thread worker([this, self = std::move(self)]() {
647 16 struct addrinfo hints{};
648 16 hints.ai_family = AF_UNSPEC;
649 16 hints.ai_socktype = SOCK_STREAM;
650 16 hints.ai_flags = flags_to_hints(op_.flags);
651
652 16 struct addrinfo* ai = nullptr;
653
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(
654 32 op_.host.empty() ? nullptr : op_.host.c_str(),
655 32 op_.service.empty() ? nullptr : op_.service.c_str(),
656 &hints, &ai);
657
658
1/2
✓ Branch 1 taken 16 times.
✗ Branch 2 not taken.
16 if (!op_.cancelled.load(std::memory_order_acquire))
659 {
660
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)
661 {
662
1/1
✓ Branch 3 taken 13 times.
13 op_.stored_results = convert_results(ai, op_.host, op_.service);
663 13 op_.gai_error = 0;
664 }
665 else
666 {
667 3 op_.gai_error = result;
668 }
669 }
670
671
2/2
✓ Branch 0 taken 13 times.
✓ Branch 1 taken 3 times.
16 if (ai)
672 13 ::freeaddrinfo(ai);
673
674 // Always post so the scheduler can properly drain the op
675 // during shutdown via destroy().
676
1/1
✓ Branch 1 taken 16 times.
16 svc_.post(&op_);
677
678 // Signal thread completion for shutdown synchronization
679 16 svc_.thread_finished();
680
1/1
✓ Branch 1 taken 16 times.
32 });
681
1/1
✓ Branch 1 taken 16 times.
16 worker.detach();
682 16 }
683 catch (std::system_error const&)
684 {
685 // Thread creation failed - no thread was started
686 svc_.thread_finished();
687
688 // Set error and post completion to avoid hanging the coroutine
689 op_.gai_error = EAI_MEMORY; // Map to "not enough memory"
690 svc_.post(&op_);
691 }
692 16 return std::noop_coroutine();
693 }
694
695 std::coroutine_handle<>
696 10 posix_resolver_impl::
697 reverse_resolve(
698 std::coroutine_handle<> h,
699 capy::executor_ref ex,
700 endpoint const& ep,
701 reverse_flags flags,
702 std::stop_token token,
703 std::error_code* ec,
704 reverse_resolver_result* result_out)
705 {
706 10 auto& op = reverse_op_;
707 10 op.reset();
708 10 op.h = h;
709 10 op.ex = ex;
710 10 op.impl = this;
711 10 op.ec_out = ec;
712 10 op.result_out = result_out;
713 10 op.ep = ep;
714 10 op.flags = flags;
715 10 op.start(token);
716
717 // Keep io_context alive while resolution is pending
718 10 op.ex.on_work_started();
719
720 // Track thread for safe shutdown
721 10 svc_.thread_started();
722
723 try
724 {
725 // Prevent impl destruction while worker thread is running
726
1/1
✓ Branch 1 taken 10 times.
10 auto self = this->shared_from_this();
727 20 std::thread worker([this, self = std::move(self)]() {
728 // Build sockaddr from endpoint
729 10 sockaddr_storage ss{};
730 socklen_t ss_len;
731
732
2/2
✓ Branch 1 taken 8 times.
✓ Branch 2 taken 2 times.
10 if (reverse_op_.ep.is_v4())
733 {
734 8 auto sa = to_sockaddr_in(reverse_op_.ep);
735 8 std::memcpy(&ss, &sa, sizeof(sa));
736 8 ss_len = sizeof(sockaddr_in);
737 }
738 else
739 {
740 2 auto sa = to_sockaddr_in6(reverse_op_.ep);
741 2 std::memcpy(&ss, &sa, sizeof(sa));
742 2 ss_len = sizeof(sockaddr_in6);
743 }
744
745 char host[NI_MAXHOST];
746 char service[NI_MAXSERV];
747
748
1/1
✓ Branch 2 taken 10 times.
10 int result = ::getnameinfo(
749 reinterpret_cast<sockaddr*>(&ss), ss_len,
750 host, sizeof(host),
751 service, sizeof(service),
752 flags_to_ni_flags(reverse_op_.flags));
753
754
1/2
✓ Branch 1 taken 10 times.
✗ Branch 2 not taken.
10 if (!reverse_op_.cancelled.load(std::memory_order_acquire))
755 {
756
2/2
✓ Branch 0 taken 9 times.
✓ Branch 1 taken 1 time.
10 if (result == 0)
757 {
758
1/1
✓ Branch 1 taken 9 times.
9 reverse_op_.stored_host = host;
759
1/1
✓ Branch 1 taken 9 times.
9 reverse_op_.stored_service = service;
760 9 reverse_op_.gai_error = 0;
761 }
762 else
763 {
764 1 reverse_op_.gai_error = result;
765 }
766 }
767
768 // Always post so the scheduler can properly drain the op
769 // during shutdown via destroy().
770
1/1
✓ Branch 1 taken 10 times.
10 svc_.post(&reverse_op_);
771
772 // Signal thread completion for shutdown synchronization
773 10 svc_.thread_finished();
774
1/1
✓ Branch 1 taken 10 times.
20 });
775
1/1
✓ Branch 1 taken 10 times.
10 worker.detach();
776 10 }
777 catch (std::system_error const&)
778 {
779 // Thread creation failed - no thread was started
780 svc_.thread_finished();
781
782 // Set error and post completion to avoid hanging the coroutine
783 reverse_op_.gai_error = EAI_MEMORY;
784 svc_.post(&reverse_op_);
785 }
786 10 return std::noop_coroutine();
787 }
788
789 void
790 33 posix_resolver_impl::
791 cancel() noexcept
792 {
793 33 op_.request_cancel();
794 33 reverse_op_.request_cancel();
795 33 }
796
797 //------------------------------------------------------------------------------
798 // posix_resolver_service_impl implementation
799 //------------------------------------------------------------------------------
800
801 void
802 336 posix_resolver_service_impl::
803 shutdown()
804 {
805 {
806
1/1
✓ Branch 1 taken 336 times.
336 std::lock_guard<std::mutex> lock(mutex_);
807
808 // Signal threads to not access service after getaddrinfo returns
809 336 shutting_down_.store(true, std::memory_order_release);
810
811 // Cancel all resolvers (sets cancelled flag checked by threads)
812
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 336 times.
336 for (auto* impl = resolver_list_.pop_front(); impl != nullptr;
813 impl = resolver_list_.pop_front())
814 {
815 impl->cancel();
816 }
817
818 // Clear the map which releases shared_ptrs
819 336 resolver_ptrs_.clear();
820 336 }
821
822 // Wait for all worker threads to finish before service is destroyed
823 {
824
1/1
✓ Branch 1 taken 336 times.
336 std::unique_lock<std::mutex> lock(mutex_);
825
1/1
✓ Branch 1 taken 336 times.
672 cv_.wait(lock, [this] { return active_threads_ == 0; });
826 336 }
827 336 }
828
829 io_object::implementation*
830 29 posix_resolver_service_impl::
831 construct()
832 {
833
1/1
✓ Branch 1 taken 29 times.
29 auto ptr = std::make_shared<posix_resolver_impl>(*this);
834 29 auto* impl = ptr.get();
835
836 {
837
1/1
✓ Branch 1 taken 29 times.
29 std::lock_guard<std::mutex> lock(mutex_);
838 29 resolver_list_.push_back(impl);
839
1/1
✓ Branch 2 taken 29 times.
29 resolver_ptrs_[impl] = std::move(ptr);
840 29 }
841
842 29 return impl;
843 29 }
844
845 void
846 29 posix_resolver_service_impl::
847 destroy_impl(posix_resolver_impl& impl)
848 {
849
1/1
✓ Branch 1 taken 29 times.
29 std::lock_guard<std::mutex> lock(mutex_);
850 29 resolver_list_.remove(&impl);
851
1/1
✓ Branch 1 taken 29 times.
29 resolver_ptrs_.erase(&impl);
852 29 }
853
854 void
855 26 posix_resolver_service_impl::
856 post(scheduler_op* op)
857 {
858 26 sched_->post(op);
859 26 }
860
861 void
862 posix_resolver_service_impl::
863 work_started() noexcept
864 {
865 sched_->work_started();
866 }
867
868 void
869 26 posix_resolver_service_impl::
870 work_finished() noexcept
871 {
872 26 sched_->work_finished();
873 26 }
874
875 void
876 26 posix_resolver_service_impl::
877 thread_started() noexcept
878 {
879 26 std::lock_guard<std::mutex> lock(mutex_);
880 26 ++active_threads_;
881 26 }
882
883 void
884 26 posix_resolver_service_impl::
885 thread_finished() noexcept
886 {
887 26 std::lock_guard<std::mutex> lock(mutex_);
888 26 --active_threads_;
889 26 cv_.notify_one();
890 26 }
891
892 bool
893 posix_resolver_service_impl::
894 is_shutting_down() const noexcept
895 {
896 return shutting_down_.load(std::memory_order_acquire);
897 }
898
899 //------------------------------------------------------------------------------
900 // Free function to get/create the resolver service
901 //------------------------------------------------------------------------------
902
903 posix_resolver_service&
904 336 get_resolver_service(capy::execution_context& ctx, scheduler& sched)
905 {
906 336 return ctx.make_service<posix_resolver_service_impl>(sched);
907 }
908
909 } // namespace boost::corosio::detail
910
911 #endif // BOOST_COROSIO_POSIX
912