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

99.2% Lines (259/261) 100.0% List of functions (67/67) 85.3% Branches (87/102)
win_overlapped_handle.hpp
f(x) Functions (67)
Function Calls Lines Branches Blocks
boost::corosio::detail::normalize_handle_error(unsigned long, bool) :70 583x 100.0% 100.0% 100.0% boost::corosio::detail::win_state_list<boost::corosio::detail::win_handle_base>::add(boost::corosio::detail::win_handle_base&) :84 490x 100.0% – 100.0% boost::corosio::detail::win_state_list<boost::corosio::detail::win_object_handle_state>::add(boost::corosio::detail::win_object_handle_state&) :84 2824x 100.0% – 100.0% boost::corosio::detail::win_state_list<boost::corosio::detail::win_handle_base>::remove(boost::corosio::detail::win_handle_base&) :90 490x 100.0% – 100.0% boost::corosio::detail::win_state_list<boost::corosio::detail::win_object_handle_state>::remove(boost::corosio::detail::win_object_handle_state&) :90 2824x 100.0% – 100.0% boost::corosio::detail::win_state_list<boost::corosio::detail::win_handle_base>::close_all() :96 432x 100.0% 83.3% 100.0% boost::corosio::detail::win_state_list<boost::corosio::detail::win_object_handle_state>::close_all() :96 2824x 100.0% 83.3% 100.0% boost::corosio::detail::win_handle_registry<boost::corosio::detail::win_object_handle_impl, boost::corosio::detail::win_object_handle_state>::win_handle_registry() :124 2824x 100.0% 100.0% 83.3% boost::corosio::detail::win_handle_registry<boost::corosio::detail::win_random_access_file, boost::corosio::detail::win_handle_base>::win_handle_registry() :124 88x 100.0% 100.0% 83.3% boost::corosio::detail::win_handle_registry<boost::corosio::detail::win_random_access_handle_impl, boost::corosio::detail::win_handle_base>::win_handle_registry() :124 24x 100.0% 100.0% 83.3% boost::corosio::detail::win_handle_registry<boost::corosio::detail::win_stream_file, boost::corosio::detail::win_handle_base>::win_handle_registry() :124 286x 100.0% 100.0% 83.3% boost::corosio::detail::win_handle_registry<boost::corosio::detail::win_stream_handle_impl, boost::corosio::detail::win_handle_base>::win_handle_registry() :124 34x 100.0% 100.0% 83.3% boost::corosio::detail::win_handle_registry<boost::corosio::detail::win_object_handle_impl, boost::corosio::detail::win_object_handle_state>::~win_handle_registry() :128 2824x 100.0% 75.0% 100.0% boost::corosio::detail::win_handle_registry<boost::corosio::detail::win_random_access_file, boost::corosio::detail::win_handle_base>::~win_handle_registry() :128 88x 100.0% 75.0% 100.0% boost::corosio::detail::win_handle_registry<boost::corosio::detail::win_random_access_handle_impl, boost::corosio::detail::win_handle_base>::~win_handle_registry() :128 24x 100.0% 75.0% 100.0% boost::corosio::detail::win_handle_registry<boost::corosio::detail::win_stream_file, boost::corosio::detail::win_handle_base>::~win_handle_registry() :128 286x 100.0% 75.0% 100.0% boost::corosio::detail::win_handle_registry<boost::corosio::detail::win_stream_handle_impl, boost::corosio::detail::win_handle_base>::~win_handle_registry() :128 34x 100.0% 75.0% 100.0% boost::corosio::detail::win_handle_registry<boost::corosio::detail::win_object_handle_impl, boost::corosio::detail::win_object_handle_state>::states() :135 2824x 100.0% – 100.0% boost::corosio::detail::win_handle_registry<boost::corosio::detail::win_random_access_file, boost::corosio::detail::win_handle_base>::states() :135 116x 100.0% – 100.0% boost::corosio::detail::win_handle_registry<boost::corosio::detail::win_random_access_handle_impl, boost::corosio::detail::win_handle_base>::states() :135 24x 100.0% – 100.0% boost::corosio::detail::win_handle_registry<boost::corosio::detail::win_stream_file, boost::corosio::detail::win_handle_base>::states() :135 315x 100.0% – 100.0% boost::corosio::detail::win_handle_registry<boost::corosio::detail::win_stream_handle_impl, boost::corosio::detail::win_handle_base>::states() :135 35x 100.0% – 100.0% boost::corosio::detail::win_handle_registry<boost::corosio::detail::win_object_handle_impl, boost::corosio::detail::win_object_handle_state>::add(boost::corosio::detail::win_object_handle_impl*) :140 2824x 100.0% – 100.0% boost::corosio::detail::win_handle_registry<boost::corosio::detail::win_random_access_file, boost::corosio::detail::win_handle_base>::add(boost::corosio::detail::win_random_access_file*) :140 116x 100.0% – 100.0% boost::corosio::detail::win_handle_registry<boost::corosio::detail::win_random_access_handle_impl, boost::corosio::detail::win_handle_base>::add(boost::corosio::detail::win_random_access_handle_impl*) :140 24x 100.0% – 100.0% boost::corosio::detail::win_handle_registry<boost::corosio::detail::win_stream_file, boost::corosio::detail::win_handle_base>::add(boost::corosio::detail::win_stream_file*) :140 315x 100.0% – 100.0% boost::corosio::detail::win_handle_registry<boost::corosio::detail::win_stream_handle_impl, boost::corosio::detail::win_handle_base>::add(boost::corosio::detail::win_stream_handle_impl*) :140 35x 100.0% – 100.0% boost::corosio::detail::win_handle_registry<boost::corosio::detail::win_object_handle_impl, boost::corosio::detail::win_object_handle_state>::destroy(boost::corosio::detail::win_object_handle_impl&) :147 2823x 100.0% 50.0% 100.0% boost::corosio::detail::win_handle_registry<boost::corosio::detail::win_random_access_file, boost::corosio::detail::win_handle_base>::destroy(boost::corosio::detail::win_random_access_file&) :147 115x 100.0% 50.0% 100.0% boost::corosio::detail::win_handle_registry<boost::corosio::detail::win_random_access_handle_impl, boost::corosio::detail::win_handle_base>::destroy(boost::corosio::detail::win_random_access_handle_impl&) :147 23x 100.0% 50.0% 100.0% boost::corosio::detail::win_handle_registry<boost::corosio::detail::win_stream_file, boost::corosio::detail::win_handle_base>::destroy(boost::corosio::detail::win_stream_file&) :147 314x 100.0% 50.0% 100.0% boost::corosio::detail::win_handle_registry<boost::corosio::detail::win_stream_handle_impl, boost::corosio::detail::win_handle_base>::destroy(boost::corosio::detail::win_stream_handle_impl&) :147 31x 100.0% 50.0% 100.0% boost::corosio::detail::win_handle_registry<boost::corosio::detail::win_object_handle_impl, boost::corosio::detail::win_object_handle_state>::shutdown() :157 2824x 100.0% – 100.0% boost::corosio::detail::win_handle_registry<boost::corosio::detail::win_random_access_file, boost::corosio::detail::win_handle_base>::shutdown() :157 88x 100.0% – 100.0% boost::corosio::detail::win_handle_registry<boost::corosio::detail::win_random_access_handle_impl, boost::corosio::detail::win_handle_base>::shutdown() :157 24x 100.0% – 100.0% boost::corosio::detail::win_handle_registry<boost::corosio::detail::win_stream_file, boost::corosio::detail::win_handle_base>::shutdown() :157 286x 100.0% – 100.0% boost::corosio::detail::win_handle_registry<boost::corosio::detail::win_stream_handle_impl, boost::corosio::detail::win_handle_base>::shutdown() :157 34x 100.0% – 100.0% boost::corosio::detail::win_handle_base::win_handle_base(boost::corosio::detail::win_scheduler&, boost::corosio::detail::win_state_list<boost::corosio::detail::win_handle_base>&) :188 490x 100.0% – 100.0% boost::corosio::detail::win_handle_base::~win_handle_base() :196 490x 100.0% – 100.0% boost::corosio::detail::win_handle_base::native_handle() const :204 2334x 100.0% – 100.0% boost::corosio::detail::win_handle_base::is_open() const :209 103x 100.0% – 100.0% boost::corosio::detail::win_handle_base::close_handle() :215 1601x 100.0% 100.0% 100.0% boost::corosio::detail::win_handle_base::check_and_register(void*, boost::corosio::detail::handle_kind) :230 273x 90.0% 83.3% 92.3% boost::corosio::detail::win_handle_base::detach_or_throw(bool, char const*) :257 21x 100.0% 100.0% 83.3% boost::corosio::detail::win_handle_base::start_io(boost::corosio::detail::handle_io_op&, unsigned long long, boost::corosio::buffer_param) :274 584x 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)) :332 967x 100.0% – 100.0% boost::corosio::detail::handle_io_op::do_cancel_impl(boost::corosio::detail::overlapped_op*) :338 103x 100.0% 50.0% 84.6% boost::corosio::detail::win_slot_handle::win_slot_handle(boost::corosio::detail::win_scheduler&, boost::corosio::detail::win_state_list<boost::corosio::detail::win_handle_base>&, bool) :350 350x 100.0% – 100.0% boost::corosio::detail::win_slot_handle::read_some(std::__n4861::coroutine_handle<void>, boost::capy::executor_ref, boost::corosio::buffer_param, std::stop_token, std::error_code*, unsigned long long*) :359 270x 100.0% 100.0% 80.0% boost::corosio::detail::win_slot_handle::write_some(std::__n4861::coroutine_handle<void>, boost::capy::executor_ref, boost::corosio::buffer_param, std::stop_token, std::error_code*, unsigned long long*) :370 47x 100.0% 100.0% 80.0% boost::corosio::detail::win_slot_handle::cancel() :381 2x 100.0% 50.0% 100.0% boost::corosio::detail::win_slot_handle::assign(unsigned long long, boost::corosio::detail::handle_kind) :389 241x 100.0% 100.0% 67.9% boost::corosio::detail::win_slot_handle::release() :400 12x 100.0% 75.0% 73.5% boost::corosio::detail::win_slot_handle::close_handle() :416 1204x 100.0% – 100.0% boost::corosio::detail::win_slot_handle::slot_op::slot_op() :428 700x 100.0% – 100.0% boost::corosio::detail::win_slot_handle::slot_op::do_complete(void*, boost::corosio::detail::scheduler_op*, unsigned int, unsigned int) :430 317x 100.0% 100.0% 80.0% boost::corosio::detail::win_slot_handle::start(boost::corosio::detail::win_slot_handle::slot_op&, bool, std::__n4861::coroutine_handle<void>, boost::capy::executor_ref, boost::corosio::buffer_param, std::stop_token, std::error_code*, unsigned long long*) :461 317x 100.0% 100.0% 73.9% boost::corosio::detail::win_concurrent_handle::read_some_at(unsigned long long, boost::capy::continuation&, boost::capy::executor_ref, boost::corosio::buffer_param, std::stop_token, std::error_code*, unsigned long long*) :504 187x 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::buffer_param, std::stop_token, std::error_code*, unsigned long long*) :517 80x 100.0% 100.0% 80.0% boost::corosio::detail::win_concurrent_handle::cancel() :530 3x 100.0% 50.0% 100.0% boost::corosio::detail::win_concurrent_handle::assign(unsigned long long, boost::corosio::detail::handle_kind) :537 32x 100.0% 100.0% 100.0% boost::corosio::detail::win_concurrent_handle::release() :546 9x 100.0% – 100.0% boost::corosio::detail::win_concurrent_handle::concurrent_op::concurrent_op() :566 267x 100.0% – 100.0% boost::corosio::detail::win_concurrent_handle::concurrent_op::do_complete(void*, boost::corosio::detail::scheduler_op*, unsigned int, unsigned int) :569 267x 100.0% 80.0% 90.6% boost::corosio::detail::win_concurrent_handle::request_cancel_all() :608 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 :612 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::buffer_param, std::stop_token, std::error_code*, unsigned long long*) :615 267x 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/except.hpp>
20 #include <boost/corosio/detail/intrusive.hpp>
21 #include <boost/corosio/detail/native_handle.hpp>
22 #include <boost/corosio/native/detail/coro_op_complete.hpp>
23 #include <boost/corosio/native/detail/iocp/win_completion_key.hpp>
24 #include <boost/corosio/native/detail/iocp/win_dissociate.hpp>
25 #include <boost/corosio/native/detail/iocp/win_mutex.hpp>
26 #include <boost/corosio/native/detail/iocp/win_overlapped_op.hpp>
27 #include <boost/corosio/native/detail/iocp/win_scheduler.hpp>
28 #include <boost/corosio/native/detail/iocp/win_validate_handle.hpp>
29 #include <boost/capy/buffers.hpp>
30 #include <boost/capy/continuation.hpp>
31 #include <boost/capy/ex/executor_ref.hpp>
32
33 #include <algorithm>
34 #include <atomic>
35 #include <coroutine>
36 #include <cstdint>
37 #include <memory>
38 #include <mutex>
39 #include <stop_token>
40 #include <system_error>
41 #include <utility>
42 #include <vector>
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 allocates an op
50 per call so any number of positional ops can be in flight.
51
52 Each front keeps the shared-pointer "state + thin wrapper" layering:
53 ops hold the state alive until their last packet drains, which can
54 outlive the wrapper.
55 */
56
57 namespace boost::corosio::detail {
58
59 /** Map a handle completion code to what decode_io_result expects.
60
61 On reads, end of data (`ERROR_HANDLE_EOF`, `ERROR_BROKEN_PIPE`
62 once a pipe's writer is gone, or `ERROR_PIPE_NOT_CONNECTED` after
63 the server disconnects) becomes zero, so a zero-byte read
64 decodes as eof, and `ERROR_MORE_DATA` (a partial message on a
65 message-mode pipe) becomes a successful partial read. Socket ops
66 never pass through here: on a datagram socket `ERROR_MORE_DATA`
67 means truncation, which is an error.
68 */
69 inline DWORD
70 583x normalize_handle_error(DWORD err, bool is_read) noexcept
71 {
72
4/4
✓ Branch 2 → 3 taken 455 times.
✓ Branch 2 → 8 taken 128 times.
✓ Branch 3 → 4 taken 449 times.
✓ Branch 3 → 7 taken 6 times.
583x if (is_read &&
73
4/4
✓ Branch 4 → 5 taken 446 times.
✓ Branch 4 → 7 taken 3 times.
✓ Branch 5 → 6 taken 444 times.
✓ Branch 5 → 7 taken 2 times.
449x (err == ERROR_HANDLE_EOF || err == ERROR_BROKEN_PIPE ||
74
2/2
✓ Branch 6 → 7 taken 3 times.
✓ Branch 6 → 8 taken 441 times.
444x err == ERROR_PIPE_NOT_CONNECTED || err == ERROR_MORE_DATA))
75 14x return 0;
76 569x return err;
77 }
78
79 /// A service's list of live states, walked by `shutdown()`.
80 template<class State>
81 class win_state_list
82 {
83 public:
84 3314x void add(State& s) noexcept
85 {
86 3314x std::lock_guard<win_mutex> lock(mutex_);
87 3314x list_.push_back(&s);
88 3314x }
89
90 3314x void remove(State& s) noexcept
91 {
92 3314x std::lock_guard<win_mutex> lock(mutex_);
93 3314x list_.remove(&s);
94 3314x }
95
96 3256x void close_all() noexcept
97 {
98 // A state whose last owner is mid-destruction cannot be pinned;
99 // it is blocked in remove() on this mutex and closes itself.
100 // Pins are released outside the lock, since dropping the last
101 // one runs a destructor that calls remove().
102 3256x std::vector<std::shared_ptr<State>> pins;
103 {
104 3256x std::lock_guard<win_mutex> lock(mutex_);
105
2/2
✓ Branch 13 → 4 taken 9 times.
✓ Branch 13 → 14 taken 3256 times.
3265x for (auto* s = list_.pop_front(); s != nullptr;
106 9x s = list_.pop_front())
107
1/2
✓ Branch 8 → 9 taken 9 times.
✗ Branch 8 → 11 not taken.
18x if (auto pin = s->weak_from_this().lock())
108 9x pins.push_back(std::move(pin));
109 3256x }
110
2/2
✓ Branch 22 → 17 taken 9 times.
✓ Branch 22 → 23 taken 3256 times.
3265x for (auto& p : pins)
111 9x p->close_handle();
112 3256x }
113
114 private:
115 win_mutex mutex_;
116 intrusive_list<State> list_;
117 };
118
119 /// A service's states and the wrappers it handed out.
120 template<class Wrapper, class State>
121 class win_handle_registry
122 {
123 public:
124
1/1
✓ Branch 3 → 4 taken 3256 times.
3256x win_handle_registry() = default;
125 win_handle_registry(win_handle_registry const&) = delete;
126 win_handle_registry& operator=(win_handle_registry const&) = delete;
127
128 3256x ~win_handle_registry()
129 {
130
2/2
✓ Branch 6 → 3 taken 8 times.
✓ Branch 6 → 7 taken 3256 times.
3264x for (auto* w = wrappers_.pop_front(); w != nullptr;
131 8x w = wrappers_.pop_front())
132
1/2
✓ Branch 3 → 4 taken 8 times.
✗ Branch 3 → 5 not taken.
8x delete w;
133 3256x }
134
135 3314x win_state_list<State>& states() noexcept
136 {
137 3314x return states_;
138 }
139
140 3314x Wrapper* add(Wrapper* w)
141 {
142 3314x std::lock_guard<win_mutex> lock(mutex_);
143 3314x wrappers_.push_back(w);
144 6628x return w;
145 3314x }
146
147 3306x void destroy(Wrapper& w) noexcept
148 {
149 3306x w.close_internal();
150 {
151 3306x std::lock_guard<win_mutex> lock(mutex_);
152 3306x wrappers_.remove(&w);
153 3306x }
154
1/2
✓ Branch 6 → 7 taken 3306 times.
✗ Branch 6 → 8 not taken.
3306x delete &w;
155 3306x }
156
157 3256x void shutdown() noexcept
158 {
159 3256x states_.close_all();
160 3256x }
161
162 private:
163 win_state_list<State> states_;
164 win_mutex mutex_;
165 intrusive_list<Wrapper> wrappers_;
166 };
167
168 class win_handle_base;
169
170 /// An overlapped read or write on an adopted handle.
171 struct handle_io_op : overlapped_op
172 {
173 void* buf = nullptr;
174 DWORD buf_len = 0;
175 win_handle_base* owner = nullptr;
176 std::shared_ptr<win_handle_base> keep_alive;
177
178 explicit handle_io_op(func_type f) noexcept;
179 static void do_cancel_impl(overlapped_op* base) noexcept;
180 };
181
182 /** State shared by every overlapped handle front. */
183 class win_handle_base
184 : public intrusive_list<win_handle_base>::node
185 , public std::enable_shared_from_this<win_handle_base>
186 {
187 public:
188 490x win_handle_base(
189 win_scheduler& sched, win_state_list<win_handle_base>& list) noexcept
190 980x : sched_(sched)
191 490x , list_(list)
192 {
193 490x list_.add(*this);
194 490x }
195
196 490x ~win_handle_base()
197 {
198 490x list_.remove(*this);
199 490x }
200
201 win_handle_base(win_handle_base const&) = delete;
202 win_handle_base& operator=(win_handle_base const&) = delete;
203
204 2334x HANDLE native_handle() const noexcept
205 {
206 2334x return handle_;
207 }
208
209 103x bool is_open() const noexcept
210 {
211 103x return handle_ != INVALID_HANDLE_VALUE;
212 }
213
214 /// Close the handle; pending ops complete with ERROR_OPERATION_ABORTED.
215 1601x void close_handle() noexcept
216 {
217
2/2
✓ Branch 2 → 3 taken 634 times.
✓ Branch 2 → 6 taken 967 times.
1601x if (handle_ != INVALID_HANDLE_VALUE)
218 {
219 634x ::CancelIoEx(handle_, nullptr);
220 634x ::CloseHandle(handle_);
221 634x handle_ = INVALID_HANDLE_VALUE;
222 }
223 1601x }
224
225 protected:
226 /** Validate @p h and register it with this context's port.
227
228 The object is closed; the public `assign()` rejects an open one.
229 */
230 273x std::error_code check_and_register(HANDLE h, handle_kind kind) noexcept
231 {
232
2/2
✓ Branch 4 → 5 taken 15 times.
✓ Branch 4 → 6 taken 258 times.
273x if (auto ec = validate_overlapped_handle(h, kind))
233 15x return ec;
234
2/2
✓ Branch 8 → 9 taken 3 times.
✓ Branch 8 → 13 taken 255 times.
258x if (!::CreateIoCompletionPort(
235 258x h, static_cast<HANDLE>(sched_.native_handle()), key_io, 0))
236 {
237 3x DWORD const err = ::GetLastError();
238 // Already bound to another port. Mapped here because the
239 // system_category -> errc mapping of 87 differs by toolchain.
240
1/2
✓ Branch 10 → 11 taken 3 times.
✗ Branch 10 → 12 not taken.
3x if (err == ERROR_INVALID_PARAMETER)
241 3x return std::make_error_code(std::errc::invalid_argument);
242 ✗ return iocp_make_err(err, /*accept_path=*/false);
243 }
244 255x return {};
245 }
246
247 /** Unbind the handle from the port and give it up.
248
249 Called after the ops were cancelled. Throws, leaving the object
250 holding the handle, when an op is still in flight or Windows
251 refuses the detach: detaching under a queued completion would
252 lose it or deliver it to the next adopter's port. Asio fails
253 its release() the same way.
254
255 @param idle True when no op of this handle is in flight.
256 */
257 21x HANDLE detach_or_throw(bool idle, char const* what)
258 {
259
2/2
✓ Branch 2 → 3 taken 5 times.
✓ Branch 2 → 5 taken 16 times.
21x if (!idle)
260 5x detail::throw_system_error(
261 10x std::make_error_code(std::errc::device_or_resource_busy), what);
262
2/2
✓ Branch 6 → 7 taken 1 time.
✓ Branch 6 → 9 taken 15 times.
16x if (!dissociate_from_iocp(handle_))
263 1x detail::throw_system_error(
264 1x std::make_error_code(std::errc::operation_not_supported), what);
265 15x return std::exchange(handle_, INVALID_HANDLE_VALUE);
266 }
267
268 /** Issue ReadFile/WriteFile for an op the caller has set up.
269
270 The caller has filled `h`, `ex`, `ec_out`, `bytes_out`,
271 `is_read`, `owner` and `keep_alive`, called `start(token)` and
272 `work_started()`.
273 */
274 584x void start_io(
275 handle_io_op& op, std::uint64_t offset, buffer_param param) noexcept
276 {
277 static constexpr std::size_t max_buffers = 16;
278
279 // Closed-object contract: bad_file_descriptor without the kernel.
280
2/2
✓ Branch 2 → 3 taken 9 times.
✓ Branch 2 → 5 taken 575 times.
584x if (handle_ == INVALID_HANDLE_VALUE)
281 {
282 9x sched_.on_completion(&op, ERROR_INVALID_HANDLE, 0);
283 27x return;
284 }
285
286 575x capy::mutable_buffer bufs[max_buffers];
287 575x auto const count = param.copy_to(bufs, max_buffers);
288
2/2
✓ Branch 6 → 7 taken 6 times.
✓ Branch 6 → 9 taken 569 times.
575x if (count == 0)
289 {
290 6x op.empty_buffer = true;
291 6x sched_.on_completion(&op, 0, 0);
292 6x return;
293 }
294
295 // ReadFile/WriteFile take one contiguous buffer.
296 569x op.buf = bufs[0].data();
297 569x op.buf_len = static_cast<DWORD>(
298 569x (std::min)(bufs[0].size(), std::size_t(0x7fffffff)));
299 569x op.Offset = static_cast<DWORD>(offset & 0xFFFFFFFF);
300 569x op.OffsetHigh = static_cast<DWORD>(offset >> 32);
301
302 569x BOOL const ok = op.is_read
303
2/2
✓ Branch 12 → 13 taken 449 times.
✓ Branch 12 → 15 taken 120 times.
569x ? ::ReadFile(handle_, op.buf, op.buf_len, nullptr, &op)
304 120x : ::WriteFile(handle_, op.buf, op.buf_len, nullptr, &op);
305
2/2
✓ Branch 17 → 18 taken 455 times.
✓ Branch 17 → 20 taken 114 times.
569x DWORD const err = ok ? 0 : ::GetLastError();
306
307 // ERROR_MORE_DATA completed a partial message and still queues
308 // a packet, so it joins the pending path.
309
5/6
✓ Branch 21 → 22 taken 455 times.
✓ Branch 21 → 26 taken 114 times.
✓ Branch 22 → 23 taken 12 times.
✓ Branch 22 → 26 taken 443 times.
✓ Branch 23 → 24 taken 12 times.
✗ Branch 23 → 26 not taken.
569x if (err != 0 && err != ERROR_IO_PENDING && err != ERROR_MORE_DATA)
310 {
311 12x sched_.on_completion(&op, err, 0);
312 12x return;
313 }
314
315 // Re-check cancellation now that the kernel I/O is issued. This
316 // must precede on_pending: once it runs, another thread may
317 // dispatch (and for concurrent ops delete) the op, so neither the
318 // op nor this may be touched afterwards. Dispatch cannot happen
319 // before on_pending, and a cancel arriving after this check
320 // reaches the issued I/O through do_cancel_impl's own CancelIoEx.
321
1/2
✗ Branch 27 → 28 not taken.
✓ Branch 27 → 29 taken 557 times.
557x if (op.cancelled.load(std::memory_order_acquire))
322 ✗ ::CancelIoEx(handle_, &op);
323
324 557x sched_.on_pending(&op);
325 }
326
327 win_scheduler& sched_;
328 win_state_list<win_handle_base>& list_;
329 HANDLE handle_ = INVALID_HANDLE_VALUE;
330 };
331
332 967x inline handle_io_op::handle_io_op(func_type f) noexcept : overlapped_op(f)
333 {
334 967x cancel_func_ = &do_cancel_impl;
335 967x }
336
337 inline void
338 103x handle_io_op::do_cancel_impl(overlapped_op* base) noexcept
339 {
340 103x auto* op = static_cast<handle_io_op*>(base);
341 103x op->cancelled.store(true, std::memory_order_release);
342
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())
343
1/2
✓ Branch 9 → 10 taken 103 times.
✗ Branch 9 → 11 not taken.
103x ::CancelIoEx(op->owner->native_handle(), op);
344 103x }
345
346 /** One read and one write in flight: stream_file, win_stream_handle. */
347 class win_slot_handle : public win_handle_base
348 {
349 public:
350 350x win_slot_handle(
351 win_scheduler& sched,
352 win_state_list<win_handle_base>& list,
353 bool track_offset) noexcept
354 350x : win_handle_base(sched, list)
355 350x , track_offset_(track_offset)
356 {
357 350x }
358
359 270x std::coroutine_handle<> read_some(
360 std::coroutine_handle<> h,
361 capy::executor_ref ex,
362 buffer_param param,
363 std::stop_token token,
364 std::error_code* ec,
365 std::size_t* bytes_out)
366 {
367
1/1
✓ Branch 4 → 5 taken 270 times.
270x return start(rd_, true, h, ex, param, std::move(token), ec, bytes_out);
368 }
369
370 47x std::coroutine_handle<> write_some(
371 std::coroutine_handle<> h,
372 capy::executor_ref ex,
373 buffer_param param,
374 std::stop_token token,
375 std::error_code* ec,
376 std::size_t* bytes_out)
377 {
378
1/1
✓ Branch 4 → 5 taken 47 times.
47x return start(wr_, false, h, ex, param, std::move(token), ec, bytes_out);
379 }
380
381 2x void cancel() noexcept
382 {
383
1/2
✓ Branch 2 → 3 taken 2 times.
✗ Branch 2 → 4 not taken.
2x if (handle_ != INVALID_HANDLE_VALUE)
384 2x ::CancelIoEx(handle_, nullptr);
385 2x rd_.request_cancel();
386 2x wr_.request_cancel();
387 2x }
388
389 241x std::error_code assign(native_handle_type nh, handle_kind kind) noexcept
390 {
391 241x HANDLE h = reinterpret_cast<HANDLE>(nh);
392
2/2
✓ Branch 4 → 5 taken 10 times.
✓ Branch 4 → 6 taken 231 times.
241x if (auto ec = check_and_register(h, kind))
393 10x return ec;
394 231x generation_.fetch_add(1, std::memory_order_acq_rel);
395 231x handle_ = h;
396 231x offset_.store(0, std::memory_order_release);
397 231x return {};
398 }
399
400 12x native_handle_type release()
401 {
402 // Cancel first, as asio does: a release that then fails still
403 // aborts the in-flight I/O, and a retry once it drains succeeds.
404 12x rd_.request_cancel();
405 12x wr_.request_cancel();
406 12x ::CancelIoEx(handle_, nullptr);
407
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) &&
408
1/2
✓ Branch 8 → 9 taken 9 times.
✗ Branch 8 → 10 not taken.
9x !wr_.in_flight.load(std::memory_order_acquire);
409 12x HANDLE h = detach_or_throw(idle, "release");
410 8x generation_.fetch_add(1, std::memory_order_acq_rel);
411 8x offset_.store(0, std::memory_order_release);
412 8x return reinterpret_cast<native_handle_type>(h);
413 }
414
415 /// Close the handle; completions still queued no longer move the position.
416 1204x void close_handle() noexcept
417 {
418 1204x generation_.fetch_add(1, std::memory_order_acq_rel);
419 1204x win_handle_base::close_handle();
420 1204x }
421
422 protected:
423 struct slot_op : handle_io_op
424 {
425 std::atomic<bool> in_flight{false};
426 std::uint32_t generation = 0;
427
428 700x slot_op() noexcept : handle_io_op(&do_complete) {}
429
430 317x static void do_complete(
431 void* owner,
432 scheduler_op* base,
433 std::uint32_t /*bytes*/,
434 std::uint32_t /*error*/)
435 {
436 317x auto* op = static_cast<slot_op*>(base);
437 317x op->in_flight.store(false, std::memory_order_release);
438
2/2
✓ Branch 3 → 4 taken 4 times.
✓ Branch 3 → 7 taken 313 times.
317x if (!owner)
439 {
440 4x op->cleanup_only();
441 4x op->keep_alive.reset();
442 4x return;
443 }
444
445 313x auto* self = static_cast<win_slot_handle*>(op->owner);
446 // A completion from before assign()/release() belongs to the
447 // old handle; its byte count must not move the new position.
448
2/2
✓ Branch 8 → 9 taken 275 times.
✓ Branch 8 → 27 taken 13 times.
288x if (self->track_offset_ && op->dwError == 0 &&
449
6/6
✓ Branch 7 → 8 taken 288 times.
✓ Branch 7 → 27 taken 25 times.
✓ Branch 9 → 10 taken 272 times.
✓ Branch 9 → 27 taken 3 times.
✓ Branch 28 → 29 taken 71 times.
✓ Branch 28 → 32 taken 242 times.
873x op->bytes_transferred > 0 &&
450 272x self->generation_.load(std::memory_order_acquire) ==
451
2/2
✓ Branch 25 → 26 taken 71 times.
✓ Branch 25 → 27 taken 201 times.
272x op->generation)
452 71x self->offset_.fetch_add(
453 71x op->bytes_transferred, std::memory_order_acq_rel);
454
455 313x op->dwError = normalize_handle_error(op->dwError, op->is_read);
456 313x auto prevent_premature_destruction = std::move(op->keep_alive);
457
1/1
✓ Branch 35 → 36 taken 313 times.
313x op->invoke_handler();
458 313x }
459 };
460
461 317x std::coroutine_handle<> start(
462 slot_op& op,
463 bool is_read,
464 std::coroutine_handle<> h,
465 capy::executor_ref ex,
466 buffer_param param,
467 std::stop_token token,
468 std::error_code* ec,
469 std::size_t* bytes_out)
470 {
471
1/1
✓ Branch 2 → 3 taken 317 times.
317x op.keep_alive = shared_from_this();
472 317x op.reset();
473 317x op.owner = this;
474 317x op.is_read = is_read;
475 317x op.h = h;
476 317x op.ex = ex;
477 317x op.ec_out = ec;
478 317x op.bytes_out = bytes_out;
479 317x op.start(token);
480
481 317x sched_.work_started();
482 317x op.generation = generation_.load(std::memory_order_acquire);
483 317x op.in_flight.store(true, std::memory_order_release);
484 317x start_io(
485
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,
486 param);
487 317x return std::noop_coroutine();
488 }
489
490 slot_op rd_;
491 slot_op wr_;
492 std::atomic<std::uint64_t> offset_{0};
493 // Bumped by assign(), release() and close; tags each op.
494 std::atomic<std::uint32_t> generation_{0};
495 bool track_offset_;
496 };
497
498 /** Any number of positional ops: random_access_file, win_random_access_handle. */
499 class win_concurrent_handle : public win_handle_base
500 {
501 public:
502 using win_handle_base::win_handle_base;
503
504 187x std::coroutine_handle<> read_some_at(
505 std::uint64_t offset,
506 capy::continuation& cont,
507 capy::executor_ref ex,
508 buffer_param param,
509 std::stop_token token,
510 std::error_code* ec,
511 std::size_t* bytes_out)
512 {
513
1/1
✓ Branch 4 → 5 taken 187 times.
374x return start(
514 561x true, offset, cont, ex, param, std::move(token), ec, bytes_out);
515 }
516
517 80x std::coroutine_handle<> write_some_at(
518 std::uint64_t offset,
519 capy::continuation& cont,
520 capy::executor_ref ex,
521 buffer_param param,
522 std::stop_token token,
523 std::error_code* ec,
524 std::size_t* bytes_out)
525 {
526
1/1
✓ Branch 4 → 5 taken 80 times.
160x return start(
527 240x false, offset, cont, ex, param, std::move(token), ec, bytes_out);
528 }
529
530 3x void cancel() noexcept
531 {
532
1/2
✓ Branch 2 → 3 taken 3 times.
✗ Branch 2 → 4 not taken.
3x if (handle_ != INVALID_HANDLE_VALUE)
533 3x ::CancelIoEx(handle_, nullptr);
534 3x request_cancel_all();
535 3x }
536
537 32x std::error_code assign(native_handle_type nh, handle_kind kind) noexcept
538 {
539 32x HANDLE h = reinterpret_cast<HANDLE>(nh);
540
2/2
✓ Branch 4 → 5 taken 8 times.
✓ Branch 4 → 6 taken 24 times.
32x if (auto ec = check_and_register(h, kind))
541 8x return ec;
542 24x handle_ = h;
543 24x return {};
544 }
545
546 9x native_handle_type release()
547 {
548 9x request_cancel_all();
549 9x ::CancelIoEx(handle_, nullptr);
550 bool idle;
551 {
552 9x std::lock_guard<win_mutex> lock(ops_mutex_);
553 9x idle = outstanding_ops_.empty();
554 9x }
555 return reinterpret_cast<native_handle_type>(
556 9x detach_or_throw(idle, "release"));
557 }
558
559 protected:
560 struct concurrent_op
561 : handle_io_op
562 , intrusive_list<concurrent_op>::node
563 {
564 capy::continuation* user_cont = nullptr;
565
566 267x concurrent_op() noexcept : handle_io_op(&do_complete) {}
567
568 // The continuation lives in the awaitable, so the op can go first.
569 267x static void do_complete(
570 void* owner,
571 scheduler_op* base,
572 std::uint32_t /*bytes*/,
573 std::uint32_t /*error*/)
574 {
575 267x auto* op = static_cast<concurrent_op*>(base);
576 267x auto* self = static_cast<win_concurrent_handle*>(op->owner);
577 267x auto keep = std::move(op->keep_alive);
578 267x op->stop_cb.reset();
579 {
580 267x std::lock_guard<win_mutex> lock(self->ops_mutex_);
581 267x self->outstanding_ops_.remove(op);
582 267x }
583
584
2/2
✓ Branch 8 → 9 taken 4 times.
✓ Branch 8 → 13 taken 263 times.
267x if (!owner)
585 {
586 4x op->h = {};
587
1/2
✓ Branch 10 → 11 taken 4 times.
✗ Branch 10 → 12 not taken.
4x delete op;
588 4x return;
589 }
590
591 263x DWORD const err = normalize_handle_error(op->dwError, op->is_read);
592 513x decode_io_result(
593 op->ec_out, op->bytes_out,
594 263x op->cancelled.load(std::memory_order_acquire),
595 13x err != 0 ? iocp_make_err(err, /*accept_path=*/false)
596 : std::error_code{},
597 263x op->is_read, static_cast<std::size_t>(op->bytes_transferred),
598
2/2
✓ Branch 14 → 15 taken 13 times.
✓ Branch 14 → 16 taken 250 times.
263x op->empty_buffer);
599
600 263x capy::continuation* c = op->user_cont;
601 263x c->h = op->h;
602 263x capy::executor_ref ex = op->ex;
603
1/2
✓ Branch 19 → 20 taken 263 times.
✗ Branch 19 → 21 not taken.
263x delete op;
604
2/2
✓ Branch 21 → 22 taken 263 times.
✓ Branch 22 → 23 taken 263 times.
263x dispatch_coro(ex, *c).resume();
605 267x }
606 };
607
608 12x void request_cancel_all() noexcept
609 {
610 12x std::lock_guard<win_mutex> lock(ops_mutex_);
611 12x outstanding_ops_.for_each(
612 4x [](concurrent_op* op) { op->request_cancel(); });
613 12x }
614
615 267x std::coroutine_handle<> start(
616 bool is_read,
617 std::uint64_t offset,
618 capy::continuation& cont,
619 capy::executor_ref ex,
620 buffer_param param,
621 std::stop_token token,
622 std::error_code* ec,
623 std::size_t* bytes_out)
624 {
625 267x auto* op = new concurrent_op();
626
1/1
✓ Branch 4 → 5 taken 267 times.
267x op->keep_alive = shared_from_this();
627 267x op->reset();
628 267x op->owner = this;
629 267x op->is_read = is_read;
630 267x op->user_cont = &cont;
631 267x op->h = cont.h;
632 267x op->ex = ex;
633 267x op->ec_out = ec;
634 267x op->bytes_out = bytes_out;
635 267x op->start(token);
636
637 267x sched_.work_started();
638 {
639 267x std::lock_guard<win_mutex> lock(ops_mutex_);
640 267x outstanding_ops_.push_back(op);
641 267x }
642 267x start_io(*op, offset, param);
643 267x return std::noop_coroutine();
644 }
645
646 win_mutex ops_mutex_;
647 intrusive_list<concurrent_op> outstanding_ops_;
648 };
649
650 } // namespace boost::corosio::detail
651
652 #endif // BOOST_COROSIO_HAS_IOCP
653
654 #endif
655