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

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