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

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