include/boost/corosio/native/detail/posix/posix_resolver_service.hpp

82.3% Lines (228/277) 93.3% Functions (28/30)
Line TLA 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 #ifndef BOOST_COROSIO_NATIVE_DETAIL_POSIX_POSIX_RESOLVER_SERVICE_HPP
11 #define BOOST_COROSIO_NATIVE_DETAIL_POSIX_POSIX_RESOLVER_SERVICE_HPP
12
13 #include <boost/corosio/detail/platform.hpp>
14
15 #if BOOST_COROSIO_POSIX
16
17 #include <boost/corosio/native/detail/posix/posix_resolver.hpp>
18 #include <boost/corosio/detail/thread_pool.hpp>
19
20 #include <unordered_map>
21
22 namespace boost::corosio::detail {
23
24 /** Resolver service for POSIX backends.
25
26 Owns all posix_resolver instances. Thread lifecycle is managed
27 by the thread_pool service.
28 */
29 class BOOST_COROSIO_DECL posix_resolver_service final
30 : public capy::execution_context::service
31 , public io_object::io_service
32 {
33 public:
34 using key_type = posix_resolver_service;
35
36 412x posix_resolver_service(capy::execution_context& ctx, scheduler& sched)
37 824x : sched_(&sched)
38 412x , pool_(ctx.make_service<thread_pool>())
39 {
40 412x }
41
42 824x ~posix_resolver_service() override = default;
43
44 posix_resolver_service(posix_resolver_service const&) = delete;
45 posix_resolver_service& operator=(posix_resolver_service const&) = delete;
46
47 io_object::implementation* construct() override;
48
49 29x void destroy(io_object::implementation* p) override
50 {
51 29x auto& impl = static_cast<posix_resolver&>(*p);
52 29x impl.cancel();
53 29x destroy_impl(impl);
54 29x }
55
56 void shutdown() override;
57 void destroy_impl(posix_resolver& impl);
58
59 void post(scheduler_op* op);
60 void work_started() noexcept;
61 void work_finished() noexcept;
62
63 /** Return the resolver thread pool. */
64 26x thread_pool& pool() noexcept { return pool_; }
65
66 private:
67 scheduler* sched_;
68 thread_pool& pool_;
69 std::mutex mutex_;
70 intrusive_list<posix_resolver> resolver_list_;
71 std::unordered_map<posix_resolver*, std::shared_ptr<posix_resolver>>
72 resolver_ptrs_;
73 };
74
75 /** Get or create the resolver service for the given context.
76
77 This function is called by the concrete scheduler during initialization
78 to create the resolver service with a reference to itself.
79
80 @param ctx Reference to the owning execution_context.
81 @param sched Reference to the scheduler for posting completions.
82 @return Reference to the resolver service.
83 */
84 posix_resolver_service&
85 get_resolver_service(capy::execution_context& ctx, scheduler& sched);
86
87 // ---------------------------------------------------------------------------
88 // Inline implementation
89 // ---------------------------------------------------------------------------
90
91 // posix_resolver_detail helpers
92
93 inline int
94 16x posix_resolver_detail::flags_to_hints(resolve_flags flags)
95 {
96 16x int hints = 0;
97
98 16x if ((flags & resolve_flags::passive) != resolve_flags::none)
99 hints |= AI_PASSIVE;
100 16x if ((flags & resolve_flags::numeric_host) != resolve_flags::none)
101 11x hints |= AI_NUMERICHOST;
102 16x if ((flags & resolve_flags::numeric_service) != resolve_flags::none)
103 8x hints |= AI_NUMERICSERV;
104 16x if ((flags & resolve_flags::address_configured) != resolve_flags::none)
105 hints |= AI_ADDRCONFIG;
106 16x if ((flags & resolve_flags::v4_mapped) != resolve_flags::none)
107 hints |= AI_V4MAPPED;
108 16x if ((flags & resolve_flags::all_matching) != resolve_flags::none)
109 hints |= AI_ALL;
110
111 16x return hints;
112 }
113
114 inline int
115 10x posix_resolver_detail::flags_to_ni_flags(reverse_flags flags)
116 {
117 10x int ni_flags = 0;
118
119 10x if ((flags & reverse_flags::numeric_host) != reverse_flags::none)
120 5x ni_flags |= NI_NUMERICHOST;
121 10x if ((flags & reverse_flags::numeric_service) != reverse_flags::none)
122 5x ni_flags |= NI_NUMERICSERV;
123 10x if ((flags & reverse_flags::name_required) != reverse_flags::none)
124 1x ni_flags |= NI_NAMEREQD;
125 10x if ((flags & reverse_flags::datagram_service) != reverse_flags::none)
126 ni_flags |= NI_DGRAM;
127
128 10x return ni_flags;
129 }
130
131 inline resolver_results
132 13x posix_resolver_detail::convert_results(
133 struct addrinfo* ai, std::string_view host, std::string_view service)
134 {
135 13x std::vector<resolver_entry> entries;
136 13x entries.reserve(4); // Most lookups return 1-4 addresses
137
138 26x for (auto* p = ai; p != nullptr; p = p->ai_next)
139 {
140 13x if (p->ai_family == AF_INET)
141 {
142 11x auto* addr = reinterpret_cast<sockaddr_in*>(p->ai_addr);
143 11x auto ep = from_sockaddr_in(*addr);
144 11x entries.emplace_back(ep, host, service);
145 }
146 2x else if (p->ai_family == AF_INET6)
147 {
148 2x auto* addr = reinterpret_cast<sockaddr_in6*>(p->ai_addr);
149 2x auto ep = from_sockaddr_in6(*addr);
150 2x entries.emplace_back(ep, host, service);
151 }
152 }
153
154 26x return resolver_results(std::move(entries));
155 13x }
156
157 inline std::error_code
158 4x posix_resolver_detail::make_gai_error(int gai_err)
159 {
160 // Map GAI errors to appropriate generic error codes
161 4x switch (gai_err)
162 {
163 case EAI_AGAIN:
164 // Temporary failure - try again later
165 return std::error_code(
166 static_cast<int>(std::errc::resource_unavailable_try_again),
167 std::generic_category());
168
169 case EAI_BADFLAGS:
170 // Invalid flags
171 return std::error_code(
172 static_cast<int>(std::errc::invalid_argument),
173 std::generic_category());
174
175 case EAI_FAIL:
176 // Non-recoverable failure
177 return std::error_code(
178 static_cast<int>(std::errc::io_error), std::generic_category());
179
180 case EAI_FAMILY:
181 // Address family not supported
182 return std::error_code(
183 static_cast<int>(std::errc::address_family_not_supported),
184 std::generic_category());
185
186 case EAI_MEMORY:
187 // Memory allocation failure
188 return std::error_code(
189 static_cast<int>(std::errc::not_enough_memory),
190 std::generic_category());
191
192 4x case EAI_NONAME:
193 // Host or service not found
194 4x return std::error_code(
195 static_cast<int>(std::errc::no_such_device_or_address),
196 4x std::generic_category());
197
198 case EAI_SERVICE:
199 // Service not supported for socket type
200 return std::error_code(
201 static_cast<int>(std::errc::invalid_argument),
202 std::generic_category());
203
204 case EAI_SOCKTYPE:
205 // Socket type not supported
206 return std::error_code(
207 static_cast<int>(std::errc::not_supported),
208 std::generic_category());
209
210 case EAI_SYSTEM:
211 // System error - use errno
212 return std::error_code(errno, std::generic_category());
213
214 default:
215 // Unknown error
216 return std::error_code(
217 static_cast<int>(std::errc::io_error), std::generic_category());
218 }
219 }
220
221 // posix_resolver
222
223 29x inline posix_resolver::posix_resolver(posix_resolver_service& svc) noexcept
224 29x : svc_(svc)
225 {
226 29x }
227
228 // posix_resolver::resolve_op implementation
229
230 inline void
231 16x posix_resolver::resolve_op::reset() noexcept
232 {
233 16x host.clear();
234 16x service.clear();
235 16x flags = resolve_flags::none;
236 16x stored_results = resolver_results{};
237 16x gai_error = 0;
238 16x cancelled.store(false, std::memory_order_relaxed);
239 16x stop_cb.reset();
240 16x ec_out = nullptr;
241 16x out = nullptr;
242 16x }
243
244 inline void
245 16x posix_resolver::resolve_op::operator()()
246 {
247 16x stop_cb.reset(); // Disconnect stop callback
248
249 16x bool const was_cancelled = cancelled.load(std::memory_order_acquire);
250
251 16x if (ec_out)
252 {
253 16x if (was_cancelled)
254 *ec_out = capy::error::canceled;
255 16x else if (gai_error != 0)
256 3x *ec_out = posix_resolver_detail::make_gai_error(gai_error);
257 else
258 13x *ec_out = {}; // Clear on success
259 }
260
261 16x if (out && !was_cancelled && gai_error == 0)
262 13x *out = std::move(stored_results);
263
264 16x impl->svc_.work_finished();
265 16x dispatch_coro(ex, h).resume();
266 16x }
267
268 inline void
269 posix_resolver::resolve_op::destroy()
270 {
271 stop_cb.reset();
272 }
273
274 inline void
275 33x posix_resolver::resolve_op::request_cancel() noexcept
276 {
277 33x cancelled.store(true, std::memory_order_release);
278 33x }
279
280 inline void
281 16x posix_resolver::resolve_op::start(std::stop_token const& token)
282 {
283 16x cancelled.store(false, std::memory_order_release);
284 16x stop_cb.reset();
285
286 16x if (token.stop_possible())
287 stop_cb.emplace(token, canceller{this});
288 16x }
289
290 // posix_resolver::reverse_resolve_op implementation
291
292 inline void
293 10x posix_resolver::reverse_resolve_op::reset() noexcept
294 {
295 10x ep = endpoint{};
296 10x flags = reverse_flags::none;
297 10x stored_host.clear();
298 10x stored_service.clear();
299 10x gai_error = 0;
300 10x cancelled.store(false, std::memory_order_relaxed);
301 10x stop_cb.reset();
302 10x ec_out = nullptr;
303 10x result_out = nullptr;
304 10x }
305
306 inline void
307 10x posix_resolver::reverse_resolve_op::operator()()
308 {
309 10x stop_cb.reset(); // Disconnect stop callback
310
311 10x bool const was_cancelled = cancelled.load(std::memory_order_acquire);
312
313 10x if (ec_out)
314 {
315 10x if (was_cancelled)
316 *ec_out = capy::error::canceled;
317 10x else if (gai_error != 0)
318 1x *ec_out = posix_resolver_detail::make_gai_error(gai_error);
319 else
320 9x *ec_out = {}; // Clear on success
321 }
322
323 10x if (result_out && !was_cancelled && gai_error == 0)
324 {
325 27x *result_out = reverse_resolver_result(
326 27x ep, std::move(stored_host), std::move(stored_service));
327 }
328
329 10x impl->svc_.work_finished();
330 10x dispatch_coro(ex, h).resume();
331 10x }
332
333 inline void
334 posix_resolver::reverse_resolve_op::destroy()
335 {
336 stop_cb.reset();
337 }
338
339 inline void
340 33x posix_resolver::reverse_resolve_op::request_cancel() noexcept
341 {
342 33x cancelled.store(true, std::memory_order_release);
343 33x }
344
345 inline void
346 10x posix_resolver::reverse_resolve_op::start(std::stop_token const& token)
347 {
348 10x cancelled.store(false, std::memory_order_release);
349 10x stop_cb.reset();
350
351 10x if (token.stop_possible())
352 stop_cb.emplace(token, canceller{this});
353 10x }
354
355 // posix_resolver implementation
356
357 inline std::coroutine_handle<>
358 16x posix_resolver::resolve(
359 std::coroutine_handle<> h,
360 capy::executor_ref ex,
361 std::string_view host,
362 std::string_view service,
363 resolve_flags flags,
364 std::stop_token token,
365 std::error_code* ec,
366 resolver_results* out)
367 {
368 16x auto& op = op_;
369 16x op.reset();
370 16x op.h = h;
371 16x op.ex = ex;
372 16x op.impl = this;
373 16x op.ec_out = ec;
374 16x op.out = out;
375 16x op.host = host;
376 16x op.service = service;
377 16x op.flags = flags;
378 16x op.start(token);
379
380 // Keep io_context alive while resolution is pending
381 16x op.ex.on_work_started();
382
383 // Prevent impl destruction while work is in flight
384 16x resolve_pool_op_.resolver_ = this;
385 16x resolve_pool_op_.ref_ = this->shared_from_this();
386 16x resolve_pool_op_.func_ = &posix_resolver::do_resolve_work;
387 16x if (!svc_.pool().post(&resolve_pool_op_))
388 {
389 // Pool shut down — complete with cancellation
390 resolve_pool_op_.ref_.reset();
391 op.cancelled.store(true, std::memory_order_release);
392 svc_.post(&op_);
393 }
394 16x return std::noop_coroutine();
395 }
396
397 inline std::coroutine_handle<>
398 10x posix_resolver::reverse_resolve(
399 std::coroutine_handle<> h,
400 capy::executor_ref ex,
401 endpoint const& ep,
402 reverse_flags flags,
403 std::stop_token token,
404 std::error_code* ec,
405 reverse_resolver_result* result_out)
406 {
407 10x auto& op = reverse_op_;
408 10x op.reset();
409 10x op.h = h;
410 10x op.ex = ex;
411 10x op.impl = this;
412 10x op.ec_out = ec;
413 10x op.result_out = result_out;
414 10x op.ep = ep;
415 10x op.flags = flags;
416 10x op.start(token);
417
418 // Keep io_context alive while resolution is pending
419 10x op.ex.on_work_started();
420
421 // Prevent impl destruction while work is in flight
422 10x reverse_pool_op_.resolver_ = this;
423 10x reverse_pool_op_.ref_ = this->shared_from_this();
424 10x reverse_pool_op_.func_ = &posix_resolver::do_reverse_resolve_work;
425 10x if (!svc_.pool().post(&reverse_pool_op_))
426 {
427 // Pool shut down — complete with cancellation
428 reverse_pool_op_.ref_.reset();
429 op.cancelled.store(true, std::memory_order_release);
430 svc_.post(&reverse_op_);
431 }
432 10x return std::noop_coroutine();
433 }
434
435 inline void
436 33x posix_resolver::cancel() noexcept
437 {
438 33x op_.request_cancel();
439 33x reverse_op_.request_cancel();
440 33x }
441
442 inline void
443 16x posix_resolver::do_resolve_work(pool_work_item* w) noexcept
444 {
445 16x auto* pw = static_cast<pool_op*>(w);
446 16x auto* self = pw->resolver_;
447
448 16x struct addrinfo hints{};
449 16x hints.ai_family = AF_UNSPEC;
450 16x hints.ai_socktype = SOCK_STREAM;
451 16x hints.ai_flags = posix_resolver_detail::flags_to_hints(self->op_.flags);
452
453 16x struct addrinfo* ai = nullptr;
454 48x int result = ::getaddrinfo(
455 32x self->op_.host.empty() ? nullptr : self->op_.host.c_str(),
456 32x self->op_.service.empty() ? nullptr : self->op_.service.c_str(),
457 &hints, &ai);
458
459 16x if (!self->op_.cancelled.load(std::memory_order_acquire))
460 {
461 16x if (result == 0 && ai)
462 {
463 self->op_.stored_results =
464 26x posix_resolver_detail::convert_results(
465 13x ai, self->op_.host, self->op_.service);
466 13x self->op_.gai_error = 0;
467 }
468 else
469 {
470 3x self->op_.gai_error = result;
471 }
472 }
473
474 16x if (ai)
475 13x ::freeaddrinfo(ai);
476
477 // Move ref to stack before post — post may trigger destroy_impl
478 // which erases the last shared_ptr, destroying *self (and *pw)
479 16x auto ref = std::move(pw->ref_);
480 16x self->svc_.post(&self->op_);
481 16x }
482
483 inline void
484 10x posix_resolver::do_reverse_resolve_work(pool_work_item* w) noexcept
485 {
486 10x auto* pw = static_cast<pool_op*>(w);
487 10x auto* self = pw->resolver_;
488
489 10x sockaddr_storage ss{};
490 socklen_t ss_len;
491
492 10x if (self->reverse_op_.ep.is_v4())
493 {
494 8x auto sa = to_sockaddr_in(self->reverse_op_.ep);
495 8x std::memcpy(&ss, &sa, sizeof(sa));
496 8x ss_len = sizeof(sockaddr_in);
497 }
498 else
499 {
500 2x auto sa = to_sockaddr_in6(self->reverse_op_.ep);
501 2x std::memcpy(&ss, &sa, sizeof(sa));
502 2x ss_len = sizeof(sockaddr_in6);
503 }
504
505 char host[NI_MAXHOST];
506 char service[NI_MAXSERV];
507
508 10x int result = ::getnameinfo(
509 reinterpret_cast<sockaddr*>(&ss), ss_len, host, sizeof(host),
510 service, sizeof(service),
511 posix_resolver_detail::flags_to_ni_flags(self->reverse_op_.flags));
512
513 10x if (!self->reverse_op_.cancelled.load(std::memory_order_acquire))
514 {
515 10x if (result == 0)
516 {
517 9x self->reverse_op_.stored_host = host;
518 9x self->reverse_op_.stored_service = service;
519 9x self->reverse_op_.gai_error = 0;
520 }
521 else
522 {
523 1x self->reverse_op_.gai_error = result;
524 }
525 }
526
527 // Move ref to stack before post — post may trigger destroy_impl
528 // which erases the last shared_ptr, destroying *self (and *pw)
529 10x auto ref = std::move(pw->ref_);
530 10x self->svc_.post(&self->reverse_op_);
531 10x }
532
533 // posix_resolver_service implementation
534
535 inline void
536 412x posix_resolver_service::shutdown()
537 {
538 412x std::lock_guard<std::mutex> lock(mutex_);
539
540 // Cancel all resolvers (sets cancelled flag checked by pool threads)
541 412x for (auto* impl = resolver_list_.pop_front(); impl != nullptr;
542 impl = resolver_list_.pop_front())
543 {
544 impl->cancel();
545 }
546
547 // Clear the map which releases shared_ptrs.
548 // The thread pool service shuts down separately via
549 // execution_context service ordering.
550 412x resolver_ptrs_.clear();
551 412x }
552
553 inline io_object::implementation*
554 29x posix_resolver_service::construct()
555 {
556 29x auto ptr = std::make_shared<posix_resolver>(*this);
557 29x auto* impl = ptr.get();
558
559 {
560 29x std::lock_guard<std::mutex> lock(mutex_);
561 29x resolver_list_.push_back(impl);
562 29x resolver_ptrs_[impl] = std::move(ptr);
563 29x }
564
565 29x return impl;
566 29x }
567
568 inline void
569 29x posix_resolver_service::destroy_impl(posix_resolver& impl)
570 {
571 29x std::lock_guard<std::mutex> lock(mutex_);
572 29x resolver_list_.remove(&impl);
573 29x resolver_ptrs_.erase(&impl);
574 29x }
575
576 inline void
577 26x posix_resolver_service::post(scheduler_op* op)
578 {
579 26x sched_->post(op);
580 26x }
581
582 inline void
583 posix_resolver_service::work_started() noexcept
584 {
585 sched_->work_started();
586 }
587
588 inline void
589 26x posix_resolver_service::work_finished() noexcept
590 {
591 26x sched_->work_finished();
592 26x }
593
594 // Free function to get/create the resolver service
595
596 inline posix_resolver_service&
597 412x get_resolver_service(capy::execution_context& ctx, scheduler& sched)
598 {
599 412x return ctx.make_service<posix_resolver_service>(sched);
600 }
601
602 } // namespace boost::corosio::detail
603
604 #endif // BOOST_COROSIO_POSIX
605
606 #endif // BOOST_COROSIO_NATIVE_DETAIL_POSIX_POSIX_RESOLVER_SERVICE_HPP
607