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

88.9% Lines (289/325) 98.0% List of functions (48/49) 74.8% Branches (113/151)
win_file_service.hpp
f(x) Functions (49)
Function Calls Lines Branches Blocks
boost::corosio::detail::file_read_op::file_read_op(boost::corosio::detail::win_stream_file_internal&) :118 47x 100.0% 100.0% boost::corosio::detail::file_write_op::file_write_op(boost::corosio::detail::win_stream_file_internal&) :125 47x 100.0% 100.0% boost::corosio::detail::file_read_op::do_cancel_impl(boost::corosio::detail::overlapped_op*) :137 1x 100.0% 50.0% 88.9% boost::corosio::detail::file_write_op::do_cancel_impl(boost::corosio::detail::overlapped_op*) :146 0 0.0% 0.0% 0.0% boost::corosio::detail::file_read_op::do_complete(void*, boost::corosio::detail::scheduler_op*, unsigned int, unsigned int) :159 15x 72.7% 85.7% 68.8% boost::corosio::detail::file_write_op::do_complete(void*, boost::corosio::detail::scheduler_op*, unsigned int, unsigned int) :183 14x 72.7% 85.7% 68.8% boost::corosio::detail::win_stream_file_internal::win_stream_file_internal(boost::corosio::detail::win_file_service&) :211 47x 100.0% 100.0% boost::corosio::detail::win_stream_file_internal::~win_stream_file_internal() :220 47x 100.0% 100.0% boost::corosio::detail::win_stream_file_internal::native_handle() const :226 151x 100.0% 100.0% boost::corosio::detail::win_stream_file_internal::is_open() const :232 1x 100.0% 100.0% boost::corosio::detail::win_stream_file_internal::cancel() :238 1x 100.0% 50.0% 100.0% boost::corosio::detail::win_stream_file_internal::close_handle() :248 133x 100.0% 100.0% 100.0% boost::corosio::detail::win_stream_file_internal::size() const :260 7x 75.0% 50.0% 55.6% boost::corosio::detail::win_stream_file_internal::resize(unsigned long long) :269 3x 85.7% 75.0% 81.8% boost::corosio::detail::win_stream_file_internal::sync_data() :281 2x 50.0% 25.0% 44.4% boost::corosio::detail::win_stream_file_internal::sync_all() :292 2x 75.0% 50.0% 66.7% boost::corosio::detail::win_stream_file_internal::release() :300 1x 100.0% 100.0% boost::corosio::detail::win_stream_file_internal::assign(unsigned long long) :309 2x 88.9% 50.0% 75.0% boost::corosio::detail::win_stream_file_internal::seek(long long, boost::corosio::file_base::seek_basis) :325 12x 91.7% 87.5% 78.9% boost::corosio::detail::win_stream_file_internal::read_some(std::__n4861::coroutine_handle<void>, boost::capy::executor_ref, boost::corosio::buffer_param, std::stop_token, std::error_code*, unsigned long long*) :355 15x 100.0% 87.5% 95.1% boost::corosio::detail::win_stream_file_internal::write_some(std::__n4861::coroutine_handle<void>, boost::capy::executor_ref, boost::corosio::buffer_param, std::stop_token, std::error_code*, unsigned long long*) :426 14x 96.8% 87.5% 95.1% boost::corosio::detail::win_stream_file::win_stream_file(std::shared_ptr<boost::corosio::detail::win_stream_file_internal>) :499 47x 100.0% 100.0% boost::corosio::detail::win_stream_file::close_internal() :506 47x 100.0% 50.0% 100.0% boost::corosio::detail::win_stream_file::read_some(std::__n4861::coroutine_handle<void>, boost::capy::executor_ref, boost::corosio::buffer_param, std::stop_token, std::error_code*, unsigned long long*) :516 15x 100.0% 100.0% 80.0% boost::corosio::detail::win_stream_file::write_some(std::__n4861::coroutine_handle<void>, boost::capy::executor_ref, boost::corosio::buffer_param, std::stop_token, std::error_code*, unsigned long long*) :528 14x 100.0% 100.0% 80.0% boost::corosio::detail::win_stream_file::native_handle() const :540 150x 100.0% 100.0% boost::corosio::detail::win_stream_file::cancel() :546 1x 100.0% 100.0% boost::corosio::detail::win_stream_file::size() const :552 7x 100.0% 100.0% boost::corosio::detail::win_stream_file::resize(unsigned long long) :558 3x 100.0% 100.0% boost::corosio::detail::win_stream_file::sync_data() :564 2x 100.0% 100.0% boost::corosio::detail::win_stream_file::sync_all() :570 2x 100.0% 100.0% boost::corosio::detail::win_stream_file::release() :576 1x 100.0% 100.0% boost::corosio::detail::win_stream_file::assign(unsigned long long) :582 2x 100.0% 100.0% boost::corosio::detail::win_stream_file::seek(long long, boost::corosio::file_base::seek_basis) :588 12x 100.0% 100.0% boost::corosio::detail::win_stream_file::get_internal() const :594 120x 100.0% 100.0% boost::corosio::detail::win_file_service::win_file_service(boost::capy::execution_context&) :604 1121x 100.0% 83.3% 80.0% boost::corosio::detail::win_file_service::~win_file_service() :618 2242x 100.0% 100.0% boost::corosio::detail::win_file_service::construct() :626 47x 100.0% 100.0% 89.5% boost::corosio::detail::win_file_service::destroy(boost::corosio::io_object::implementation*) :646 47x 100.0% 50.0% 100.0% boost::corosio::detail::win_file_service::close(boost::corosio::io_object::handle&) :657 84x 100.0% 100.0% boost::corosio::detail::win_file_service::shutdown() :664 1121x 66.7% 50.0% 71.4% boost::corosio::detail::win_file_service::open_file(boost::corosio::stream_file::implementation&, std::filesystem::__cxx11::path const&, boost::corosio::file_base::flags) :676 38x 77.8% 86.0% 84.8% boost::corosio::detail::win_file_service::destroy_impl(boost::corosio::detail::win_stream_file&) :764 47x 100.0% 50.0% 100.0% boost::corosio::detail::win_file_service::unregister_impl(boost::corosio::detail::win_stream_file_internal&) :774 47x 100.0% 100.0% boost::corosio::detail::win_file_service::on_pending(boost::corosio::detail::overlapped_op*) :787 19x 100.0% 100.0% boost::corosio::detail::win_file_service::on_completion(boost::corosio::detail::overlapped_op*, unsigned long, unsigned long) :793 10x 100.0% 100.0% boost::corosio::detail::win_file_service::work_started() :800 29x 100.0% 100.0% boost::corosio::detail::win_file_service::iocp_handle() const :812 2x 100.0% 100.0% boost::corosio::detail::win_file_service::try_flush_data(void*) :818 2x 85.7% 50.0% 71.4%
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_FILE_SERVICE_HPP
11 #define BOOST_COROSIO_NATIVE_DETAIL_IOCP_WIN_FILE_SERVICE_HPP
12
13 #include <boost/corosio/detail/platform.hpp>
14
15 #if BOOST_COROSIO_HAS_IOCP
16
17 #include <boost/corosio/detail/config.hpp>
18 #include <boost/corosio/detail/except.hpp>
19 #include <boost/corosio/detail/file_service.hpp>
20 #include <boost/capy/ex/execution_context.hpp>
21 #include <boost/corosio/detail/intrusive.hpp>
22 #include <boost/corosio/native/detail/iocp/win_mutex.hpp>
23 #include <boost/corosio/native/detail/iocp/win_stream_file.hpp>
24 #include <boost/corosio/native/detail/iocp/win_scheduler.hpp>
25 #include <boost/corosio/native/detail/iocp/win_completion_key.hpp>
26 #include <boost/corosio/native/detail/make_err.hpp>
27 #include <boost/corosio/detail/dispatch_coro.hpp>
28 #include <boost/corosio/detail/buffer_param.hpp>
29 #include <boost/capy/buffers.hpp>
30
31 #include <filesystem>
32
33 namespace boost::corosio::detail {
34
35 /** Windows IOCP stream file management service.
36
37 Owns all stream file implementations and coordinates their
38 lifecycle with the IOCP.
39
40 @par Thread Safety
41 All public member functions are thread-safe.
42 */
43 class BOOST_COROSIO_DECL win_file_service final
44 : public file_service
45 {
46 public:
47 using key_type = win_file_service;
48
49 explicit win_file_service(capy::execution_context& ctx);
50 ~win_file_service();
51
52 win_file_service(win_file_service const&) = delete;
53 win_file_service& operator=(win_file_service const&) = delete;
54
55 io_object::implementation* construct() override;
56 void destroy(io_object::implementation* p) override;
57 void close(io_object::handle& h) override;
58 void shutdown() override;
59
60 std::error_code open_file(
61 stream_file::implementation& impl,
62 std::filesystem::path const& path,
63 file_base::flags mode) override;
64
65 void destroy_impl(win_stream_file& impl);
66 void unregister_impl(win_stream_file_internal& impl);
67
68 void post(overlapped_op* op);
69 void on_pending(overlapped_op* op) noexcept;
70 void on_completion(overlapped_op* op, DWORD error, DWORD bytes) noexcept;
71 void work_started() noexcept;
72 void work_finished() noexcept;
73
74 void* iocp_handle() const noexcept;
75
76 /** Attempt data-only flush via NtFlushBuffersFileEx.
77
78 @return true if data-only flush succeeded, false if
79 caller should fall back to FlushFileBuffers.
80 */
81 bool try_flush_data(HANDLE h) noexcept;
82
83 private:
84 // NtFlushBuffersFileEx support for data-only sync
85 struct io_status_block
86 {
87 union { LONG Status; void* Pointer; };
88 ULONG_PTR Information;
89 };
90
91 enum { flush_flags_file_data_sync_only = 4 };
92
93 using nt_flush_fn = LONG(NTAPI*)(
94 HANDLE, ULONG, void*, ULONG, io_status_block*);
95
96 win_scheduler& sched_;
97 BOOST_COROSIO_MSVC_WARNING_PUSH
98 BOOST_COROSIO_MSVC_WARNING_DISABLE(4251) // detail:: members, dll-interface
99 win_mutex mutex_;
100 intrusive_list<win_stream_file_internal> file_list_;
101 intrusive_list<win_stream_file> wrapper_list_;
102 BOOST_COROSIO_MSVC_WARNING_POP
103 void* iocp_;
104 nt_flush_fn nt_flush_buffers_file_ex_;
105 };
106
107 /** Get or create the stream file service for the given context. */
108 inline win_file_service&
109 get_stream_file_service(capy::execution_context& ctx, win_scheduler&)
110 {
111 return ctx.make_service<win_file_service>();
112 }
113
114 // ---------------------------------------------------------------------------
115 // Operation constructors
116 // ---------------------------------------------------------------------------
117
118 47x inline file_read_op::file_read_op(win_stream_file_internal& f) noexcept
119 : overlapped_op(&do_complete)
120 47x , file_(f)
121 {
122 47x cancel_func_ = &do_cancel_impl;
123 47x }
124
125 47x inline file_write_op::file_write_op(win_stream_file_internal& f) noexcept
126 : overlapped_op(&do_complete)
127 47x , file_(f)
128 {
129 47x cancel_func_ = &do_cancel_impl;
130 47x }
131
132 // ---------------------------------------------------------------------------
133 // Cancellation functions
134 // ---------------------------------------------------------------------------
135
136 inline void
137 1x file_read_op::do_cancel_impl(overlapped_op* base) noexcept
138 {
139 1x auto* op = static_cast<file_read_op*>(base);
140 1x op->cancelled.store(true, std::memory_order_release);
141
1/2
✓ Branch 4 → 5 taken 1 time.
✗ Branch 4 → 10 not taken.
1x if (op->file_.is_open())
142
1/2
✓ Branch 5 → 6 taken 1 time.
✗ Branch 5 → 7 not taken.
1x ::CancelIoEx(op->file_.native_handle(), op);
143 1x }
144
145 inline void
146 file_write_op::do_cancel_impl(overlapped_op* base) noexcept
147 {
148 auto* op = static_cast<file_write_op*>(base);
149 op->cancelled.store(true, std::memory_order_release);
150 if (op->file_.is_open())
151 ::CancelIoEx(op->file_.native_handle(), op);
152 }
153
154 // ---------------------------------------------------------------------------
155 // Completion handlers
156 // ---------------------------------------------------------------------------
157
158 inline void
159 15x file_read_op::do_complete(
160 void* owner,
161 scheduler_op* base,
162 std::uint32_t /*bytes*/,
163 std::uint32_t /*error*/)
164 {
165 15x auto* op = static_cast<file_read_op*>(base);
166
167
1/2
✗ Branch 2 → 3 not taken.
✓ Branch 2 → 6 taken 15 times.
15x if (!owner)
168 {
169 op->cleanup_only();
170 op->file_ptr.reset();
171 return;
172 }
173
174 // Advance stream position on success
175
4/4
✓ Branch 6 → 7 taken 9 times.
✓ Branch 6 → 9 taken 6 times.
✓ Branch 7 → 8 taken 8 times.
✓ Branch 7 → 9 taken 1 time.
15x if (op->dwError == 0 && op->bytes_transferred > 0)
176 8x op->file_.offset_ += op->bytes_transferred;
177
178 15x auto prevent_premature_destruction = std::move(op->file_ptr);
179
1/1
✓ Branch 11 → 12 taken 15 times.
15x op->invoke_handler();
180 15x }
181
182 inline void
183 14x file_write_op::do_complete(
184 void* owner,
185 scheduler_op* base,
186 std::uint32_t /*bytes*/,
187 std::uint32_t /*error*/)
188 {
189 14x auto* op = static_cast<file_write_op*>(base);
190
191
1/2
✗ Branch 2 → 3 not taken.
✓ Branch 2 → 6 taken 14 times.
14x if (!owner)
192 {
193 op->cleanup_only();
194 op->file_ptr.reset();
195 return;
196 }
197
198 // Advance stream position on success
199
4/4
✓ Branch 6 → 7 taken 10 times.
✓ Branch 6 → 9 taken 4 times.
✓ Branch 7 → 8 taken 9 times.
✓ Branch 7 → 9 taken 1 time.
14x if (op->dwError == 0 && op->bytes_transferred > 0)
200 9x op->file_.offset_ += op->bytes_transferred;
201
202 14x auto prevent_premature_destruction = std::move(op->file_ptr);
203
1/1
✓ Branch 11 → 12 taken 14 times.
14x op->invoke_handler();
204 14x }
205
206 // ---------------------------------------------------------------------------
207 // win_stream_file_internal
208 // ---------------------------------------------------------------------------
209
210 inline
211 47x win_stream_file_internal::win_stream_file_internal(
212 47x win_file_service& svc) noexcept
213 47x : svc_(svc)
214 47x , rd_(*this)
215 94x , wr_(*this)
216 {
217 47x }
218
219 inline
220 47x win_stream_file_internal::~win_stream_file_internal()
221 {
222 47x svc_.unregister_impl(*this);
223 47x }
224
225 inline HANDLE
226 151x win_stream_file_internal::native_handle() const noexcept
227 {
228 151x return handle_;
229 }
230
231 inline bool
232 1x win_stream_file_internal::is_open() const noexcept
233 {
234 1x return handle_ != INVALID_HANDLE_VALUE;
235 }
236
237 inline void
238 1x win_stream_file_internal::cancel() noexcept
239 {
240
1/2
✓ Branch 2 → 3 taken 1 time.
✗ Branch 2 → 4 not taken.
1x if (handle_ != INVALID_HANDLE_VALUE)
241 1x ::CancelIoEx(handle_, nullptr);
242
243 1x rd_.request_cancel();
244 1x wr_.request_cancel();
245 1x }
246
247 inline void
248 133x win_stream_file_internal::close_handle() noexcept
249 {
250
2/2
✓ Branch 2 → 3 taken 37 times.
✓ Branch 2 → 6 taken 96 times.
133x if (handle_ != INVALID_HANDLE_VALUE)
251 {
252 37x ::CancelIoEx(handle_, nullptr);
253 37x ::CloseHandle(handle_);
254 37x handle_ = INVALID_HANDLE_VALUE;
255 }
256 133x offset_ = 0;
257 133x }
258
259 inline std::uint64_t
260 7x win_stream_file_internal::size() const
261 {
262 LARGE_INTEGER li;
263
2/3
✓ Branch 2 → 3 taken 7 times.
✗ Branch 3 → 4 not taken.
✓ Branch 3 → 7 taken 7 times.
7x if (!::GetFileSizeEx(handle_, &li))
264 throw_system_error(make_err(::GetLastError()), "stream_file::size");
265 7x return static_cast<std::uint64_t>(li.QuadPart);
266 }
267
268 inline std::error_code
269 3x win_stream_file_internal::resize(std::uint64_t new_size) noexcept
270 {
271 LARGE_INTEGER li;
272 3x li.QuadPart = static_cast<LONGLONG>(new_size);
273
1/2
✗ Branch 3 → 4 not taken.
✓ Branch 3 → 6 taken 3 times.
3x if (!::SetFilePointerEx(handle_, li, nullptr, FILE_BEGIN))
274 return make_err(::GetLastError());
275
2/2
✓ Branch 7 → 8 taken 1 time.
✓ Branch 7 → 10 taken 2 times.
3x if (!::SetEndOfFile(handle_))
276 1x return make_err(::GetLastError());
277 2x return {};
278 }
279
280 inline std::error_code
281 2x win_stream_file_internal::sync_data() noexcept
282 {
283 // Attempt data-only flush; fall back to full flush
284
1/2
✓ Branch 3 → 4 taken 2 times.
✗ Branch 3 → 5 not taken.
2x if (svc_.try_flush_data(handle_))
285 2x return {};
286 if (!::FlushFileBuffers(handle_))
287 return make_err(::GetLastError());
288 return {};
289 }
290
291 inline std::error_code
292 2x win_stream_file_internal::sync_all() noexcept
293 {
294
1/2
✗ Branch 3 → 4 not taken.
✓ Branch 3 → 6 taken 2 times.
2x if (!::FlushFileBuffers(handle_))
295 return make_err(::GetLastError());
296 2x return {};
297 }
298
299 inline native_handle_type
300 1x win_stream_file_internal::release()
301 {
302 1x HANDLE h = handle_;
303 1x handle_ = INVALID_HANDLE_VALUE;
304 1x offset_ = 0;
305 1x return reinterpret_cast<native_handle_type>(h);
306 }
307
308 inline std::error_code
309 2x win_stream_file_internal::assign(native_handle_type handle) noexcept
310 {
311 2x close_handle();
312 2x HANDLE h = reinterpret_cast<HANDLE>(handle);
313 // Register with IOCP so overlapped I/O works
314
1/2
✗ Branch 5 → 6 not taken.
✓ Branch 5 → 8 taken 2 times.
2x if (!::CreateIoCompletionPort(
315 2x h, static_cast<HANDLE>(svc_.iocp_handle()), key_io, 0))
316 {
317 return make_err(::GetLastError());
318 }
319 2x handle_ = h;
320 2x offset_ = 0;
321 2x return {};
322 }
323
324 inline capy::io_result<std::uint64_t>
325 12x win_stream_file_internal::seek(
326 std::int64_t offset, file_base::seek_basis origin) noexcept
327 {
328 // We manage offset_ ourselves (same as POSIX impl).
329 std::int64_t new_pos;
330
331
2/2
✓ Branch 2 → 3 taken 5 times.
✓ Branch 2 → 4 taken 7 times.
12x if (origin == file_base::seek_set)
332 {
333 5x new_pos = offset;
334 }
335
2/2
✓ Branch 4 → 5 taken 3 times.
✓ Branch 4 → 6 taken 4 times.
7x else if (origin == file_base::seek_cur)
336 {
337 3x new_pos = static_cast<std::int64_t>(offset_) + offset;
338 }
339 else // seek_end
340 {
341 LARGE_INTEGER li;
342
1/2
✗ Branch 7 → 8 not taken.
✓ Branch 7 → 12 taken 4 times.
4x if (!::GetFileSizeEx(handle_, &li))
343 return {make_err(::GetLastError()), 0};
344 4x new_pos = li.QuadPart + offset;
345 }
346
347
2/2
✓ Branch 13 → 14 taken 3 times.
✓ Branch 13 → 17 taken 9 times.
12x if (new_pos < 0)
348 3x return {make_err(ERROR_NEGATIVE_SEEK), 0};
349
350 9x offset_ = static_cast<std::uint64_t>(new_pos);
351 9x return {std::error_code{}, offset_};
352 }
353
354 inline std::coroutine_handle<>
355 15x win_stream_file_internal::read_some(
356 std::coroutine_handle<> h,
357 capy::executor_ref ex,
358 buffer_param param,
359 std::stop_token token,
360 std::error_code* ec,
361 std::size_t* bytes_out)
362 {
363 static constexpr std::size_t max_buffers = 16;
364
365 // Keep internal alive during I/O
366
1/1
✓ Branch 2 → 3 taken 15 times.
15x rd_.file_ptr = shared_from_this();
367
368 15x auto& op = rd_;
369 15x op.reset();
370 15x op.is_read = true;
371 15x op.h = h;
372 15x op.ex = ex;
373 15x op.ec_out = ec;
374 15x op.bytes_out = bytes_out;
375 15x op.start(token);
376
377 15x svc_.work_started();
378
379 // Closed-object contract: complete with bad_file_descriptor
380 // without touching the kernel.
381
2/2
✓ Branch 8 → 9 taken 3 times.
✓ Branch 8 → 13 taken 12 times.
15x if (handle_ == INVALID_HANDLE_VALUE)
382 {
383 3x svc_.on_completion(&op, ERROR_INVALID_HANDLE, 0);
384 3x return std::noop_coroutine();
385 }
386
387 // Extract first buffer from buffer_param
388 12x capy::mutable_buffer bufs[max_buffers];
389 12x auto count = param.copy_to(bufs, max_buffers);
390
391
2/2
✓ Branch 14 → 15 taken 1 time.
✓ Branch 14 → 19 taken 11 times.
12x if (count == 0)
392 {
393 // Empty buffer — complete with 0 bytes
394 1x op.empty_buffer = true;
395 1x svc_.on_completion(&op, 0, 0);
396 1x return std::noop_coroutine();
397 }
398
399 // ReadFile uses a single contiguous buffer
400 11x op.buf = bufs[0].data();
401 11x op.buf_len = static_cast<DWORD>(bufs[0].size());
402
403 // Set file offset in OVERLAPPED
404 11x op.Offset = static_cast<DWORD>(offset_ & 0xFFFFFFFF);
405 11x op.OffsetHigh = static_cast<DWORD>(offset_ >> 32);
406
407
1/1
✓ Branch 21 → 22 taken 11 times.
11x BOOL ok = ::ReadFile(handle_, op.buf, op.buf_len, nullptr, &op);
408
2/3
✓ Branch 22 → 23 taken 11 times.
✗ Branch 22 → 25 not taken.
✓ Branch 23 → 24 taken 11 times.
11x DWORD err = ok ? 0 : ::GetLastError();
409
410
3/4
✓ Branch 26 → 27 taken 11 times.
✗ Branch 26 → 32 not taken.
✓ Branch 27 → 28 taken 1 time.
✓ Branch 27 → 32 taken 10 times.
11x if (err != 0 && err != ERROR_IO_PENDING)
411 {
412 1x svc_.on_completion(&op, err, 0);
413 1x return std::noop_coroutine();
414 }
415
416 10x svc_.on_pending(&op);
417
418 // Re-check cancellation after I/O is pending
419
2/2
✓ Branch 34 → 35 taken 1 time.
✓ Branch 34 → 36 taken 9 times.
10x if (op.cancelled.load(std::memory_order_acquire))
420
1/1
✓ Branch 35 → 36 taken 1 time.
1x ::CancelIoEx(handle_, &op);
421
422 10x return std::noop_coroutine();
423 }
424
425 inline std::coroutine_handle<>
426 14x win_stream_file_internal::write_some(
427 std::coroutine_handle<> h,
428 capy::executor_ref ex,
429 buffer_param param,
430 std::stop_token token,
431 std::error_code* ec,
432 std::size_t* bytes_out)
433 {
434 static constexpr std::size_t max_buffers = 16;
435
436 // Keep internal alive during I/O
437
1/1
✓ Branch 2 → 3 taken 14 times.
14x wr_.file_ptr = shared_from_this();
438
439 14x auto& op = wr_;
440 14x op.reset();
441 14x op.h = h;
442 14x op.ex = ex;
443 14x op.ec_out = ec;
444 14x op.bytes_out = bytes_out;
445 14x op.start(token);
446
447 14x svc_.work_started();
448
449 // Closed-object contract: complete with bad_file_descriptor
450 // without touching the kernel.
451
2/2
✓ Branch 8 → 9 taken 3 times.
✓ Branch 8 → 13 taken 11 times.
14x if (handle_ == INVALID_HANDLE_VALUE)
452 {
453 3x svc_.on_completion(&op, ERROR_INVALID_HANDLE, 0);
454 3x return std::noop_coroutine();
455 }
456
457 // Extract first buffer from buffer_param
458 11x capy::mutable_buffer bufs[max_buffers];
459 11x auto count = param.copy_to(bufs, max_buffers);
460
461
2/2
✓ Branch 14 → 15 taken 1 time.
✓ Branch 14 → 19 taken 10 times.
11x if (count == 0)
462 {
463 // Empty buffer — complete with 0 bytes
464 1x svc_.on_completion(&op, 0, 0);
465 1x return std::noop_coroutine();
466 }
467
468 // WriteFile uses a single contiguous buffer
469 10x op.buf = bufs[0].data();
470 10x op.buf_len = static_cast<DWORD>(bufs[0].size());
471
472 // Set file offset in OVERLAPPED
473 10x op.Offset = static_cast<DWORD>(offset_ & 0xFFFFFFFF);
474 10x op.OffsetHigh = static_cast<DWORD>(offset_ >> 32);
475
476
1/1
✓ Branch 21 → 22 taken 10 times.
10x BOOL ok = ::WriteFile(handle_, op.buf, op.buf_len, nullptr, &op);
477
3/3
✓ Branch 22 → 23 taken 2 times.
✓ Branch 22 → 25 taken 8 times.
✓ Branch 23 → 24 taken 2 times.
10x DWORD err = ok ? 0 : ::GetLastError();
478
479
4/4
✓ Branch 26 → 27 taken 2 times.
✓ Branch 26 → 32 taken 8 times.
✓ Branch 27 → 28 taken 1 time.
✓ Branch 27 → 32 taken 1 time.
10x if (err != 0 && err != ERROR_IO_PENDING)
480 {
481 1x svc_.on_completion(&op, err, 0);
482 1x return std::noop_coroutine();
483 }
484
485 9x svc_.on_pending(&op);
486
487 // Re-check cancellation after I/O is pending
488
1/2
✗ Branch 34 → 35 not taken.
✓ Branch 34 → 36 taken 9 times.
9x if (op.cancelled.load(std::memory_order_acquire))
489 ::CancelIoEx(handle_, &op);
490
491 9x return std::noop_coroutine();
492 }
493
494 // ---------------------------------------------------------------------------
495 // win_stream_file wrapper
496 // ---------------------------------------------------------------------------
497
498 inline
499 47x win_stream_file::win_stream_file(
500 47x std::shared_ptr<win_stream_file_internal> internal) noexcept
501 47x : internal_(std::move(internal))
502 {
503 47x }
504
505 inline void
506 47x win_stream_file::close_internal() noexcept
507 {
508
1/2
✓ Branch 3 → 4 taken 47 times.
✗ Branch 3 → 7 not taken.
47x if (internal_)
509 {
510 47x internal_->close_handle();
511 47x internal_.reset();
512 }
513 47x }
514
515 inline std::coroutine_handle<>
516 15x win_stream_file::read_some(
517 std::coroutine_handle<> h,
518 capy::executor_ref d,
519 buffer_param buf,
520 std::stop_token token,
521 std::error_code* ec,
522 std::size_t* bytes)
523 {
524
1/1
✓ Branch 4 → 5 taken 15 times.
15x return internal_->read_some(h, d, buf, token, ec, bytes);
525 }
526
527 inline std::coroutine_handle<>
528 14x win_stream_file::write_some(
529 std::coroutine_handle<> h,
530 capy::executor_ref d,
531 buffer_param buf,
532 std::stop_token token,
533 std::error_code* ec,
534 std::size_t* bytes)
535 {
536
1/1
✓ Branch 4 → 5 taken 14 times.
14x return internal_->write_some(h, d, buf, token, ec, bytes);
537 }
538
539 inline native_handle_type
540 150x win_stream_file::native_handle() const noexcept
541 {
542 150x return reinterpret_cast<native_handle_type>(internal_->native_handle());
543 }
544
545 inline void
546 1x win_stream_file::cancel() noexcept
547 {
548 1x internal_->cancel();
549 1x }
550
551 inline std::uint64_t
552 7x win_stream_file::size() const
553 {
554 7x return internal_->size();
555 }
556
557 inline std::error_code
558 3x win_stream_file::resize(std::uint64_t new_size) noexcept
559 {
560 3x return internal_->resize(new_size);
561 }
562
563 inline std::error_code
564 2x win_stream_file::sync_data() noexcept
565 {
566 2x return internal_->sync_data();
567 }
568
569 inline std::error_code
570 2x win_stream_file::sync_all() noexcept
571 {
572 2x return internal_->sync_all();
573 }
574
575 inline native_handle_type
576 1x win_stream_file::release()
577 {
578 1x return internal_->release();
579 }
580
581 inline std::error_code
582 2x win_stream_file::assign(native_handle_type handle) noexcept
583 {
584 2x return internal_->assign(handle);
585 }
586
587 inline capy::io_result<std::uint64_t>
588 12x win_stream_file::seek(std::int64_t offset, file_base::seek_basis origin) noexcept
589 {
590 12x return internal_->seek(offset, origin);
591 }
592
593 inline win_stream_file_internal*
594 120x win_stream_file::get_internal() const noexcept
595 {
596 120x return internal_.get();
597 }
598
599 // ---------------------------------------------------------------------------
600 // win_file_service
601 // ---------------------------------------------------------------------------
602
603 inline
604 1121x win_file_service::win_file_service(capy::execution_context& ctx)
605 2242x : sched_(ctx.use_service<win_scheduler>())
606 1121x , iocp_(sched_.native_handle())
607
2/2
✓ Branch 3 → 4 taken 1121 times.
✓ Branch 4 → 5 taken 1121 times.
1121x , nt_flush_buffers_file_ex_(nullptr)
608 {
609
3/4
✓ Branch 8 → 9 taken 1121 times.
✓ Branch 9 → 10 taken 1121 times.
✓ Branch 10 → 11 taken 1121 times.
✗ Branch 10 → 12 not taken.
1121x if (FARPROC p = ::GetProcAddress(
610 ::GetModuleHandleA("NTDLL"), "NtFlushBuffersFileEx"))
611 {
612 1121x nt_flush_buffers_file_ex_ = reinterpret_cast<nt_flush_fn>(
613 reinterpret_cast<void*>(p));
614 }
615 1121x }
616
617 inline
618 2242x win_file_service::~win_file_service()
619 {
620
1/2
✗ Branch 6 → 3 not taken.
✓ Branch 6 → 7 taken 1121 times.
1121x for (auto* w = wrapper_list_.pop_front(); w != nullptr;
621 w = wrapper_list_.pop_front())
622 delete w;
623 2242x }
624
625 inline io_object::implementation*
626 47x win_file_service::construct()
627 {
628
1/1
✓ Branch 2 → 3 taken 47 times.
47x auto internal = std::make_shared<win_stream_file_internal>(*this);
629
630 {
631 47x std::lock_guard<win_mutex> lock(mutex_);
632 47x file_list_.push_back(internal.get());
633 47x }
634
635
1/1
✓ Branch 7 → 8 taken 47 times.
47x auto* wrapper = new win_stream_file(std::move(internal));
636
637 {
638 47x std::lock_guard<win_mutex> lock(mutex_);
639 47x wrapper_list_.push_back(wrapper);
640 47x }
641
642 47x return wrapper;
643 47x }
644
645 inline void
646 47x win_file_service::destroy(io_object::implementation* p)
647 {
648
1/2
✓ Branch 2 → 3 taken 47 times.
✗ Branch 2 → 5 not taken.
47x if (p)
649 {
650 47x auto& wrapper = static_cast<win_stream_file&>(*p);
651 47x wrapper.close_internal();
652 47x destroy_impl(wrapper);
653 }
654 47x }
655
656 inline void
657 84x win_file_service::close(io_object::handle& h)
658 {
659 84x auto& wrapper = static_cast<win_stream_file&>(*h.get());
660 84x wrapper.get_internal()->close_handle();
661 84x }
662
663 inline void
664 1121x win_file_service::shutdown()
665 {
666 1121x std::lock_guard<win_mutex> lock(mutex_);
667
668
1/2
✗ Branch 6 → 4 not taken.
✓ Branch 6 → 7 taken 1121 times.
1121x for (auto* impl = file_list_.pop_front(); impl != nullptr;
669 impl = file_list_.pop_front())
670 {
671 impl->close_handle();
672 }
673 1121x }
674
675 inline std::error_code
676 38x win_file_service::open_file(
677 stream_file::implementation& impl,
678 std::filesystem::path const& path,
679 file_base::flags mode)
680 {
681 // Build access mask
682 38x DWORD access = 0;
683 38x unsigned a = static_cast<unsigned>(mode) & 3u;
684
2/2
✓ Branch 2 → 3 taken 2 times.
✓ Branch 2 → 4 taken 36 times.
38x if (a == 3)
685 2x access = GENERIC_READ | GENERIC_WRITE;
686
2/2
✓ Branch 4 → 5 taken 13 times.
✓ Branch 4 → 6 taken 23 times.
36x else if (a == 2)
687 13x access = GENERIC_WRITE;
688 else
689 23x access = GENERIC_READ;
690
691 // Build creation disposition
692 38x DWORD disposition = OPEN_EXISTING;
693
6/6
✓ Branch 8 → 9 taken 11 times.
✓ Branch 8 → 12 taken 27 times.
✓ Branch 10 → 11 taken 1 time.
✓ Branch 10 → 12 taken 10 times.
✓ Branch 13 → 14 taken 1 time.
✓ Branch 13 → 15 taken 37 times.
38x if ((mode & file_base::create) && (mode & file_base::exclusive))
694 1x disposition = CREATE_NEW;
695
6/6
✓ Branch 16 → 17 taken 10 times.
✓ Branch 16 → 20 taken 27 times.
✓ Branch 18 → 19 taken 8 times.
✓ Branch 18 → 20 taken 2 times.
✓ Branch 21 → 22 taken 8 times.
✓ Branch 21 → 23 taken 29 times.
37x else if ((mode & file_base::create) && (mode & file_base::truncate))
696 8x disposition = OPEN_ALWAYS;
697
2/2
✓ Branch 24 → 25 taken 2 times.
✓ Branch 24 → 26 taken 27 times.
29x else if (mode & file_base::create)
698 2x disposition = OPEN_ALWAYS;
699
2/2
✓ Branch 27 → 28 taken 1 time.
✓ Branch 27 → 29 taken 26 times.
27x else if (mode & file_base::truncate)
700 1x disposition = TRUNCATE_EXISTING;
701
702 // Build flags — FILE_FLAG_OVERLAPPED is required for IOCP
703 38x DWORD flags = FILE_ATTRIBUTE_NORMAL
704 | FILE_FLAG_OVERLAPPED
705 | FILE_FLAG_SEQUENTIAL_SCAN;
706
2/2
✓ Branch 30 → 31 taken 1 time.
✓ Branch 30 → 32 taken 37 times.
38x if (mode & file_base::sync_all_on_write)
707 1x flags |= FILE_FLAG_WRITE_THROUGH;
708
709 38x HANDLE h = ::CreateFileW(
710 path.c_str(),
711 access,
712 FILE_SHARE_READ | FILE_SHARE_WRITE,
713 nullptr,
714 disposition,
715 flags,
716 nullptr);
717
718
2/2
✓ Branch 34 → 35 taken 2 times.
✓ Branch 34 → 37 taken 36 times.
38x if (h == INVALID_HANDLE_VALUE)
719 2x return make_err(::GetLastError());
720
721 // Register with IOCP
722
1/2
✗ Branch 38 → 39 not taken.
✓ Branch 38 → 42 taken 36 times.
36x if (!::CreateIoCompletionPort(
723 36x h, static_cast<HANDLE>(iocp_), key_io, 0))
724 {
725 DWORD err = ::GetLastError();
726 ::CloseHandle(h);
727 return make_err(err);
728 }
729
730 // Handle truncation for create|truncate combo
731
2/2
✓ Branch 45 → 46 taken 8 times.
✓ Branch 45 → 48 taken 2 times.
46x if ((mode & file_base::create) && (mode & file_base::truncate)
732
5/6
✓ Branch 43 → 44 taken 10 times.
✓ Branch 43 → 48 taken 26 times.
✓ Branch 46 → 47 taken 8 times.
✗ Branch 46 → 48 not taken.
✓ Branch 49 → 50 taken 8 times.
✓ Branch 49 → 55 taken 28 times.
46x && disposition == OPEN_ALWAYS)
733 {
734
1/2
✗ Branch 51 → 52 not taken.
✓ Branch 51 → 55 taken 8 times.
8x if (!::SetEndOfFile(h))
735 {
736 DWORD err = ::GetLastError();
737 ::CloseHandle(h);
738 return make_err(err);
739 }
740 }
741
742 36x auto& internal = *static_cast<win_stream_file&>(impl).get_internal();
743 36x internal.handle_ = h;
744 36x internal.offset_ = 0;
745
746 // Handle append: seek to end
747
2/2
✓ Branch 57 → 58 taken 2 times.
✓ Branch 57 → 65 taken 34 times.
36x if (mode & file_base::append)
748 {
749 LARGE_INTEGER sz;
750
2/3
✓ Branch 58 → 59 taken 2 times.
✗ Branch 59 → 60 not taken.
✓ Branch 59 → 63 taken 2 times.
2x if (!::GetFileSizeEx(h, &sz))
751 {
752 DWORD err = ::GetLastError();
753 internal.handle_ = INVALID_HANDLE_VALUE;
754 ::CloseHandle(h);
755 return make_err(err);
756 }
757 2x internal.offset_ = static_cast<std::uint64_t>(sz.QuadPart);
758 }
759
760 36x return {};
761 }
762
763 inline void
764 47x win_file_service::destroy_impl(win_stream_file& impl)
765 {
766 {
767 47x std::lock_guard<win_mutex> lock(mutex_);
768 47x wrapper_list_.remove(&impl);
769 47x }
770
1/2
✓ Branch 5 → 6 taken 47 times.
✗ Branch 5 → 7 not taken.
47x delete &impl;
771 47x }
772
773 inline void
774 47x win_file_service::unregister_impl(win_stream_file_internal& impl)
775 {
776 47x std::lock_guard<win_mutex> lock(mutex_);
777 47x file_list_.remove(&impl);
778 47x }
779
780 inline void
781 win_file_service::post(overlapped_op* op)
782 {
783 sched_.post(op);
784 }
785
786 inline void
787 19x win_file_service::on_pending(overlapped_op* op) noexcept
788 {
789 19x sched_.on_pending(op);
790 19x }
791
792 inline void
793 10x win_file_service::on_completion(
794 overlapped_op* op, DWORD error, DWORD bytes) noexcept
795 {
796 10x sched_.on_completion(op, error, bytes);
797 10x }
798
799 inline void
800 29x win_file_service::work_started() noexcept
801 {
802 29x sched_.work_started();
803 29x }
804
805 inline void
806 win_file_service::work_finished() noexcept
807 {
808 sched_.work_finished();
809 }
810
811 inline void*
812 2x win_file_service::iocp_handle() const noexcept
813 {
814 2x return iocp_;
815 }
816
817 inline bool
818 2x win_file_service::try_flush_data(HANDLE h) noexcept
819 {
820
1/2
✓ Branch 2 → 3 taken 2 times.
✗ Branch 2 → 7 not taken.
2x if (nt_flush_buffers_file_ex_)
821 {
822 2x io_status_block status = {};
823 2x if (nt_flush_buffers_file_ex_(
824 h, flush_flags_file_data_sync_only,
825
1/2
✓ Branch 4 → 5 taken 2 times.
✗ Branch 4 → 6 not taken.
2x nullptr, 0, &status) == 0)
826 2x return true;
827 }
828 return false;
829 }
830
831 } // namespace boost::corosio::detail
832
833 #endif // BOOST_COROSIO_HAS_IOCP
834
835 #endif // BOOST_COROSIO_NATIVE_DETAIL_IOCP_WIN_FILE_SERVICE_HPP
836