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

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