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

88.8% Lines (222/250) 100.0% Functions (27/27) 75.4% Branches (98/130)
include/boost/corosio/native/detail/iocp/win_resolver_service.hpp
Line Branch TLA Hits Source Code
1 //
2 // Copyright (c) 2025 Vinnie Falco ([email protected])
3 // Copyright (c) 2026 Steve Gerbino
4 //
5 // Distributed under the Boost Software License, Version 1.0. (See accompanying
6 // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
7 //
8 // Official repository: https://github.com/cppalliance/corosio
9 //
10
11 #ifndef BOOST_COROSIO_NATIVE_DETAIL_IOCP_WIN_RESOLVER_SERVICE_HPP
12 #define BOOST_COROSIO_NATIVE_DETAIL_IOCP_WIN_RESOLVER_SERVICE_HPP
13
14 #include <boost/corosio/detail/platform.hpp>
15
16 #if BOOST_COROSIO_HAS_IOCP
17
18 #include <boost/corosio/native/detail/iocp/win_resolver.hpp>
19
20 namespace boost::corosio::detail {
21
22 /** Windows IOCP resolver management service.
23
24 This service owns all resolver implementations and coordinates their
25 lifecycle. It provides:
26
27 - Resolver implementation allocation and deallocation
28 - Async DNS resolution via GetAddrInfoExW
29 - Graceful shutdown - destroys all implementations when io_context stops
30
31 @par Thread Safety
32 All public member functions are thread-safe.
33
34 @note Only available on Windows platforms with _WIN32_WINNT >= 0x0602.
35 */
36 class BOOST_COROSIO_DECL win_resolver_service final
37 : private win_wsa_init
38 , public capy::execution_context::service
39 , public io_object::io_service
40 {
41 public:
42 using key_type = win_resolver_service;
43
44 io_object::implementation* construct() override;
45
46 32 void destroy(io_object::implementation* p) override
47 {
48 32 auto& impl = static_cast<win_resolver&>(*p);
49 32 impl.cancel();
50 32 destroy_impl(impl);
51 32 }
52
53 /** Construct the resolver service.
54
55 @param ctx Reference to the owning execution_context.
56 @param sched Reference to the scheduler for posting completions.
57 */
58 win_resolver_service(capy::execution_context& ctx, scheduler& sched);
59
60 /** Destroy the resolver service. */
61 ~win_resolver_service();
62
63 win_resolver_service(win_resolver_service const&) = delete;
64 win_resolver_service& operator=(win_resolver_service const&) = delete;
65
66 /** Shut down the service. */
67 void shutdown() override;
68
69 /** Destroy a resolver implementation. */
70 void destroy_impl(win_resolver& impl);
71
72 /** Post an operation for completion. */
73 void post(overlapped_op* op);
74
75 /** Notify scheduler of pending I/O work. */
76 void work_started() noexcept;
77
78 /** Notify scheduler that I/O work completed. */
79 void work_finished() noexcept;
80
81 /** Track worker thread start for safe shutdown. */
82 void thread_started() noexcept;
83
84 /** Track worker thread completion for safe shutdown. */
85 void thread_finished() noexcept;
86
87 /** Check if service is shutting down. */
88 bool is_shutting_down() const noexcept;
89
90 private:
91 scheduler& sched_;
92 win_mutex mutex_;
93 std::condition_variable_any cv_;
94 std::atomic<bool> shutting_down_{false};
95 std::size_t active_threads_ = 0;
96 intrusive_list<win_resolver> resolver_list_;
97 std::unordered_map<win_resolver*, std::shared_ptr<win_resolver>>
98 resolver_ptrs_;
99 };
100
101 namespace resolver_detail {
102
103 // Convert narrow string to wide string
104 inline std::wstring
105 34 to_wide(std::string_view s)
106 {
107
1/2
✗ Branch 3 → 4 not taken.
✓ Branch 3 → 5 taken 34 times.
34 if (s.empty())
108 return {};
109
110
1/1
✓ Branch 7 → 8 taken 34 times.
34 int len = ::MultiByteToWideChar(
111 34 CP_UTF8, 0, s.data(), static_cast<int>(s.size()), nullptr, 0);
112
113
1/2
✗ Branch 8 → 9 not taken.
✓ Branch 8 → 10 taken 34 times.
34 if (len <= 0)
114 return {};
115
116
1/1
✓ Branch 12 → 13 taken 34 times.
34 std::wstring result(static_cast<std::size_t>(len), L'\0');
117
1/1
✓ Branch 17 → 18 taken 34 times.
68 ::MultiByteToWideChar(
118 34 CP_UTF8, 0, s.data(), static_cast<int>(s.size()), result.data(), len);
119
120 34 return result;
121 34 }
122
123 // Convert resolve_flags to ADDRINFOEXW hints
124 inline int
125 17 flags_to_hints(resolve_flags flags)
126 {
127 17 int hints = 0;
128
129
1/2
✗ Branch 3 → 4 not taken.
✓ Branch 3 → 5 taken 17 times.
17 if ((flags & resolve_flags::passive) != resolve_flags::none)
130 hints |= AI_PASSIVE;
131
2/2
✓ Branch 6 → 7 taken 11 times.
✓ Branch 6 → 8 taken 6 times.
17 if ((flags & resolve_flags::numeric_host) != resolve_flags::none)
132 11 hints |= AI_NUMERICHOST;
133
2/2
✓ Branch 9 → 10 taken 8 times.
✓ Branch 9 → 11 taken 9 times.
17 if ((flags & resolve_flags::numeric_service) != resolve_flags::none)
134 8 hints |= AI_NUMERICSERV;
135
1/2
✗ Branch 12 → 13 not taken.
✓ Branch 12 → 14 taken 17 times.
17 if ((flags & resolve_flags::address_configured) != resolve_flags::none)
136 hints |= AI_ADDRCONFIG;
137
1/2
✗ Branch 15 → 16 not taken.
✓ Branch 15 → 17 taken 17 times.
17 if ((flags & resolve_flags::v4_mapped) != resolve_flags::none)
138 hints |= AI_V4MAPPED;
139
1/2
✗ Branch 18 → 19 not taken.
✓ Branch 18 → 20 taken 17 times.
17 if ((flags & resolve_flags::all_matching) != resolve_flags::none)
140 hints |= AI_ALL;
141
142 17 return hints;
143 }
144
145 // Convert reverse_flags to getnameinfo NI_* flags
146 inline int
147 10 flags_to_ni_flags(reverse_flags flags)
148 {
149 10 int ni_flags = 0;
150
151
2/2
✓ Branch 3 → 4 taken 5 times.
✓ Branch 3 → 5 taken 5 times.
10 if ((flags & reverse_flags::numeric_host) != reverse_flags::none)
152 5 ni_flags |= NI_NUMERICHOST;
153
2/2
✓ Branch 6 → 7 taken 5 times.
✓ Branch 6 → 8 taken 5 times.
10 if ((flags & reverse_flags::numeric_service) != reverse_flags::none)
154 5 ni_flags |= NI_NUMERICSERV;
155
2/2
✓ Branch 9 → 10 taken 1 time.
✓ Branch 9 → 11 taken 9 times.
10 if ((flags & reverse_flags::name_required) != reverse_flags::none)
156 1 ni_flags |= NI_NAMEREQD;
157
1/2
✗ Branch 12 → 13 not taken.
✓ Branch 12 → 14 taken 10 times.
10 if ((flags & reverse_flags::datagram_service) != reverse_flags::none)
158 ni_flags |= NI_DGRAM;
159
160 10 return ni_flags;
161 }
162
163 // Convert wide string to UTF-8 string
164 inline std::string
165 18 from_wide(std::wstring_view s)
166 {
167
1/2
✗ Branch 3 → 4 not taken.
✓ Branch 3 → 5 taken 18 times.
18 if (s.empty())
168 return {};
169
170
1/1
✓ Branch 7 → 8 taken 18 times.
18 int len = ::WideCharToMultiByte(
171 18 CP_UTF8, 0, s.data(), static_cast<int>(s.size()), nullptr, 0, nullptr,
172 nullptr);
173
174
1/2
✗ Branch 8 → 9 not taken.
✓ Branch 8 → 10 taken 18 times.
18 if (len <= 0)
175 return {};
176
177
1/1
✓ Branch 12 → 13 taken 18 times.
18 std::string result(static_cast<std::size_t>(len), '\0');
178
1/1
✓ Branch 17 → 18 taken 18 times.
36 ::WideCharToMultiByte(
179 18 CP_UTF8, 0, s.data(), static_cast<int>(s.size()), result.data(), len,
180 nullptr, nullptr);
181
182 18 return result;
183 18 }
184
185 // Convert ADDRINFOEXW results to resolver_results
186 inline resolver_results
187 14 convert_results(
188 ADDRINFOEXW* ai, std::string_view host, std::string_view service)
189 {
190 14 std::vector<resolver_entry> entries;
191
192
2/2
✓ Branch 12 → 3 taken 19 times.
✓ Branch 12 → 13 taken 14 times.
33 for (auto* p = ai; p != nullptr; p = p->ai_next)
193 {
194
2/2
✓ Branch 3 → 4 taken 12 times.
✓ Branch 3 → 7 taken 7 times.
19 if (p->ai_family == AF_INET)
195 {
196 12 auto* addr = reinterpret_cast<sockaddr_in*>(p->ai_addr);
197 12 auto ep = from_sockaddr_in(*addr);
198
1/1
✓ Branch 5 → 6 taken 12 times.
12 entries.emplace_back(ep, host, service);
199 }
200
1/2
✓ Branch 7 → 8 taken 7 times.
✗ Branch 7 → 11 not taken.
7 else if (p->ai_family == AF_INET6)
201 {
202 7 auto* addr = reinterpret_cast<sockaddr_in6*>(p->ai_addr);
203 7 auto ep = from_sockaddr_in6(*addr);
204
1/1
✓ Branch 9 → 10 taken 7 times.
7 entries.emplace_back(ep, host, service);
205 }
206 }
207
208
1/1
✓ Branch 15 → 16 taken 14 times.
28 return resolver_results(std::move(entries));
209 14 }
210
211 } // namespace resolver_detail
212
213 // ---------------------------------------------------------------------------
214 // Inline implementation
215 // ---------------------------------------------------------------------------
216
217 // resolve_op
218
219 inline void CALLBACK
220 6 resolve_op::completion(DWORD dwError, DWORD /*bytes*/, OVERLAPPED* ov)
221 {
222
1/2
✓ Branch 2 → 3 taken 6 times.
✗ Branch 2 → 4 not taken.
6 auto* op = static_cast<resolve_op*>(ov);
223 6 op->dwError = dwError;
224 6 op->impl->svc_.work_finished();
225 6 op->impl->svc_.post(op);
226 6 }
227
228 32 inline resolve_op::resolve_op() noexcept : overlapped_op(&do_complete) {}
229
230 inline void
231 17 resolve_op::do_complete(
232 void* owner,
233 scheduler_op* base,
234 std::uint32_t /*bytes*/,
235 std::uint32_t /*error*/)
236 {
237 17 auto* op = static_cast<resolve_op*>(base);
238
239
1/2
✗ Branch 2 → 3 not taken.
✓ Branch 2 → 8 taken 17 times.
17 if (!owner)
240 {
241 // Destroy path
242 op->stop_cb.reset();
243 if (op->results)
244 {
245 ::FreeAddrInfoExW(op->results);
246 op->results = nullptr;
247 }
248 op->cancel_handle = nullptr;
249 return;
250 }
251
252 17 op->stop_cb.reset();
253
254
1/2
✓ Branch 9 → 10 taken 17 times.
✗ Branch 9 → 18 not taken.
17 if (op->ec_out)
255 {
256
1/2
✗ Branch 11 → 12 not taken.
✓ Branch 11 → 14 taken 17 times.
17 if (op->cancelled.load(std::memory_order_acquire))
257 *op->ec_out = capy::error::canceled;
258
2/2
✓ Branch 14 → 15 taken 3 times.
✓ Branch 14 → 16 taken 14 times.
17 else if (op->dwError != 0)
259 3 *op->ec_out = make_err(op->dwError);
260 else
261 14 *op->ec_out = {};
262 }
263
264
1/2
✓ Branch 20 → 21 taken 17 times.
✗ Branch 20 → 24 not taken.
17 if (op->out && !op->cancelled.load(std::memory_order_acquire) &&
265
6/8
✓ Branch 18 → 19 taken 17 times.
✗ Branch 18 → 24 not taken.
✓ Branch 21 → 22 taken 14 times.
✓ Branch 21 → 24 taken 3 times.
✓ Branch 22 → 23 taken 14 times.
✗ Branch 22 → 24 not taken.
✓ Branch 25 → 26 taken 14 times.
✓ Branch 25 → 32 taken 3 times.
34 op->dwError == 0 && op->results)
266 {
267
1/1
✓ Branch 28 → 29 taken 14 times.
28 *op->out = resolver_detail::convert_results(
268 14 op->results, op->host, op->service);
269 }
270
271
2/2
✓ Branch 32 → 33 taken 14 times.
✓ Branch 32 → 35 taken 3 times.
17 if (op->results)
272 {
273 14 ::FreeAddrInfoExW(op->results);
274 14 op->results = nullptr;
275 }
276
277 17 op->cancel_handle = nullptr;
278
279
2/2
✓ Branch 35 → 36 taken 17 times.
✓ Branch 36 → 37 taken 17 times.
17 dispatch_coro(op->ex, op->h).resume();
280 }
281
282 // reverse_resolve_op
283
284 32 inline reverse_resolve_op::reverse_resolve_op() noexcept
285 32 : overlapped_op(&do_complete)
286 {
287 32 }
288
289 inline void
290 10 reverse_resolve_op::do_complete(
291 void* owner,
292 scheduler_op* base,
293 std::uint32_t /*bytes*/,
294 std::uint32_t /*error*/)
295 {
296 10 auto* op = static_cast<reverse_resolve_op*>(base);
297
298
1/2
✗ Branch 2 → 3 not taken.
✓ Branch 2 → 5 taken 10 times.
10 if (!owner)
299 {
300 op->stop_cb.reset();
301 return;
302 }
303
304 10 op->stop_cb.reset();
305
306
1/2
✓ Branch 6 → 7 taken 10 times.
✗ Branch 6 → 15 not taken.
10 if (op->ec_out)
307 {
308
1/2
✗ Branch 8 → 9 not taken.
✓ Branch 8 → 11 taken 10 times.
10 if (op->cancelled.load(std::memory_order_acquire))
309 *op->ec_out = capy::error::canceled;
310
2/2
✓ Branch 11 → 12 taken 1 time.
✓ Branch 11 → 13 taken 9 times.
10 else if (op->gai_error != 0)
311 1 *op->ec_out = make_err(static_cast<DWORD>(op->gai_error));
312 else
313 9 *op->ec_out = {};
314 }
315
316
4/6
✓ Branch 15 → 16 taken 10 times.
✗ Branch 15 → 20 not taken.
✓ Branch 17 → 18 taken 10 times.
✗ Branch 17 → 20 not taken.
✓ Branch 21 → 22 taken 9 times.
✓ Branch 21 → 32 taken 1 time.
20 if (op->result_out && !op->cancelled.load(std::memory_order_acquire) &&
317
2/2
✓ Branch 18 → 19 taken 9 times.
✓ Branch 18 → 20 taken 1 time.
10 op->gai_error == 0)
318 {
319 9 *op->result_out = reverse_resolver_result(
320 18 op->ep, std::move(op->stored_host), std::move(op->stored_service));
321 }
322
323
2/2
✓ Branch 32 → 33 taken 10 times.
✓ Branch 33 → 34 taken 10 times.
10 dispatch_coro(op->ex, op->h).resume();
324 }
325
326 // win_resolver
327
328 32 inline win_resolver::win_resolver(win_resolver_service& svc) noexcept
329 32 : svc_(svc)
330 {
331 32 }
332
333 inline std::coroutine_handle<>
334 17 win_resolver::resolve(
335 std::coroutine_handle<> h,
336 capy::executor_ref d,
337 std::string_view host,
338 std::string_view service,
339 resolve_flags flags,
340 std::stop_token token,
341 std::error_code* ec,
342 resolver_results* out)
343 {
344 17 auto& op = op_;
345 17 op.reset();
346 17 op.h = h;
347 17 op.ex = d;
348 17 op.ec_out = ec;
349 17 op.out = out;
350 17 op.impl = this;
351
1/1
✓ Branch 3 → 4 taken 17 times.
17 op.host = host;
352
1/1
✓ Branch 4 → 5 taken 17 times.
17 op.service = service;
353
1/1
✓ Branch 5 → 6 taken 17 times.
17 op.host_w = resolver_detail::to_wide(host);
354
1/1
✓ Branch 8 → 9 taken 17 times.
17 op.service_w = resolver_detail::to_wide(service);
355 17 op.start(token);
356
357 17 ADDRINFOEXW hints{};
358 17 hints.ai_family = AF_UNSPEC;
359 17 hints.ai_socktype = SOCK_STREAM;
360 17 hints.ai_flags = resolver_detail::flags_to_hints(flags);
361
362 // Keep io_context alive while resolution is pending
363 17 svc_.work_started();
364
365
3/5
✗ Branch 17 → 18 not taken.
✓ Branch 17 → 19 taken 17 times.
✗ Branch 21 → 22 not taken.
✓ Branch 21 → 23 taken 17 times.
✓ Branch 24 → 25 taken 17 times.
51 int result = ::GetAddrInfoExW(
366 34 op.host_w.empty() ? nullptr : op.host_w.c_str(),
367 34 op.service_w.empty() ? nullptr : op.service_w.c_str(), NS_DNS, nullptr,
368 &hints, &op.results, nullptr, &op, &resolve_op::completion,
369 &op.cancel_handle);
370
371
2/2
✓ Branch 25 → 26 taken 11 times.
✓ Branch 25 → 32 taken 6 times.
17 if (result != WSA_IO_PENDING)
372 {
373 // Completed synchronously - callback won't be invoked
374 11 svc_.work_finished();
375
376
2/2
✓ Branch 27 → 28 taken 9 times.
✓ Branch 27 → 29 taken 2 times.
11 if (result == 0)
377 {
378 // Completed synchronously
379 9 op.dwError = 0;
380 }
381 else
382 {
383
1/1
✓ Branch 29 → 30 taken 2 times.
2 op.dwError = static_cast<DWORD>(::WSAGetLastError());
384 }
385
386
1/1
✓ Branch 31 → 32 taken 11 times.
11 svc_.post(&op);
387 }
388 // completion is always posted to scheduler queue, never inline.
389 17 return std::noop_coroutine();
390 }
391
392 inline std::coroutine_handle<>
393 10 win_resolver::reverse_resolve(
394 std::coroutine_handle<> h,
395 capy::executor_ref d,
396 endpoint const& ep,
397 reverse_flags flags,
398 std::stop_token token,
399 std::error_code* ec,
400 reverse_resolver_result* result_out)
401 {
402 10 auto& op = reverse_op_;
403 10 op.reset();
404 10 op.h = h;
405 10 op.ex = d;
406 10 op.ec_out = ec;
407 10 op.result_out = result_out;
408 10 op.impl = this;
409 10 op.ep = ep;
410 10 op.flags = flags;
411 10 op.start(token);
412
413 // Keep io_context alive while resolution is pending
414 10 svc_.work_started();
415
416 // Track thread for safe shutdown
417 10 svc_.thread_started();
418
419 try
420 {
421 // Prevent impl destruction while worker thread is running
422
1/1
✓ Branch 8 → 9 taken 10 times.
10 auto self = this->shared_from_this();
423
424 // GetNameInfoW is synchronous, so we need to use a thread
425 20 std::thread worker([this, self = std::move(self)]() {
426 // Build sockaddr from endpoint
427 10 sockaddr_storage ss{};
428 int ss_len;
429
430
2/2
✓ Branch 3 → 4 taken 8 times.
✓ Branch 3 → 6 taken 2 times.
10 if (reverse_op_.ep.is_v4())
431 {
432 8 auto sa = to_sockaddr_in(reverse_op_.ep);
433 8 std::memcpy(&ss, &sa, sizeof(sa));
434 8 ss_len = sizeof(sockaddr_in);
435 }
436 else
437 {
438 2 auto sa = to_sockaddr_in6(reverse_op_.ep);
439 2 std::memcpy(&ss, &sa, sizeof(sa));
440 2 ss_len = sizeof(sockaddr_in6);
441 }
442
443 wchar_t host[NI_MAXHOST];
444 wchar_t service[NI_MAXSERV];
445
446
1/1
✓ Branch 9 → 10 taken 10 times.
10 int result = ::GetNameInfoW(
447 reinterpret_cast<sockaddr*>(&ss), ss_len, host, NI_MAXHOST,
448 service, NI_MAXSERV,
449 resolver_detail::flags_to_ni_flags(reverse_op_.flags));
450
451
1/2
✓ Branch 11 → 12 taken 10 times.
✗ Branch 11 → 23 not taken.
10 if (!reverse_op_.cancelled.load(std::memory_order_acquire))
452 {
453
2/2
✓ Branch 12 → 13 taken 9 times.
✓ Branch 12 → 22 taken 1 time.
10 if (result == 0)
454 {
455
1/1
✓ Branch 14 → 15 taken 9 times.
9 reverse_op_.stored_host = resolver_detail::from_wide(host);
456 9 reverse_op_.stored_service =
457
1/1
✓ Branch 18 → 19 taken 9 times.
9 resolver_detail::from_wide(service);
458 9 reverse_op_.gai_error = 0;
459 }
460 else
461 {
462 1 reverse_op_.gai_error = result;
463 }
464 }
465
466 // Always post so the scheduler can properly drain the op
467 // during shutdown via destroy().
468 10 svc_.work_finished();
469
1/1
✓ Branch 24 → 25 taken 10 times.
10 svc_.post(&reverse_op_);
470
471 // Signal thread completion for shutdown synchronization
472 10 svc_.thread_finished();
473
1/1
✓ Branch 11 → 12 taken 10 times.
20 });
474
1/1
✓ Branch 13 → 14 taken 10 times.
10 worker.detach();
475 10 }
476 catch (std::system_error const&)
477 {
478 // Thread creation failed - no thread was started
479 svc_.thread_finished();
480
481 // Set error and post completion to avoid hanging the coroutine
482 svc_.work_finished();
483 reverse_op_.gai_error = WSAENOBUFS; // Map to "not enough memory"
484 svc_.post(&reverse_op_);
485 }
486 // completion is always posted to scheduler queue, never inline.
487 10 return std::noop_coroutine();
488 }
489
490 inline void
491 36 win_resolver::cancel() noexcept
492 {
493 36 op_.request_cancel();
494 36 reverse_op_.request_cancel();
495
496
1/2
✗ Branch 4 → 5 not taken.
✓ Branch 4 → 6 taken 36 times.
36 if (op_.cancel_handle)
497 {
498 ::GetAddrInfoExCancel(&op_.cancel_handle);
499 }
500 36 }
501
502 // win_resolver_service
503
504 290 inline win_resolver_service::win_resolver_service(
505 290 capy::execution_context& ctx, scheduler& sched)
506
2/2
✓ Branch 5 → 6 taken 290 times.
✓ Branch 6 → 7 taken 290 times.
290 : sched_(sched)
507 {
508 (void)ctx;
509 290 }
510
511 580 inline win_resolver_service::~win_resolver_service() {}
512
513 inline void
514 290 win_resolver_service::shutdown()
515 {
516 {
517 290 std::lock_guard<win_mutex> lock(mutex_);
518
519 // Signal threads to not access service after GetNameInfoW returns
520 290 shutting_down_.store(true, std::memory_order_release);
521
522 // Cancel all resolvers (sets cancelled flag checked by threads)
523
1/2
✗ Branch 7 → 5 not taken.
✓ Branch 7 → 8 taken 290 times.
290 for (auto* impl = resolver_list_.pop_front(); impl != nullptr;
524 impl = resolver_list_.pop_front())
525 {
526 impl->cancel();
527 }
528
529 // Clear the map which releases shared_ptrs
530 // Note: impls may still be alive if worker threads hold references
531 290 resolver_ptrs_.clear();
532 290 }
533
534 // Wait for all worker threads to finish before service is destroyed
535 {
536
1/1
✓ Branch 10 → 11 taken 290 times.
290 std::unique_lock<win_mutex> lock(mutex_);
537
1/1
✓ Branch 11 → 12 taken 290 times.
580 cv_.wait(lock, [this] { return active_threads_ == 0; });
538 290 }
539 290 }
540
541 inline io_object::implementation*
542 32 win_resolver_service::construct()
543 {
544
1/1
✓ Branch 2 → 3 taken 32 times.
32 auto ptr = std::make_shared<win_resolver>(*this);
545 32 auto* impl = ptr.get();
546
547 {
548 32 std::lock_guard<win_mutex> lock(mutex_);
549 32 resolver_list_.push_back(impl);
550
1/1
✓ Branch 7 → 8 taken 32 times.
32 resolver_ptrs_[impl] = std::move(ptr);
551 32 }
552
553 32 return impl;
554 32 }
555
556 inline void
557 32 win_resolver_service::destroy_impl(win_resolver& impl)
558 {
559 32 std::lock_guard<win_mutex> lock(mutex_);
560 32 resolver_list_.remove(&impl);
561
1/1
✓ Branch 4 → 5 taken 32 times.
32 resolver_ptrs_.erase(&impl);
562 32 }
563
564 inline void
565 27 win_resolver_service::post(overlapped_op* op)
566 {
567 27 sched_.post(op);
568 27 }
569
570 inline void
571 27 win_resolver_service::work_started() noexcept
572 {
573 27 sched_.work_started();
574 27 }
575
576 inline void
577 27 win_resolver_service::work_finished() noexcept
578 {
579 27 sched_.work_finished();
580 27 }
581
582 inline void
583 10 win_resolver_service::thread_started() noexcept
584 {
585 10 std::lock_guard<win_mutex> lock(mutex_);
586 10 ++active_threads_;
587 10 }
588
589 inline void
590 10 win_resolver_service::thread_finished() noexcept
591 {
592 10 std::lock_guard<win_mutex> lock(mutex_);
593 10 --active_threads_;
594 10 cv_.notify_one();
595 10 }
596
597 inline bool
598 win_resolver_service::is_shutting_down() const noexcept
599 {
600 return shutting_down_.load(std::memory_order_acquire);
601 }
602
603 } // namespace boost::corosio::detail
604
605 #endif // BOOST_COROSIO_HAS_IOCP
606
607 #endif // BOOST_COROSIO_NATIVE_DETAIL_IOCP_WIN_RESOLVER_SERVICE_HPP
608