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

99.1% Lines (229 / 231) 85.0% Branches (85 / 100) 100.0% Functions (32 / 32)
win_overlapped_handle.hpp
f(x) Functions (32)
Function Calls Lines Branches Blocks
boost::corosio::detail::normalize_handle_error(unsigned long, bool) :71 585x 100.0% 100.0% 100.0% boost::corosio::detail::win_handle_base::win_handle_base(boost::corosio::detail::win_scheduler&, boost::corosio::io_object::implementation&) :101 491x 100.0% – 100.0% boost::corosio::detail::win_handle_base::native_handle() const :111 2348x 100.0% – 100.0% boost::corosio::detail::win_handle_base::is_open() const :116 104x 100.0% – 100.0% boost::corosio::detail::win_handle_base::close_handle() :122 1620x 100.0% 100.0% 100.0% boost::corosio::detail::win_handle_base::check_and_register(void*, boost::corosio::detail::handle_kind) :137 273x 90.0% 83.3% 92.3% boost::corosio::detail::win_handle_base::detach_or_error(bool) :163 21x 100.0% 100.0% 78.6% boost::corosio::detail::win_handle_base::start_io(boost::corosio::detail::handle_io_op&, unsigned long long, boost::corosio::detail::buffer_param) :183 586x 96.0% 87.5% 96.8% boost::corosio::detail::handle_io_op::handle_io_op(void (*)(void*, boost::corosio::detail::scheduler_op*, unsigned int, unsigned int)) :241 950x 100.0% – 100.0% boost::corosio::detail::handle_io_op::do_cancel_impl(boost::corosio::detail::overlapped_op*) :247 103x 100.0% 50.0% 84.6% boost::corosio::detail::win_slot_handle::win_slot_handle(boost::corosio::detail::win_scheduler&, boost::corosio::io_object::implementation&, bool) :259 349x 100.0% – 100.0% boost::corosio::detail::win_slot_handle::read_some(boost::capy::continuation&, boost::capy::executor_ref, boost::corosio::detail::buffer_param, std::stop_token, std::error_code*, unsigned long long*) :268 270x 100.0% – 100.0% boost::corosio::detail::win_slot_handle::write_some(boost::capy::continuation&, boost::capy::executor_ref, boost::corosio::detail::buffer_param, std::stop_token, std::error_code*, unsigned long long*) :280 47x 100.0% – 100.0% boost::corosio::detail::win_slot_handle::cancel() :292 2x 100.0% 50.0% 100.0% boost::corosio::detail::win_slot_handle::assign(unsigned long long, boost::corosio::detail::handle_kind) :300 241x 100.0% 100.0% 67.9% boost::corosio::detail::win_slot_handle::release() :311 12x 100.0% 83.3% 78.6% boost::corosio::detail::win_slot_handle::close_handle() :329 1219x 100.0% – 100.0% boost::corosio::detail::win_slot_handle::slot_op::slot_op() :341 698x 100.0% – 100.0% boost::corosio::detail::win_slot_handle::slot_op::do_complete(void*, boost::corosio::detail::scheduler_op*, unsigned int, unsigned int) :343 317x 100.0% 100.0% 79.5% boost::corosio::detail::win_slot_handle::start(boost::corosio::detail::win_slot_handle::slot_op&, bool, boost::capy::continuation&, boost::capy::executor_ref, boost::corosio::detail::buffer_param, std::stop_token, std::error_code*, unsigned long long*) :373 317x 100.0% 100.0% 73.3% boost::corosio::detail::win_concurrent_handle::~win_concurrent_handle() :418 142x 100.0% 75.0% 100.0% boost::corosio::detail::win_concurrent_handle::read_some_at(unsigned long long, boost::capy::continuation&, boost::capy::executor_ref, boost::corosio::detail::buffer_param, std::stop_token, std::error_code*, unsigned long long*) :424 189x 100.0% 100.0% 80.0% boost::corosio::detail::win_concurrent_handle::write_some_at(unsigned long long, boost::capy::continuation&, boost::capy::executor_ref, boost::corosio::detail::buffer_param, std::stop_token, std::error_code*, unsigned long long*) :437 80x 100.0% 100.0% 80.0% boost::corosio::detail::win_concurrent_handle::cancel() :450 3x 100.0% 50.0% 100.0% boost::corosio::detail::win_concurrent_handle::assign(unsigned long long, boost::corosio::detail::handle_kind) :457 32x 100.0% 100.0% 100.0% boost::corosio::detail::win_concurrent_handle::release() :466 9x 100.0% 100.0% 100.0% boost::corosio::detail::win_concurrent_handle::concurrent_op::concurrent_op() :486 252x 100.0% – 100.0% boost::corosio::detail::win_concurrent_handle::concurrent_op::do_complete(void*, boost::corosio::detail::scheduler_op*, unsigned int, unsigned int) :489 269x 100.0% 72.7% 88.2% boost::corosio::detail::win_concurrent_handle::recycle_op(boost::corosio::detail::win_concurrent_handle::concurrent_op*) :529 269x 100.0% – 100.0% boost::corosio::detail::win_concurrent_handle::request_cancel_all() :536 12x 100.0% – 100.0% boost::corosio::detail::win_concurrent_handle::request_cancel_all()::{lambda(boost::corosio::detail::win_concurrent_handle::concurrent_op*)#1}::operator()(boost::corosio::detail::win_concurrent_handle::concurrent_op*) const :540 4x 100.0% – 100.0% boost::corosio::detail::win_concurrent_handle::start(bool, unsigned long long, boost::capy::continuation&, boost::capy::executor_ref, boost::corosio::detail::buffer_param, std::stop_token, std::error_code*, unsigned long long*) :543 269x 100.0% 100.0% 100.0%
Line Branch TLA Hits Source Code
1 //
2 // Copyright (c) 2026 Michael Vandeberg
3 //
4 // Distributed under the Boost Software License, Version 1.0. (See accompanying
5 // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
6 //
7 // Official repository: https://github.com/cppalliance/corosio
8 //
9
10 #ifndef BOOST_COROSIO_NATIVE_DETAIL_IOCP_WIN_OVERLAPPED_HANDLE_HPP
11 #define BOOST_COROSIO_NATIVE_DETAIL_IOCP_WIN_OVERLAPPED_HANDLE_HPP
12
13 #include <boost/corosio/detail/platform.hpp>
14
15 #if BOOST_COROSIO_HAS_IOCP
16
17 #include <boost/corosio/detail/buffer_param.hpp>
18 #include <boost/corosio/detail/dispatch_coro.hpp>
19 #include <boost/corosio/detail/intrusive.hpp>
20 #include <boost/corosio/detail/native_handle.hpp>
21 #include <boost/corosio/detail/object_ref.hpp>
22 #include <boost/corosio/io/io_object.hpp>
23 #include <boost/corosio/native/detail/coro_op_complete.hpp>
24 #include <boost/corosio/native/detail/iocp/win_completion_key.hpp>
25 #include <boost/corosio/native/detail/iocp/win_dissociate.hpp>
26 #include <boost/corosio/native/detail/iocp/win_mutex.hpp>
27 #include <boost/corosio/native/detail/iocp/win_overlapped_op.hpp>
28 #include <boost/corosio/native/detail/iocp/win_scheduler.hpp>
29 #include <boost/corosio/native/detail/iocp/win_validate_handle.hpp>
30 #include <boost/capy/buffers.hpp>
31 #include <boost/capy/continuation.hpp>
32 #include <boost/capy/ex/executor_ref.hpp>
33 #include <boost/capy/results.hpp>
34
35 #include <algorithm>
36 #include <atomic>
37 #include <coroutine>
38 #include <cstdint>
39 #include <mutex>
40 #include <stop_token>
41 #include <system_error>
42 #include <utility>
43
44 /* The overlapped-handle core shared by stream_file, random_access_file,
45 win_stream_handle and win_random_access_handle.
46
47 Two op models sit on one handle base. The slot model embeds one read
48 and one write op (one of each in flight) and either tracks a file
49 position or submits at offset 0. The concurrent model takes an op
50 per call from a per-handle free list, so any number of positional
51 ops can be in flight and steady state allocates nothing.
52
53 Each front embeds its handle core in the pooled implementation the
54 service recycles. Ops hold a reference on that implementation until
55 their last packet drains, which can outlive the I/O object.
56 */
57
58 namespace boost::corosio::detail {
59
60 /** Map a handle completion code to what decode_results expects.
61
62 On reads, end of data (`ERROR_HANDLE_EOF`, `ERROR_BROKEN_PIPE`
63 once a pipe's writer is gone, or `ERROR_PIPE_NOT_CONNECTED` after
64 the server disconnects) becomes zero, so a zero-byte read
65 decodes as eof, and `ERROR_MORE_DATA` (a partial message on a
66 message-mode pipe) becomes a successful partial read. Socket ops
67 never pass through here: on a datagram socket `ERROR_MORE_DATA`
68 means truncation, which is an error.
69 */
70 inline DWORD
71 585x normalize_handle_error(DWORD err, bool is_read) noexcept
72 {
73
4/4
✓ Branch 2 → 3 taken 457 times.
✓ Branch 2 → 8 taken 128 times.
✓ Branch 3 → 4 taken 451 times.
✓ Branch 3 → 7 taken 6 times.
585x if (is_read &&
74
4/4
✓ Branch 4 → 5 taken 448 times.
✓ Branch 4 → 7 taken 3 times.
✓ Branch 5 → 6 taken 446 times.
✓ Branch 5 → 7 taken 2 times.
451x (err == ERROR_HANDLE_EOF || err == ERROR_BROKEN_PIPE ||
75
2/2
✓ Branch 6 → 7 taken 3 times.
✓ Branch 6 → 8 taken 443 times.
446x err == ERROR_PIPE_NOT_CONNECTED || err == ERROR_MORE_DATA))
76 14x return 0;
77 571x return err;
78 }
79
80 class win_handle_base;
81
82 /// An overlapped read or write on an adopted handle.
83 struct handle_io_op : overlapped_op
84 {
85 void* buf = nullptr;
86 DWORD buf_len = 0;
87 win_handle_base* owner = nullptr;
88
89 explicit handle_io_op(func_type f) noexcept;
90 static void do_cancel_impl(overlapped_op* base) noexcept;
91 };
92
93 /** State shared by every overlapped handle front.
94
95 Embedded in the implementation named by `io`, whose reference the
96 in-flight ops hold.
97 */
98 class win_handle_base
99 {
100 public:
101 491x win_handle_base(
102 win_scheduler& sched, io_object::implementation& io) noexcept
103 491x : sched_(sched)
104 491x , io_(io)
105 {
106 491x }
107
108 win_handle_base(win_handle_base const&) = delete;
109 win_handle_base& operator=(win_handle_base const&) = delete;
110
111 2348x HANDLE native_handle() const noexcept
112 {
113 2348x return handle_;
114 }
115
116 104x bool is_open() const noexcept
117 {
118 104x return handle_ != INVALID_HANDLE_VALUE;
119 }
120
121 /// Close the handle; pending ops complete with ERROR_OPERATION_ABORTED.
122 1620x void close_handle() noexcept
123 {
124
2/2
✓ Branch 2 → 3 taken 635 times.
✓ Branch 2 → 6 taken 985 times.
1620x if (handle_ != INVALID_HANDLE_VALUE)
125 {
126 635x ::CancelIoEx(handle_, nullptr);
127 635x ::CloseHandle(handle_);
128 635x handle_ = INVALID_HANDLE_VALUE;
129 }
130 1620x }
131
132 protected:
133 /** Validate @p h and register it with this context's port.
134
135 The object is closed; the public `assign()` rejects an open one.
136 */
137 273x std::error_code check_and_register(HANDLE h, handle_kind kind) noexcept
138 {
139
2/2
✓ Branch 4 → 5 taken 15 times.
✓ Branch 4 → 6 taken 258 times.
273x if (auto ec = validate_overlapped_handle(h, kind))
140 15x return ec;
141
2/2
✓ Branch 8 → 9 taken 3 times.
✓ Branch 8 → 13 taken 255 times.
258x if (!::CreateIoCompletionPort(
142 258x h, static_cast<HANDLE>(sched_.native_handle()), key_io, 0))
143 {
144 3x DWORD const err = ::GetLastError();
145 // Already bound to another port. Mapped here because the
146 // system_category -> errc mapping of 87 differs by toolchain.
147
1/2
✓ Branch 10 → 11 taken 3 times.
✗ Branch 10 → 12 not taken.
3x if (err == ERROR_INVALID_PARAMETER)
148 3x return std::make_error_code(std::errc::invalid_argument);
149 ✗ return iocp_make_err(err, /*accept_path=*/false);
150 }
151 255x return {};
152 }
153
154 /** Unbind the handle from the port and give it up.
155
156 Called after the ops were cancelled. Returns an error, leaving
157 the object holding the handle, when an op is still in flight or
158 Windows refuses the detach: detaching under a queued completion
159 would lose it or deliver it to the next adopter's port.
160
161 @param idle True when no op of this handle is in flight.
162 */
163 21x capy::results<HANDLE> detach_or_error(bool idle) noexcept
164 {
165
2/2
✓ Branch 2 → 3 taken 5 times.
✓ Branch 2 → 6 taken 16 times.
21x if (!idle)
166 return {
167 5x std::make_error_code(std::errc::device_or_resource_busy),
168 5x INVALID_HANDLE_VALUE};
169
2/2
✓ Branch 7 → 8 taken 1 time.
✓ Branch 7 → 11 taken 15 times.
16x if (!dissociate_from_iocp(handle_))
170 return {
171 1x std::make_error_code(std::errc::operation_not_supported),
172 1x INVALID_HANDLE_VALUE};
173 return {
174 15x std::error_code{}, std::exchange(handle_, INVALID_HANDLE_VALUE)};
175 }
176
177 /** Issue ReadFile/WriteFile for an op the caller has set up.
178
179 The caller has filled `h`, `ex`, `ec_out`, `bytes_out`,
180 `is_read`, `owner` and `object_ref_`, called `start(token)` and
181 `work_started()`.
182 */
183 586x void start_io(
184 handle_io_op& op, std::uint64_t offset, buffer_param param) noexcept
185 {
186 static constexpr std::size_t max_buffers = 16;
187
188 // Closed-object contract: bad_file_descriptor without the kernel.
189
2/2
✓ Branch 2 → 3 taken 9 times.
✓ Branch 2 → 5 taken 577 times.
586x if (handle_ == INVALID_HANDLE_VALUE)
190 {
191 9x sched_.on_completion(&op, ERROR_INVALID_HANDLE, 0);
192 27x return;
193 }
194
195 577x capy::mutable_buffer bufs[max_buffers];
196 577x auto const count = param.copy_to(bufs, max_buffers);
197
2/2
✓ Branch 6 → 7 taken 6 times.
✓ Branch 6 → 9 taken 571 times.
577x if (count == 0)
198 {
199 6x op.empty_buffer = true;
200 6x sched_.on_completion(&op, 0, 0);
201 6x return;
202 }
203
204 // ReadFile/WriteFile take one contiguous buffer.
205 571x op.buf = bufs[0].data();
206 571x op.buf_len = static_cast<DWORD>(
207 571x (std::min)(bufs[0].size(), std::size_t(0x7fffffff)));
208 571x op.Offset = static_cast<DWORD>(offset & 0xFFFFFFFF);
209 571x op.OffsetHigh = static_cast<DWORD>(offset >> 32);
210
211 571x BOOL const ok = op.is_read
212
2/2
✓ Branch 12 → 13 taken 451 times.
✓ Branch 12 → 15 taken 120 times.
571x ? ::ReadFile(handle_, op.buf, op.buf_len, nullptr, &op)
213 120x : ::WriteFile(handle_, op.buf, op.buf_len, nullptr, &op);
214
2/2
✓ Branch 17 → 18 taken 457 times.
✓ Branch 17 → 20 taken 114 times.
571x DWORD const err = ok ? 0 : ::GetLastError();
215
216 // ERROR_MORE_DATA completed a partial message and still queues
217 // a packet, so it joins the pending path.
218
5/6
✓ Branch 21 → 22 taken 457 times.
✓ Branch 21 → 26 taken 114 times.
✓ Branch 22 → 23 taken 12 times.
✓ Branch 22 → 26 taken 445 times.
✓ Branch 23 → 24 taken 12 times.
✗ Branch 23 → 26 not taken.
571x if (err != 0 && err != ERROR_IO_PENDING && err != ERROR_MORE_DATA)
219 {
220 12x sched_.on_completion(&op, err, 0);
221 12x return;
222 }
223
224 // Re-check cancellation now that the kernel I/O is issued. This
225 // must precede on_pending: once it runs, another thread may
226 // dispatch (and for concurrent ops delete) the op, so neither the
227 // op nor this may be touched afterwards. Dispatch cannot happen
228 // before on_pending, and a cancel arriving after this check
229 // reaches the issued I/O through do_cancel_impl's own CancelIoEx.
230
1/2
✗ Branch 27 → 28 not taken.
✓ Branch 27 → 29 taken 559 times.
559x if (op.cancelled.load(std::memory_order_acquire))
231 ✗ ::CancelIoEx(handle_, &op);
232
233 559x sched_.on_pending(&op);
234 }
235
236 win_scheduler& sched_;
237 io_object::implementation& io_;
238 HANDLE handle_ = INVALID_HANDLE_VALUE;
239 };
240
241 950x inline handle_io_op::handle_io_op(func_type f) noexcept : overlapped_op(f)
242 {
243 950x cancel_func_ = &do_cancel_impl;
244 950x }
245
246 inline void
247 103x handle_io_op::do_cancel_impl(overlapped_op* base) noexcept
248 {
249 103x auto* op = static_cast<handle_io_op*>(base);
250 103x op->cancelled.store(true, std::memory_order_release);
251
3/6
✓ Branch 3 → 4 taken 103 times.
✗ Branch 3 → 7 not taken.
✓ Branch 5 → 6 taken 103 times.
✗ Branch 5 → 7 not taken.
✓ Branch 8 → 9 taken 103 times.
✗ Branch 8 → 14 not taken.
103x if (op->owner && op->owner->is_open())
252
1/2
✓ Branch 9 → 10 taken 103 times.
✗ Branch 9 → 11 not taken.
103x ::CancelIoEx(op->owner->native_handle(), op);
253 103x }
254
255 /** One read and one write in flight: stream_file, win_stream_handle. */
256 class win_slot_handle : public win_handle_base
257 {
258 public:
259 349x win_slot_handle(
260 win_scheduler& sched,
261 io_object::implementation& io,
262 bool track_offset) noexcept
263 349x : win_handle_base(sched, io)
264 349x , track_offset_(track_offset)
265 {
266 349x }
267
268 270x std::coroutine_handle<> read_some(
269 capy::continuation& cont,
270 capy::executor_ref ex,
271 buffer_param param,
272 std::stop_token token,
273 std::error_code* ec,
274 std::size_t* bytes_out)
275 {
276 540x return start(
277 540x rd_, true, cont, ex, param, std::move(token), ec, bytes_out);
278 }
279
280 47x std::coroutine_handle<> write_some(
281 capy::continuation& cont,
282 capy::executor_ref ex,
283 buffer_param param,
284 std::stop_token token,
285 std::error_code* ec,
286 std::size_t* bytes_out)
287 {
288 94x return start(
289 94x wr_, false, cont, ex, param, std::move(token), ec, bytes_out);
290 }
291
292 2x void cancel() noexcept
293 {
294
1/2
✓ Branch 2 → 3 taken 2 times.
✗ Branch 2 → 4 not taken.
2x if (handle_ != INVALID_HANDLE_VALUE)
295 2x ::CancelIoEx(handle_, nullptr);
296 2x rd_.request_cancel();
297 2x wr_.request_cancel();
298 2x }
299
300 241x std::error_code assign(native_handle_type nh, handle_kind kind) noexcept
301 {
302 241x HANDLE h = reinterpret_cast<HANDLE>(nh);
303
2/2
✓ Branch 4 → 5 taken 10 times.
✓ Branch 4 → 6 taken 231 times.
241x if (auto ec = check_and_register(h, kind))
304 10x return ec;
305 231x generation_.fetch_add(1, std::memory_order_acq_rel);
306 231x handle_ = h;
307 231x offset_.store(0, std::memory_order_release);
308 231x return {};
309 }
310
311 12x capy::results<native_handle_type> release() noexcept
312 {
313 // A release that fails still aborts the in-flight I/O, and a
314 // retry once it drains succeeds.
315 12x rd_.request_cancel();
316 12x wr_.request_cancel();
317 12x ::CancelIoEx(handle_, nullptr);
318
2/2
✓ Branch 6 → 7 taken 9 times.
✓ Branch 6 → 10 taken 3 times.
21x bool const idle = !rd_.in_flight.load(std::memory_order_acquire) &&
319
1/2
✓ Branch 8 → 9 taken 9 times.
✗ Branch 8 → 10 not taken.
9x !wr_.in_flight.load(std::memory_order_acquire);
320 12x auto [ec, h] = detach_or_error(idle);
321
2/2
✓ Branch 15 → 16 taken 4 times.
✓ Branch 15 → 17 taken 8 times.
12x if (ec)
322 4x return {ec, invalid_native_handle};
323 8x generation_.fetch_add(1, std::memory_order_acq_rel);
324 8x offset_.store(0, std::memory_order_release);
325 8x return {std::error_code{}, reinterpret_cast<native_handle_type>(h)};
326 }
327
328 /// Close the handle; completions still queued no longer move the position.
329 1219x void close_handle() noexcept
330 {
331 1219x generation_.fetch_add(1, std::memory_order_acq_rel);
332 1219x win_handle_base::close_handle();
333 1219x }
334
335 protected:
336 struct slot_op : handle_io_op
337 {
338 std::atomic<bool> in_flight{false};
339 std::uint32_t generation = 0;
340
341 698x slot_op() noexcept : handle_io_op(&do_complete) {}
342
343 317x static void do_complete(
344 void* owner,
345 scheduler_op* base,
346 std::uint32_t /*bytes*/,
347 std::uint32_t /*error*/)
348 {
349 317x auto* op = static_cast<slot_op*>(base);
350 317x op->in_flight.store(false, std::memory_order_release);
351
2/2
✓ Branch 3 → 4 taken 4 times.
✓ Branch 3 → 6 taken 313 times.
317x if (!owner)
352 {
353 4x op->destroy_awaiting();
354 4x return;
355 }
356
357 313x auto* self = static_cast<win_slot_handle*>(op->owner);
358 // A completion from before assign()/release() belongs to the
359 // old handle; its byte count must not move the new position.
360
2/2
✓ Branch 7 → 8 taken 275 times.
✓ Branch 7 → 26 taken 13 times.
288x if (self->track_offset_ && op->dwError == 0 &&
361
6/6
✓ Branch 6 → 7 taken 288 times.
✓ Branch 6 → 26 taken 25 times.
✓ Branch 8 → 9 taken 272 times.
✓ Branch 8 → 26 taken 3 times.
✓ Branch 27 → 28 taken 71 times.
✓ Branch 27 → 31 taken 242 times.
873x op->bytes_transferred > 0 &&
362 272x self->generation_.load(std::memory_order_acquire) ==
363
2/2
✓ Branch 24 → 25 taken 71 times.
✓ Branch 24 → 26 taken 201 times.
272x op->generation)
364 71x self->offset_.fetch_add(
365 71x op->bytes_transferred, std::memory_order_acq_rel);
366
367 313x op->dwError = normalize_handle_error(op->dwError, op->is_read);
368 313x auto prevent_premature_destruction = std::move(op->object_ref_);
369
1/1
✓ Branch 34 → 35 taken 313 times.
313x op->invoke_handler();
370 313x }
371 };
372
373 317x std::coroutine_handle<> start(
374 slot_op& op,
375 bool is_read,
376 capy::continuation& cont,
377 capy::executor_ref ex,
378 buffer_param param,
379 std::stop_token token,
380 std::error_code* ec,
381 std::size_t* bytes_out)
382 {
383 317x op.object_ref_ = detail::object_ref(&io_);
384 317x op.reset();
385 317x op.owner = this;
386 317x op.is_read = is_read;
387 317x op.cont = &cont;
388 317x op.ex = ex;
389 317x op.ec_out = ec;
390 317x op.bytes_out = bytes_out;
391 317x op.start(token);
392
393 317x sched_.work_started();
394 317x op.generation = generation_.load(std::memory_order_acquire);
395 317x op.in_flight.store(true, std::memory_order_release);
396 317x start_io(
397
2/2
✓ Branch 24 → 25 taken 288 times.
✓ Branch 24 → 41 taken 29 times.
605x op, track_offset_ ? offset_.load(std::memory_order_acquire) : 0,
398 param);
399 317x return std::noop_coroutine();
400 }
401
402 slot_op rd_;
403 slot_op wr_;
404 std::atomic<std::uint64_t> offset_{0};
405 // Bumped by assign(), release() and close; tags each op.
406 std::atomic<std::uint32_t> generation_{0};
407 bool track_offset_;
408 };
409
410 /** Any number of positional ops: random_access_file, win_random_access_handle. */
411 class win_concurrent_handle : public win_handle_base
412 {
413 public:
414 using win_handle_base::win_handle_base;
415
416 // The recycled ops belong to the implementation's storage, which
417 // survives recycling; only its destruction frees them.
418 390x ~win_concurrent_handle()
419 {
420
4/4
✓ Branch 3 → 4 taken 248 times.
✓ Branch 3 → 7 taken 137 times.
✓ Branch 4 → 5 taken 4 times.
✓ Branch 4 → 8 taken 5 times.
394x while (auto* op = free_ops_.pop_front())
421
2/4
✓ Branch 4 → 5 taken 248 times.
✗ Branch 4 → 6 not taken.
✓ Branch 5 → 6 taken 4 times.
✗ Branch 5 → 7 not taken.
252x delete op;
422 142x }
423
424 189x std::coroutine_handle<> read_some_at(
425 std::uint64_t offset,
426 capy::continuation& cont,
427 capy::executor_ref ex,
428 buffer_param param,
429 std::stop_token token,
430 std::error_code* ec,
431 std::size_t* bytes_out)
432 {
433
1/1
✓ Branch 4 → 5 taken 189 times.
378x return start(
434 567x true, offset, cont, ex, param, std::move(token), ec, bytes_out);
435 }
436
437 80x std::coroutine_handle<> write_some_at(
438 std::uint64_t offset,
439 capy::continuation& cont,
440 capy::executor_ref ex,
441 buffer_param param,
442 std::stop_token token,
443 std::error_code* ec,
444 std::size_t* bytes_out)
445 {
446
1/1
✓ Branch 4 → 5 taken 80 times.
160x return start(
447 240x false, offset, cont, ex, param, std::move(token), ec, bytes_out);
448 }
449
450 3x void cancel() noexcept
451 {
452
1/2
✓ Branch 2 → 3 taken 3 times.
✗ Branch 2 → 4 not taken.
3x if (handle_ != INVALID_HANDLE_VALUE)
453 3x ::CancelIoEx(handle_, nullptr);
454 3x request_cancel_all();
455 3x }
456
457 32x std::error_code assign(native_handle_type nh, handle_kind kind) noexcept
458 {
459 32x HANDLE h = reinterpret_cast<HANDLE>(nh);
460
2/2
✓ Branch 4 → 5 taken 8 times.
✓ Branch 4 → 6 taken 24 times.
32x if (auto ec = check_and_register(h, kind))
461 8x return ec;
462 24x handle_ = h;
463 24x return {};
464 }
465
466 9x capy::results<native_handle_type> release() noexcept
467 {
468 9x request_cancel_all();
469 9x ::CancelIoEx(handle_, nullptr);
470 bool idle;
471 {
472 9x std::lock_guard<win_mutex> lock(ops_mutex_);
473 9x idle = outstanding_ops_.empty();
474 9x }
475 9x auto [ec, h] = detach_or_error(idle);
476
2/2
✓ Branch 11 → 12 taken 2 times.
✓ Branch 11 → 13 taken 7 times.
9x if (ec)
477 2x return {ec, invalid_native_handle};
478 7x return {std::error_code{}, reinterpret_cast<native_handle_type>(h)};
479 }
480
481 protected:
482 struct concurrent_op
483 : handle_io_op
484 , intrusive_list<concurrent_op>::node
485 {
486 252x concurrent_op() noexcept : handle_io_op(&do_complete) {}
487
488 // The continuation lives in the awaitable, so the op can go first.
489 269x static void do_complete(
490 void* owner,
491 scheduler_op* base,
492 std::uint32_t /*bytes*/,
493 std::uint32_t /*error*/)
494 {
495 269x auto* op = static_cast<concurrent_op*>(base);
496 269x auto* self = static_cast<win_concurrent_handle*>(op->owner);
497 // Dropped after the op is back on the free list, so a final
498 // release never runs under ops_mutex_.
499 269x auto keep = std::move(op->object_ref_);
500 269x op->stop_cb.reset();
501
502
2/2
✓ Branch 5 → 6 taken 4 times.
✓ Branch 5 → 16 taken 265 times.
269x if (!owner)
503 {
504 4x auto* c = std::exchange(op->cont, nullptr);
505 4x self->recycle_op(op);
506
3/6
✓ Branch 8 → 9 taken 4 times.
✗ Branch 8 → 12 not taken.
✓ Branch 10 → 11 taken 4 times.
✗ Branch 10 → 12 not taken.
✓ Branch 13 → 14 taken 4 times.
✗ Branch 13 → 15 not taken.
4x if (c && c->h)
507 4x capy::abandon(*c);
508 4x return;
509 }
510
511 265x DWORD const err = normalize_handle_error(op->dwError, op->is_read);
512 517x decode_results(
513 op->ec_out, op->bytes_out,
514 265x op->cancelled.load(std::memory_order_acquire),
515 13x err != 0 ? iocp_make_err(err, /*accept_path=*/false)
516 : std::error_code{},
517 265x op->is_read, static_cast<std::size_t>(op->bytes_transferred),
518
2/2
✓ Branch 17 → 18 taken 13 times.
✓ Branch 17 → 19 taken 252 times.
265x op->empty_buffer);
519
520 265x capy::continuation* c = op->cont;
521 265x capy::executor_ref ex = op->ex;
522 // Once recycled, a concurrent initiation may refill the op.
523 265x self->recycle_op(op);
524
1/1
✓ Branch 24 → 25 taken 265 times.
265x dispatch_coro(ex, *c).resume();
525 269x }
526 };
527
528 /// Move a finished op from `outstanding_ops_` to `free_ops_`.
529 269x void recycle_op(concurrent_op* op) noexcept
530 {
531 269x std::lock_guard<win_mutex> lock(ops_mutex_);
532 269x outstanding_ops_.remove(op);
533 269x free_ops_.push_front(op);
534 269x }
535
536 12x void request_cancel_all() noexcept
537 {
538 12x std::lock_guard<win_mutex> lock(ops_mutex_);
539 12x outstanding_ops_.for_each(
540 4x [](concurrent_op* op) { op->request_cancel(); });
541 12x }
542
543 269x std::coroutine_handle<> start(
544 bool is_read,
545 std::uint64_t offset,
546 capy::continuation& cont,
547 capy::executor_ref ex,
548 buffer_param param,
549 std::stop_token token,
550 std::error_code* ec,
551 std::size_t* bytes_out)
552 {
553 concurrent_op* op;
554 {
555 269x std::lock_guard<win_mutex> lock(ops_mutex_);
556 269x op = free_ops_.pop_front();
557 269x }
558
2/2
✓ Branch 5 → 6 taken 252 times.
✓ Branch 5 → 9 taken 17 times.
269x if (!op)
559 252x op = new concurrent_op();
560 269x op->object_ref_ = detail::object_ref(&io_);
561 269x op->reset();
562 269x op->owner = this;
563 269x op->is_read = is_read;
564 269x op->cont = &cont;
565 269x op->ex = ex;
566 269x op->ec_out = ec;
567 269x op->bytes_out = bytes_out;
568 269x op->start(token);
569
570 269x sched_.work_started();
571 {
572 269x std::lock_guard<win_mutex> lock(ops_mutex_);
573 269x outstanding_ops_.push_back(op);
574 269x }
575 269x start_io(*op, offset, param);
576 269x return std::noop_coroutine();
577 }
578
579 win_mutex ops_mutex_;
580 intrusive_list<concurrent_op> outstanding_ops_;
581 intrusive_list<concurrent_op> free_ops_;
582 };
583
584 } // namespace boost::corosio::detail
585
586 #endif // BOOST_COROSIO_HAS_IOCP
587
588 #endif
589