include/boost/corosio/native/detail/iocp/win_resolver_service.hpp

95.8% Lines (253/264) 100.0% List of functions (25/25) 88.8% Branches (111/125)
win_resolver_service.hpp
f(x) Functions (25)
Function Calls Lines Branches Blocks
boost::corosio::detail::win_resolver_service::destroy(boost::corosio::io_object::implementation*) :54 64x 100.0% – 100.0% boost::corosio::detail::win_resolver_service::pool() :101 15x 100.0% – 100.0% boost::corosio::detail::resolver_detail::to_wide[abi:cxx11](std::basic_string_view<char, std::char_traits<char> >) :120 74x 100.0% 100.0% 76.9% boost::corosio::detail::resolver_detail::flags_to_hints(boost::corosio::resolve_flags) :140 37x 100.0% 100.0% 100.0% boost::corosio::detail::resolver_detail::flags_to_ni_flags(boost::corosio::reverse_flags) :162 15x 100.0% 100.0% 100.0% boost::corosio::detail::resolver_detail::from_wide[abi:cxx11](std::basic_string_view<wchar_t, std::char_traits<wchar_t> >) :180 24x 91.7% 85.7% 76.9% boost::corosio::detail::resolver_detail::convert_results(addrinfoexW*) :202 21x 90.9% 87.5% 82.4% boost::corosio::detail::resolve_op::completion(unsigned long, unsigned long, _OVERLAPPED*) :228 16x 100.0% 75.0% 75.0% boost::corosio::detail::resolve_op::resolve_op() :255 65x 100.0% – 100.0% boost::corosio::detail::resolve_op::do_complete(void*, boost::corosio::detail::scheduler_op*, unsigned int, unsigned int) :258 37x 93.1% 81.5% 82.6% boost::corosio::detail::reverse_resolve_op::reverse_resolve_op() :319 65x 100.0% – 100.0% boost::corosio::detail::reverse_resolve_op::do_complete(void*, boost::corosio::detail::scheduler_op*, unsigned int, unsigned int) :325 15x 95.8% 77.8% 87.8% boost::corosio::detail::win_resolver::win_resolver(boost::corosio::detail::win_resolver_service&) :375 65x 100.0% – 100.0% boost::corosio::detail::win_resolver::resolve(std::__n4861::coroutine_handle<void>, boost::capy::executor_ref, std::basic_string_view<char, std::char_traits<char> >, std::basic_string_view<char, std::char_traits<char> >, boost::corosio::resolve_flags, std::stop_token, std::error_code*, std::vector<boost::corosio::endpoint, std::allocator<boost::corosio::endpoint> >*) :381 37x 100.0% 100.0% 88.6% boost::corosio::detail::win_resolver::reverse_resolve(std::__n4861::coroutine_handle<void>, boost::capy::executor_ref, boost::corosio::endpoint const&, boost::corosio::reverse_flags, std::stop_token, std::error_code*, boost::corosio::endpoint_name*) :458 15x 73.9% 60.0% 71.4% boost::corosio::detail::win_resolver::cancel() :504 73x 100.0% 100.0% 100.0% boost::corosio::detail::win_resolver::do_reverse_resolve_work(boost::corosio::detail::pool_work_item*) :526 15x 100.0% 100.0% 100.0% boost::corosio::detail::win_resolver_service::win_resolver_service(boost::capy::execution_context&) :582 64x 100.0% 100.0% 66.7% boost::corosio::detail::win_resolver_service::~win_resolver_service() :589 128x 100.0% – 100.0% boost::corosio::detail::win_resolver_service::shutdown() :592 64x 100.0% 100.0% 100.0% boost::corosio::detail::win_resolver_service::construct() :610 65x 100.0% 100.0% 70.6% boost::corosio::detail::win_resolver_service::destroy_impl(boost::corosio::detail::win_resolver&) :625 64x 100.0% 100.0% 66.7% boost::corosio::detail::win_resolver_service::post(boost::corosio::detail::overlapped_op*) :633 52x 100.0% – 100.0% boost::corosio::detail::win_resolver_service::work_started() :639 52x 100.0% – 100.0% boost::corosio::detail::win_resolver_service::work_finished() :645 52x 100.0% – 100.0%
Line Branch TLA Hits Source Code
1 //
2 // Copyright (c) 2025 Vinnie Falco ([email protected])
3 // Copyright (c) 2026 Steve Gerbino
4 // Copyright (c) 2026 Michael Vandeberg
5 //
6 // Distributed under the Boost Software License, Version 1.0. (See accompanying
7 // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
8 //
9 // Official repository: https://github.com/cppalliance/corosio
10 //
11
12 #ifndef BOOST_COROSIO_NATIVE_DETAIL_IOCP_WIN_RESOLVER_SERVICE_HPP
13 #define BOOST_COROSIO_NATIVE_DETAIL_IOCP_WIN_RESOLVER_SERVICE_HPP
14
15 #include <boost/corosio/detail/platform.hpp>
16
17 #if BOOST_COROSIO_HAS_IOCP
18
19 #include <boost/corosio/native/detail/iocp/win_resolver.hpp>
20 #include <boost/corosio/detail/scheduler.hpp>
21 #include <boost/corosio/detail/thread_pool.hpp>
22
23 #include <unordered_map>
24
25 namespace boost::corosio::detail {
26
27 /** Windows IOCP resolver management service.
28
29 This service owns all resolver implementations and coordinates their
30 lifecycle. It provides:
31
32 - Resolver implementation allocation and deallocation
33 - Async DNS resolution via GetAddrInfoExW
34 - Graceful shutdown - destroys all implementations when io_context stops
35
36 @par Thread Safety
37 All public member functions are thread-safe.
38
39 @note Only available on Windows platforms with _WIN32_WINNT >= 0x0602.
40 */
41 BOOST_COROSIO_MSVC_WARNING_PUSH
42 // 4251: std::/detail:: members; 4275: non-exported win_wsa_init base
43 BOOST_COROSIO_MSVC_WARNING_DISABLE(4251 4275)
44 class BOOST_COROSIO_DECL win_resolver_service final
45 : private win_wsa_init
46 , public capy::execution_context::service
47 , public io_object::io_service
48 {
49 public:
50 using key_type = win_resolver_service;
51
52 io_object::implementation* construct() override;
53
54 64x void destroy(io_object::implementation* p) override
55 {
56 64x auto& impl = static_cast<win_resolver&>(*p);
57 64x impl.cancel();
58 64x destroy_impl(impl);
59 64x }
60
61 /** Construct the resolver service.
62
63 @param ctx Reference to the owning execution_context.
64 */
65 explicit win_resolver_service(capy::execution_context& ctx);
66
67 /** Destroy the resolver service. */
68 ~win_resolver_service();
69
70 win_resolver_service(win_resolver_service const&) = delete;
71 win_resolver_service& operator=(win_resolver_service const&) = delete;
72
73 /** Shut down the service. */
74 void shutdown() override;
75
76 /** Destroy a resolver implementation. */
77 void destroy_impl(win_resolver& impl);
78
79 /** Post an operation for completion. */
80 void post(overlapped_op* op);
81
82 /** Notify scheduler of pending I/O work. */
83 void work_started() noexcept;
84
85 /** Notify scheduler that I/O work completed. */
86 void work_finished() noexcept;
87
88 /** Return the resolver thread pool.
89
90 The pool's service is created on first use, so this can fail
91 where a plain accessor could not. Its workers start later, on
92 the first post, and a thread the system refuses there is
93 reported by that post rather than thrown here.
94
95 @throws std::bad_alloc If the service cannot be allocated.
96
97 @return The context's shared blocking-I/O pool.
98
99 @see thread_pool_ref::get
100 */
101 15x thread_pool& pool()
102 {
103 15x return pool_.get();
104 }
105
106 private:
107 scheduler& sched_;
108 thread_pool_ref pool_;
109 win_mutex mutex_;
110 intrusive_list<win_resolver> resolver_list_;
111 std::unordered_map<win_resolver*, std::shared_ptr<win_resolver>>
112 resolver_ptrs_;
113 };
114 BOOST_COROSIO_MSVC_WARNING_POP
115
116 namespace resolver_detail {
117
118 // Convert narrow string to wide string
119 inline std::wstring
120 74x to_wide(std::string_view s)
121 {
122
2/2
✓ Branch 3 → 4 taken 7 times.
✓ Branch 3 → 5 taken 67 times.
74x if (s.empty())
123 7x return {};
124
125
1/1
✓ Branch 7 → 8 taken 67 times.
67x int len = ::MultiByteToWideChar(
126 67x CP_UTF8, 0, s.data(), static_cast<int>(s.size()), nullptr, 0);
127
128
2/2
✓ Branch 8 → 9 taken 2 times.
✓ Branch 8 → 10 taken 65 times.
67x if (len <= 0)
129 2x return {};
130
131
1/1
✓ Branch 12 → 13 taken 65 times.
65x std::wstring result(static_cast<std::size_t>(len), L'\0');
132
1/1
✓ Branch 17 → 18 taken 65 times.
130x ::MultiByteToWideChar(
133 65x CP_UTF8, 0, s.data(), static_cast<int>(s.size()), result.data(), len);
134
135 65x return result;
136 65x }
137
138 // Convert resolve_flags to ADDRINFOEXW hints
139 inline int
140 37x flags_to_hints(resolve_flags flags)
141 {
142 37x int hints = 0;
143
144
2/2
✓ Branch 3 → 4 taken 1 time.
✓ Branch 3 → 5 taken 36 times.
37x if ((flags & resolve_flags::passive) != resolve_flags::none)
145 1x hints |= AI_PASSIVE;
146
2/2
✓ Branch 6 → 7 taken 17 times.
✓ Branch 6 → 8 taken 20 times.
37x if ((flags & resolve_flags::numeric_host) != resolve_flags::none)
147 17x hints |= AI_NUMERICHOST;
148
2/2
✓ Branch 9 → 10 taken 11 times.
✓ Branch 9 → 11 taken 26 times.
37x if ((flags & resolve_flags::numeric_service) != resolve_flags::none)
149 11x hints |= AI_NUMERICSERV;
150
2/2
✓ Branch 12 → 13 taken 1 time.
✓ Branch 12 → 14 taken 36 times.
37x if ((flags & resolve_flags::address_configured) != resolve_flags::none)
151 1x hints |= AI_ADDRCONFIG;
152
2/2
✓ Branch 15 → 16 taken 1 time.
✓ Branch 15 → 17 taken 36 times.
37x if ((flags & resolve_flags::v4_mapped) != resolve_flags::none)
153 1x hints |= AI_V4MAPPED;
154
2/2
✓ Branch 18 → 19 taken 1 time.
✓ Branch 18 → 20 taken 36 times.
37x if ((flags & resolve_flags::all_matching) != resolve_flags::none)
155 1x hints |= AI_ALL;
156
157 37x return hints;
158 }
159
160 // Convert reverse_flags to getnameinfo NI_* flags
161 inline int
162 15x flags_to_ni_flags(reverse_flags flags)
163 {
164 15x int ni_flags = 0;
165
166
2/2
✓ Branch 3 → 4 taken 7 times.
✓ Branch 3 → 5 taken 8 times.
15x if ((flags & reverse_flags::numeric_host) != reverse_flags::none)
167 7x ni_flags |= NI_NUMERICHOST;
168
2/2
✓ Branch 6 → 7 taken 7 times.
✓ Branch 6 → 8 taken 8 times.
15x if ((flags & reverse_flags::numeric_service) != reverse_flags::none)
169 7x ni_flags |= NI_NUMERICSERV;
170
2/2
✓ Branch 9 → 10 taken 1 time.
✓ Branch 9 → 11 taken 14 times.
15x if ((flags & reverse_flags::name_required) != reverse_flags::none)
171 1x ni_flags |= NI_NAMEREQD;
172
2/2
✓ Branch 12 → 13 taken 1 time.
✓ Branch 12 → 14 taken 14 times.
15x if ((flags & reverse_flags::datagram_service) != reverse_flags::none)
173 1x ni_flags |= NI_DGRAM;
174
175 15x return ni_flags;
176 }
177
178 // Convert wide string to UTF-8 string
179 inline std::string
180 24x from_wide(std::wstring_view s)
181 {
182
1/2
✗ Branch 3 → 4 not taken.
✓ Branch 3 → 5 taken 24 times.
24x if (s.empty())
183 ✗ return {};
184
185
1/1
✓ Branch 7 → 8 taken 24 times.
24x int len = ::WideCharToMultiByte(
186 24x CP_UTF8, 0, s.data(), static_cast<int>(s.size()), nullptr, 0, nullptr,
187 nullptr);
188
189
2/2
✓ Branch 8 → 9 taken 1 time.
✓ Branch 8 → 10 taken 23 times.
24x if (len <= 0)
190 1x return {};
191
192
1/1
✓ Branch 12 → 13 taken 23 times.
23x std::string result(static_cast<std::size_t>(len), '\0');
193
1/1
✓ Branch 17 → 18 taken 23 times.
46x ::WideCharToMultiByte(
194 23x CP_UTF8, 0, s.data(), static_cast<int>(s.size()), result.data(), len,
195 nullptr, nullptr);
196
197 23x return result;
198 23x }
199
200 // Convert ADDRINFOEXW results to endpoints
201 inline std::vector<endpoint>
202 21x convert_results(ADDRINFOEXW* ai)
203 {
204 21x std::vector<endpoint> endpoints;
205
206
2/2
✓ Branch 12 → 3 taken 28 times.
✓ Branch 12 → 13 taken 21 times.
49x for (auto* p = ai; p != nullptr; p = p->ai_next)
207 {
208
2/2
✓ Branch 3 → 4 taken 18 times.
✓ Branch 3 → 7 taken 10 times.
28x if (p->ai_family == AF_INET)
209 {
210 18x auto* addr = reinterpret_cast<sockaddr_in*>(p->ai_addr);
211
1/1
✓ Branch 5 → 6 taken 18 times.
18x endpoints.push_back(from_sockaddr_in(*addr));
212 }
213
1/2
✓ Branch 7 → 8 taken 10 times.
✗ Branch 7 → 11 not taken.
10x else if (p->ai_family == AF_INET6)
214 {
215 10x auto* addr = reinterpret_cast<sockaddr_in6*>(p->ai_addr);
216
1/1
✓ Branch 9 → 10 taken 10 times.
10x endpoints.push_back(from_sockaddr_in6(*addr));
217 }
218 }
219
220 21x return endpoints;
221 ✗ }
222
223 } // namespace resolver_detail
224
225 // resolve_op
226
227 inline void CALLBACK
228 16x resolve_op::completion(DWORD dwError, DWORD /*bytes*/, OVERLAPPED* ov)
229 {
230
1/2
✓ Branch 2 → 3 taken 16 times.
✗ Branch 2 → 4 not taken.
16x auto* op = static_cast<resolve_op*>(ov);
231
232 // The post below can be drained and the win_resolver freed before
233 // this returns, and Windows owns the OVERLAPPED embedded in it until
234 // then.
235
1/1
✓ Branch 5 → 6 taken 16 times.
16x auto keepalive = op->impl->shared_from_this();
236
237 16x op->dwError = dwError;
238
239 // Posting may free the impl svc_ is read through.
240 16x auto& svc = op->impl->svc_;
241
242 // The handle dies with this callback's entry; a racing cancel() must
243 // not reach one Windows has already reclaimed.
244 {
245 16x std::lock_guard<win_mutex> lock(op->impl->cancel_mutex_);
246 16x op->cancel_handle = nullptr;
247 16x }
248
249 // The initiation credit rides with the op and is released where it is
250 // consumed, as on POSIX; releasing it here would let the count reach
251 // zero with the op still queued.
252
1/1
✓ Branch 8 → 9 taken 16 times.
16x svc.post(op);
253 16x }
254
255 65x inline resolve_op::resolve_op() noexcept : overlapped_op(&do_complete) {}
256
257 inline void
258 37x resolve_op::do_complete(
259 void* owner,
260 scheduler_op* base,
261 std::uint32_t /*bytes*/,
262 std::uint32_t /*error*/)
263 {
264 37x auto* op = static_cast<resolve_op*>(base);
265
266 // Dropping the keepalive below may free the impl svc_ is read through.
267 37x auto& svc = op->impl->svc_;
268
269
2/2
✓ Branch 2 → 3 taken 5 times.
✓ Branch 2 → 10 taken 32 times.
37x if (!owner)
270 {
271 // Destroy path
272 5x op->stop_cb.reset();
273
1/2
✗ Branch 4 → 5 not taken.
✓ Branch 4 → 7 taken 5 times.
5x if (op->results)
274 {
275 ✗ ::FreeAddrInfoExW(op->results);
276 ✗ op->results = nullptr;
277 }
278 // Dropping the keepalive may destroy the implementation this op
279 // is embedded in, so nothing may touch it afterwards.
280 5x op->impl_ptr.reset();
281 5x svc.work_finished();
282 5x return;
283 }
284
285 32x op->stop_cb.reset();
286
287
1/2
✓ Branch 11 → 12 taken 32 times.
✗ Branch 11 → 20 not taken.
32x if (op->ec_out)
288 {
289
2/2
✓ Branch 13 → 14 taken 1 time.
✓ Branch 13 → 16 taken 31 times.
32x if (op->cancelled.load(std::memory_order_acquire))
290 1x *op->ec_out = capy::error::canceled;
291
2/2
✓ Branch 16 → 17 taken 10 times.
✓ Branch 16 → 18 taken 21 times.
31x else if (op->dwError != 0)
292 10x *op->ec_out = make_err(op->dwError);
293 else
294 21x *op->ec_out = {};
295 }
296
297
2/2
✓ Branch 22 → 23 taken 31 times.
✓ Branch 22 → 26 taken 1 time.
32x if (op->out && !op->cancelled.load(std::memory_order_acquire) &&
298
6/8
✓ Branch 20 → 21 taken 32 times.
✗ Branch 20 → 26 not taken.
✓ Branch 23 → 24 taken 21 times.
✓ Branch 23 → 26 taken 10 times.
✓ Branch 24 → 25 taken 21 times.
✗ Branch 24 → 26 not taken.
✓ Branch 27 → 28 taken 21 times.
✓ Branch 27 → 32 taken 11 times.
64x op->dwError == 0 && op->results)
299 {
300
1/1
✓ Branch 28 → 29 taken 21 times.
21x *op->out = resolver_detail::convert_results(op->results);
301 }
302
303
2/2
✓ Branch 32 → 33 taken 21 times.
✓ Branch 32 → 35 taken 11 times.
32x if (op->results)
304 {
305
1/1
✓ Branch 33 → 34 taken 21 times.
21x ::FreeAddrInfoExW(op->results);
306 21x op->results = nullptr;
307 }
308
309 32x op->cont.h = op->h;
310 // Hold the keepalive across the dispatch: it may be the last
311 // reference to the implementation this op is embedded in.
312 32x auto prevent_destroy = std::move(op->impl_ptr);
313 32x svc.work_finished();
314
2/2
✓ Branch 38 → 39 taken 32 times.
✓ Branch 39 → 40 taken 32 times.
32x dispatch_coro(op->ex, op->cont).resume();
315 32x }
316
317 // reverse_resolve_op
318
319 65x inline reverse_resolve_op::reverse_resolve_op() noexcept
320 65x : overlapped_op(&do_complete)
321 {
322 65x }
323
324 inline void
325 15x reverse_resolve_op::do_complete(
326 void* owner,
327 scheduler_op* base,
328 std::uint32_t /*bytes*/,
329 std::uint32_t /*error*/)
330 {
331 15x auto* op = static_cast<reverse_resolve_op*>(base);
332
333 // Cached before the keepalive drops: see resolve_op::do_complete.
334 15x auto& svc = op->impl->svc_;
335
336
2/2
✓ Branch 2 → 3 taken 1 time.
✓ Branch 2 → 7 taken 14 times.
15x if (!owner)
337 {
338 1x op->stop_cb.reset();
339 // Dropping the keepalive may destroy the implementation this
340 // op is embedded in, so nothing may touch it afterwards.
341 1x op->impl_ptr.reset();
342 1x svc.work_finished();
343 1x return;
344 }
345
346 14x op->stop_cb.reset();
347
348
1/2
✓ Branch 8 → 9 taken 14 times.
✗ Branch 8 → 17 not taken.
14x if (op->ec_out)
349 {
350
1/2
✗ Branch 10 → 11 not taken.
✓ Branch 10 → 13 taken 14 times.
14x if (op->cancelled.load(std::memory_order_acquire))
351 ✗ *op->ec_out = capy::error::canceled;
352
2/2
✓ Branch 13 → 14 taken 2 times.
✓ Branch 13 → 15 taken 12 times.
14x else if (op->gai_error != 0)
353 2x *op->ec_out = make_err(static_cast<DWORD>(op->gai_error));
354 else
355 12x *op->ec_out = {};
356 }
357
358
4/6
✓ Branch 17 → 18 taken 14 times.
✗ Branch 17 → 22 not taken.
✓ Branch 19 → 20 taken 14 times.
✗ Branch 19 → 22 not taken.
✓ Branch 23 → 24 taken 12 times.
✓ Branch 23 → 31 taken 2 times.
28x if (op->result_out && !op->cancelled.load(std::memory_order_acquire) &&
359
2/2
✓ Branch 20 → 21 taken 12 times.
✓ Branch 20 → 22 taken 2 times.
14x op->gai_error == 0)
360 {
361 36x *op->result_out = endpoint_name{
362 36x std::move(op->stored_host), std::move(op->stored_service)};
363 }
364
365 14x op->cont.h = op->h;
366 // Hold the keepalive across the dispatch: it may be the last
367 // reference to the implementation this op is embedded in.
368 14x auto prevent_destroy = std::move(op->impl_ptr);
369 14x svc.work_finished();
370
2/2
✓ Branch 34 → 35 taken 14 times.
✓ Branch 35 → 36 taken 14 times.
14x dispatch_coro(op->ex, op->cont).resume();
371 14x }
372
373 // win_resolver
374
375 65x inline win_resolver::win_resolver(win_resolver_service& svc) noexcept
376 65x : svc_(svc)
377 {
378 65x }
379
380 inline std::coroutine_handle<>
381 37x win_resolver::resolve(
382 std::coroutine_handle<> h,
383 capy::executor_ref d,
384 std::string_view host,
385 std::string_view service,
386 resolve_flags flags,
387 std::stop_token token,
388 std::error_code* ec,
389 std::vector<endpoint>* out)
390 {
391 37x auto& op = op_;
392 37x op.reset();
393 37x op.h = h;
394 37x op.ex = d;
395 37x op.ec_out = ec;
396 37x op.out = out;
397 37x op.impl = this;
398 37x op.host = host;
399 37x op.service = service;
400
1/1
✓ Branch 5 → 6 taken 37 times.
37x op.host_w = resolver_detail::to_wide(host);
401
1/1
✓ Branch 8 → 9 taken 37 times.
37x op.service_w = resolver_detail::to_wide(service);
402 37x op.start(token);
403
404 37x op.hints = ADDRINFOEXW{};
405 37x op.hints.ai_family = AF_UNSPEC;
406 37x op.hints.ai_socktype = SOCK_STREAM;
407 37x op.hints.ai_flags = resolver_detail::flags_to_hints(flags);
408
409 // Keep io_context alive while resolution is pending
410 37x auto& svc = svc_;
411 37x svc.work_started();
412
413 // Prevent impl destruction while the async resolve is in flight and
414 // its completion waits in the scheduler queue: the op is embedded in
415 // this win_resolver, which teardown may otherwise free before the
416 // queued completion drains. Mirrors the reverse path's keepalive.
417
1/1
✓ Branch 14 → 15 taken 37 times.
37x op.impl_ptr = this->shared_from_this();
418
419 // Under the lock: the handle written here and the callback that
420 // retires it must not interleave.
421 int result;
422 {
423 37x std::lock_guard<win_mutex> lock(cancel_mutex_);
424 37x op.cancel_handle = nullptr;
425
426
5/5
✓ Branch 19 → 20 taken 7 times.
✓ Branch 19 → 21 taken 30 times.
✓ Branch 23 → 24 taken 2 times.
✓ Branch 23 → 25 taken 35 times.
✓ Branch 26 → 27 taken 37 times.
109x result = ::GetAddrInfoExW(
427 72x op.host_w.empty() ? nullptr : op.host_w.c_str(),
428 30x op.service_w.empty() ? nullptr : op.service_w.c_str(), NS_DNS,
429 37x nullptr, &op.hints, &op.results, nullptr, &op,
430 &resolve_op::completion, &op.cancel_handle);
431
432 // No callback runs on synchronous completion.
433
2/2
✓ Branch 27 → 28 taken 21 times.
✓ Branch 27 → 29 taken 16 times.
37x if (result != WSA_IO_PENDING)
434 21x op.cancel_handle = nullptr;
435 37x }
436
437
2/2
✓ Branch 30 → 31 taken 21 times.
✓ Branch 30 → 36 taken 16 times.
37x if (result != WSA_IO_PENDING)
438 {
439 // Completed synchronously - callback won't be invoked
440
2/2
✓ Branch 31 → 32 taken 13 times.
✓ Branch 31 → 33 taken 8 times.
21x if (result == 0)
441 {
442 13x op.dwError = 0;
443 }
444 else
445 {
446 8x op.dwError = static_cast<DWORD>(::WSAGetLastError());
447 }
448
449 // Post only, as above; svc_ is read through this, which the post
450 // may free.
451 21x svc.post(&op);
452 }
453 // completion is always posted to scheduler queue, never inline.
454 37x return std::noop_coroutine();
455 }
456
457 inline std::coroutine_handle<>
458 15x win_resolver::reverse_resolve(
459 std::coroutine_handle<> h,
460 capy::executor_ref d,
461 endpoint const& ep,
462 reverse_flags flags,
463 std::stop_token token,
464 std::error_code* ec,
465 endpoint_name* result_out)
466 {
467 15x auto& op = reverse_op_;
468 15x op.reset();
469 15x op.h = h;
470 15x op.ex = d;
471 15x op.ec_out = ec;
472 15x op.result_out = result_out;
473 15x op.impl = this;
474 15x op.ep = ep;
475 15x op.flags = flags;
476 15x op.start(token);
477
478 // Keep io_context alive while resolution is pending
479 15x svc_.work_started();
480
481 // Prevent impl destruction while work is in flight
482 15x reverse_pool_op_.resolver_ = this;
483
1/1
✓ Branch 5 → 6 taken 15 times.
15x reverse_pool_op_.ref_ = this->shared_from_this();
484 15x reverse_pool_op_.func_ = &win_resolver::do_reverse_resolve_work;
485
2/3
✓ Branch 8 → 9 taken 15 times.
✗ Branch 11 → 12 not taken.
✓ Branch 11 → 17 taken 15 times.
15x if (auto pec = svc_.pool().post(&reverse_pool_op_))
486 {
487 // The pool is shutting down, or the system refused it a thread.
488 // Nothing of this resolve went cross-thread, so it answers here
489 // rather than through a completion the scheduler has to carry
490 // back.
491 ✗ reverse_pool_op_.ref_.reset();
492 ✗ op.stop_cb.reset();
493 ✗ svc_.work_finished();
494 ✗ *ec = pec;
495 ✗ op.cont.h = h;
496 ✗ return dispatch_coro(d, op.cont);
497 }
498 // The work the pool took completes on its own thread and is always
499 // posted to the scheduler queue, never inline.
500 15x return std::noop_coroutine();
501 }
502
503 inline void
504 73x win_resolver::cancel() noexcept
505 {
506 73x op_.request_cancel();
507 73x reverse_op_.request_cancel();
508
509 // Whoever claims the handle owns it: the callback retires it on
510 // entry, and a claim consumes it, so neither a racing callback nor a
511 // second cancel() reaches one Windows has reclaimed.
512 73x HANDLE h = nullptr;
513 {
514 73x std::lock_guard<win_mutex> lock(cancel_mutex_);
515 73x h = op_.cancel_handle;
516 73x op_.cancel_handle = nullptr;
517 73x }
518
519 // Outside the lock: GetAddrInfoExCancel can wait on the completion
520 // routine, which takes that same lock.
521
2/2
✓ Branch 6 → 7 taken 6 times.
✓ Branch 6 → 8 taken 67 times.
73x if (h)
522 6x ::GetAddrInfoExCancel(&h);
523 73x }
524
525 inline void
526 15x win_resolver::do_reverse_resolve_work(pool_work_item* w) noexcept
527 {
528 15x auto* pw = static_cast<pool_op*>(w);
529 15x auto* self = pw->resolver_;
530
531 15x sockaddr_storage ss{};
532 int ss_len;
533
534
2/2
✓ Branch 3 → 4 taken 13 times.
✓ Branch 3 → 6 taken 2 times.
15x if (self->reverse_op_.ep.is_v4())
535 {
536 13x auto sa = to_sockaddr_in(self->reverse_op_.ep);
537 13x std::memcpy(&ss, &sa, sizeof(sa));
538 13x ss_len = sizeof(sockaddr_in);
539 }
540 else
541 {
542 2x auto sa = to_sockaddr_in6(self->reverse_op_.ep);
543 2x std::memcpy(&ss, &sa, sizeof(sa));
544 2x ss_len = sizeof(sockaddr_in6);
545 }
546
547 wchar_t host[NI_MAXHOST];
548 wchar_t service[NI_MAXSERV];
549
550 15x int result = ::GetNameInfoW(
551 reinterpret_cast<sockaddr*>(&ss), ss_len, host, NI_MAXHOST, service,
552 NI_MAXSERV,
553 resolver_detail::flags_to_ni_flags(self->reverse_op_.flags));
554
555
2/2
✓ Branch 11 → 12 taken 14 times.
✓ Branch 11 → 23 taken 1 time.
15x if (!self->reverse_op_.cancelled.load(std::memory_order_acquire))
556 {
557
2/2
✓ Branch 12 → 13 taken 12 times.
✓ Branch 12 → 22 taken 2 times.
14x if (result == 0)
558 {
559 12x self->reverse_op_.stored_host = resolver_detail::from_wide(host);
560 self->reverse_op_.stored_service =
561 12x resolver_detail::from_wide(service);
562 12x self->reverse_op_.gai_error = 0;
563 }
564 else
565 {
566 2x self->reverse_op_.gai_error = result;
567 }
568 }
569
570 // Hand the keepalive to the op: the completion waits in the
571 // scheduler's queue, and the implementation embedding it must
572 // outlive that wait. Nothing may touch *self after the post.
573 15x self->reverse_op_.impl_ptr = std::move(pw->ref_);
574
575 // Post only; the initiation credit is released where the op is
576 // consumed.
577 15x self->svc_.post(&self->reverse_op_);
578 15x }
579
580 // win_resolver_service
581
582 64x inline win_resolver_service::win_resolver_service(
583 64x capy::execution_context& ctx)
584 128x : sched_(get_scheduler(ctx))
585
2/2
✓ Branch 5 → 6 taken 64 times.
✓ Branch 7 → 8 taken 64 times.
64x , pool_(ctx)
586 {
587 64x }
588
589 128x inline win_resolver_service::~win_resolver_service() {}
590
591 inline void
592 64x win_resolver_service::shutdown()
593 {
594 64x std::lock_guard<win_mutex> lock(mutex_);
595
596 // Cancel all resolvers (sets cancelled flag checked by pool threads)
597
2/2
✓ Branch 6 → 4 taken 1 time.
✓ Branch 6 → 7 taken 64 times.
65x for (auto* impl = resolver_list_.pop_front(); impl != nullptr;
598 1x impl = resolver_list_.pop_front())
599 {
600 1x impl->cancel();
601 }
602
603 // Clear the map which releases shared_ptrs.
604 // The thread pool service shuts down separately via
605 // execution_context service ordering.
606 64x resolver_ptrs_.clear();
607 64x }
608
609 inline io_object::implementation*
610 65x win_resolver_service::construct()
611 {
612
1/1
✓ Branch 2 → 3 taken 65 times.
65x auto ptr = std::make_shared<win_resolver>(*this);
613 65x auto* impl = ptr.get();
614
615 {
616 65x std::lock_guard<win_mutex> lock(mutex_);
617 65x resolver_list_.push_back(impl);
618
1/1
✓ Branch 7 → 8 taken 65 times.
65x resolver_ptrs_[impl] = std::move(ptr);
619 65x }
620
621 65x return impl;
622 65x }
623
624 inline void
625 64x win_resolver_service::destroy_impl(win_resolver& impl)
626 {
627 64x std::lock_guard<win_mutex> lock(mutex_);
628 64x resolver_list_.remove(&impl);
629
1/1
✓ Branch 4 → 5 taken 64 times.
64x resolver_ptrs_.erase(&impl);
630 64x }
631
632 inline void
633 52x win_resolver_service::post(overlapped_op* op)
634 {
635 52x sched_.post(op);
636 52x }
637
638 inline void
639 52x win_resolver_service::work_started() noexcept
640 {
641 52x sched_.work_started();
642 52x }
643
644 inline void
645 52x win_resolver_service::work_finished() noexcept
646 {
647 52x sched_.work_finished();
648 52x }
649
650 } // namespace boost::corosio::detail
651
652 #endif // BOOST_COROSIO_HAS_IOCP
653
654 #endif // BOOST_COROSIO_NATIVE_DETAIL_IOCP_WIN_RESOLVER_SERVICE_HPP
655