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

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