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

99.3% Lines (142 / 143) 93.7% Branches (74 / 79) 100.0% Functions (29 / 29)
win_file_service.hpp
f(x) Functions (29)
Function Calls Lines Branches Blocks
boost::corosio::detail::win_stream_file_internal::size() const :116 7x 100.0% 100.0% 72.7% boost::corosio::detail::win_stream_file_internal::resize(unsigned long long) :125 5x 100.0% 100.0% 90.9% boost::corosio::detail::win_stream_file_internal::sync_data() :137 4x 100.0% 100.0% 88.9% boost::corosio::detail::win_stream_file_internal::sync_all() :148 3x 100.0% 100.0% 83.3% boost::corosio::detail::win_stream_file_internal::seek(long long, boost::corosio::file_base::seek_basis) :156 217x 100.0% 100.0% 64.8% boost::corosio::detail::win_stream_file::win_stream_file(boost::corosio::detail::win_file_service&) :192 314x 100.0% – 100.0% boost::corosio::detail::win_stream_file::retire() :199 315x 100.0% – 100.0% boost::corosio::detail::win_stream_file::reuse() :205 1x 100.0% 50.0% 80.0% boost::corosio::detail::win_stream_file::read_some(boost::capy::continuation&, boost::capy::executor_ref, boost::corosio::detail::buffer_param, std::stop_token, std::error_code*, unsigned long long*) :211 247x 100.0% 100.0% 77.8% boost::corosio::detail::win_stream_file::write_some(boost::capy::continuation&, boost::capy::executor_ref, boost::corosio::detail::buffer_param, std::stop_token, std::error_code*, unsigned long long*) :223 41x 100.0% 100.0% 77.8% boost::corosio::detail::win_stream_file::native_handle() const :235 1427x 100.0% – 100.0% boost::corosio::detail::win_stream_file::cancel() :241 1x 100.0% – 100.0% boost::corosio::detail::win_stream_file::size() const :247 7x 100.0% – 100.0% boost::corosio::detail::win_stream_file::resize(unsigned long long) :253 5x 100.0% – 100.0% boost::corosio::detail::win_stream_file::sync_data() :259 4x 100.0% – 100.0% boost::corosio::detail::win_stream_file::sync_all() :265 3x 100.0% – 100.0% boost::corosio::detail::win_stream_file::release() :271 5x 100.0% – 100.0% boost::corosio::detail::win_stream_file::assign(unsigned long long) :277 208x 100.0% – 100.0% boost::corosio::detail::win_stream_file::seek(long long, boost::corosio::file_base::seek_basis) :283 217x 100.0% – 100.0% boost::corosio::detail::win_stream_file::get_internal() :290 1431x 100.0% – 100.0% boost::corosio::detail::win_file_service::win_file_service(boost::capy::execution_context&) :299 286x 100.0% 80.0% 76.9% boost::corosio::detail::win_file_service::~win_file_service() :312 572x 100.0% – 100.0% boost::corosio::detail::win_file_service::construct() :315 315x 100.0% – 100.0% boost::corosio::detail::win_file_service::destroy(boost::corosio::io_object::implementation*) :321 315x 83.3% 50.0% 83.3% boost::corosio::detail::win_file_service::close(boost::corosio::io_object::handle&) :331 810x 100.0% – 100.0% boost::corosio::detail::win_file_service::shutdown() :337 286x 100.0% 100.0% 100.0% boost::corosio::detail::win_file_service::shutdown()::{lambda(boost::corosio::detail::win_stream_file*)#1}::operator()(boost::corosio::detail::win_stream_file*) const :340 1x 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) :344 326x 100.0% 97.7% 73.6% boost::corosio::detail::win_file_service::try_flush_data(void*) :426 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/file_service.hpp>
19 #include <boost/capy/continuation.hpp>
20 #include <boost/capy/ex/execution_context.hpp>
21 #include <boost/corosio/detail/intrusive.hpp>
22 #include <boost/corosio/detail/object_pool.hpp>
23 #include <boost/corosio/detail/object_ref.hpp>
24 #include <boost/corosio/native/detail/iocp/win_mutex.hpp>
25 #include <boost/corosio/native/detail/iocp/win_stream_file.hpp>
26 #include <boost/corosio/native/detail/iocp/win_scheduler.hpp>
27 #include <boost/corosio/native/detail/iocp/win_completion_key.hpp>
28 #include <boost/corosio/native/detail/make_err.hpp>
29 #include <boost/corosio/detail/dispatch_coro.hpp>
30 #include <boost/corosio/detail/buffer_param.hpp>
31 #include <boost/capy/buffers.hpp>
32
33 #include <filesystem>
34
35 namespace boost::corosio::detail {
36
37 /** Windows IOCP stream file management service.
38
39 Owns all stream file implementations and coordinates their
40 lifecycle with the IOCP.
41
42 @par Thread Safety
43 All public member functions are thread-safe.
44 */
45 class BOOST_COROSIO_DECL win_file_service final : public file_service
46 {
47 friend class win_stream_file;
48
49 public:
50 using key_type = win_file_service;
51
52 explicit win_file_service(capy::execution_context& ctx);
53 ~win_file_service();
54
55 win_file_service(win_file_service const&) = delete;
56 win_file_service& operator=(win_file_service const&) = delete;
57
58 io_object::implementation* construct() override;
59 void destroy(io_object::implementation* p) override;
60 void close(io_object::handle& h) override;
61 void shutdown() override;
62
63 std::error_code open_file(
64 stream_file::implementation& impl,
65 std::filesystem::path const& path,
66 file_base::flags mode) override;
67
68 /** Attempt data-only flush via NtFlushBuffersFileEx.
69
70 @return true if data-only flush succeeded, false if
71 caller should fall back to FlushFileBuffers.
72 */
73 bool try_flush_data(HANDLE h) noexcept;
74
75 private:
76 // NtFlushBuffersFileEx support for data-only sync
77 struct io_status_block
78 {
79 union
80 {
81 LONG Status;
82 void* Pointer;
83 };
84 ULONG_PTR Information;
85 };
86
87 enum
88 {
89 flush_flags_file_data_sync_only = 4
90 };
91
92 using nt_flush_fn =
93 LONG(NTAPI*)(HANDLE, ULONG, void*, ULONG, io_status_block*);
94
95 win_scheduler& sched_;
96 BOOST_COROSIO_MSVC_WARNING_PUSH
97 BOOST_COROSIO_MSVC_WARNING_DISABLE(4251) // detail:: members, dll-interface
98 object_pool<win_stream_file> pool_;
99 BOOST_COROSIO_MSVC_WARNING_POP
100 void* iocp_;
101 nt_flush_fn nt_flush_buffers_file_ex_;
102 };
103
104 /** Get or create the stream file service for the given context. */
105 inline win_file_service&
106 get_stream_file_service(capy::execution_context& ctx, win_scheduler&)
107 {
108 return ctx.make_service<win_file_service>();
109 }
110
111 // ---------------------------------------------------------------------------
112 // win_stream_file_internal
113 // ---------------------------------------------------------------------------
114
115 inline capy::results<std::uint64_t>
116 7x win_stream_file_internal::size() const noexcept
117 {
118 LARGE_INTEGER li;
119
2/2
✓ Branch 3 → 4 taken 1 time.
✓ Branch 3 → 8 taken 6 times.
7x if (!::GetFileSizeEx(handle_, &li))
120 1x return {make_err(::GetLastError()), 0};
121 6x return {std::error_code{}, static_cast<std::uint64_t>(li.QuadPart)};
122 }
123
124 inline std::error_code
125 5x win_stream_file_internal::resize(std::uint64_t new_size) noexcept
126 {
127 LARGE_INTEGER li;
128 5x li.QuadPart = static_cast<LONGLONG>(new_size);
129
2/2
✓ Branch 3 → 4 taken 1 time.
✓ Branch 3 → 6 taken 4 times.
5x if (!::SetFilePointerEx(handle_, li, nullptr, FILE_BEGIN))
130 1x return make_err(::GetLastError());
131
2/2
✓ Branch 7 → 8 taken 2 times.
✓ Branch 7 → 10 taken 2 times.
4x if (!::SetEndOfFile(handle_))
132 2x return make_err(::GetLastError());
133 2x return {};
134 }
135
136 inline std::error_code
137 4x win_stream_file_internal::sync_data() noexcept
138 {
139 // Attempt data-only flush; fall back to full flush
140
2/2
✓ Branch 3 → 4 taken 2 times.
✓ Branch 3 → 5 taken 2 times.
4x if (svc_.try_flush_data(handle_))
141 2x return {};
142
2/2
✓ Branch 6 → 7 taken 1 time.
✓ Branch 6 → 9 taken 1 time.
2x if (!::FlushFileBuffers(handle_))
143 1x return make_err(::GetLastError());
144 1x return {};
145 }
146
147 inline std::error_code
148 3x win_stream_file_internal::sync_all() noexcept
149 {
150
2/2
✓ Branch 3 → 4 taken 1 time.
✓ Branch 3 → 6 taken 2 times.
3x if (!::FlushFileBuffers(handle_))
151 1x return make_err(::GetLastError());
152 2x return {};
153 }
154
155 inline capy::results<std::uint64_t>
156 217x win_stream_file_internal::seek(
157 std::int64_t offset, file_base::seek_basis origin) noexcept
158 {
159 // We manage offset_ ourselves (same as POSIX impl).
160 std::int64_t new_pos;
161
162
2/2
✓ Branch 2 → 3 taken 7 times.
✓ Branch 2 → 4 taken 210 times.
217x if (origin == file_base::seek_set)
163 {
164 7x new_pos = offset;
165 }
166
2/2
✓ Branch 4 → 5 taken 205 times.
✓ Branch 4 → 21 taken 5 times.
210x else if (origin == file_base::seek_cur)
167 {
168 205x new_pos = static_cast<std::int64_t>(
169 410x offset_.load(std::memory_order_acquire)) +
170 offset;
171 }
172 else // seek_end
173 {
174 LARGE_INTEGER li;
175
2/2
✓ Branch 22 → 23 taken 1 time.
✓ Branch 22 → 27 taken 4 times.
5x if (!::GetFileSizeEx(handle_, &li))
176 1x return {make_err(::GetLastError()), 0};
177 4x new_pos = li.QuadPart + offset;
178 }
179
180
2/2
✓ Branch 28 → 29 taken 3 times.
✓ Branch 28 → 32 taken 213 times.
216x if (new_pos < 0)
181 3x return {make_err(ERROR_NEGATIVE_SEEK), 0};
182
183 213x offset_.store(
184 static_cast<std::uint64_t>(new_pos), std::memory_order_release);
185 213x return {std::error_code{}, static_cast<std::uint64_t>(new_pos)};
186 }
187
188 // ---------------------------------------------------------------------------
189 // win_stream_file wrapper
190 // ---------------------------------------------------------------------------
191
192 314x inline win_stream_file::win_stream_file(win_file_service& svc) noexcept
193 314x : svc_(svc)
194 314x , internal_(svc.sched_, *this, svc)
195 {
196 314x }
197
198 inline void
199 315x win_stream_file::retire() noexcept
200 {
201 315x svc_.pool_.recycle(this);
202 315x }
203
204 inline void
205 1x win_stream_file::reuse() noexcept
206 {
207
1/2
✗ Branch 3 → 4 not taken.
✓ Branch 3 → 5 taken 1 time.
1x BOOST_COROSIO_ASSERT(!internal_.is_open());
208 1x }
209
210 inline std::coroutine_handle<>
211 247x win_stream_file::read_some(
212 capy::continuation& cont,
213 capy::executor_ref d,
214 buffer_param buf,
215 std::stop_token token,
216 std::error_code* ec,
217 std::size_t* bytes)
218 {
219
1/1
✓ Branch 3 → 4 taken 247 times.
247x return internal_.read_some(cont, d, buf, token, ec, bytes);
220 }
221
222 inline std::coroutine_handle<>
223 41x win_stream_file::write_some(
224 capy::continuation& cont,
225 capy::executor_ref d,
226 buffer_param buf,
227 std::stop_token token,
228 std::error_code* ec,
229 std::size_t* bytes)
230 {
231
1/1
✓ Branch 3 → 4 taken 41 times.
41x return internal_.write_some(cont, d, buf, token, ec, bytes);
232 }
233
234 inline native_handle_type
235 1427x win_stream_file::native_handle() const noexcept
236 {
237 1427x return reinterpret_cast<native_handle_type>(internal_.native_handle());
238 }
239
240 inline void
241 1x win_stream_file::cancel() noexcept
242 {
243 1x internal_.cancel();
244 1x }
245
246 inline capy::results<std::uint64_t>
247 7x win_stream_file::size() const noexcept
248 {
249 7x return internal_.size();
250 }
251
252 inline std::error_code
253 5x win_stream_file::resize(std::uint64_t new_size) noexcept
254 {
255 5x return internal_.resize(new_size);
256 }
257
258 inline std::error_code
259 4x win_stream_file::sync_data() noexcept
260 {
261 4x return internal_.sync_data();
262 }
263
264 inline std::error_code
265 3x win_stream_file::sync_all() noexcept
266 {
267 3x return internal_.sync_all();
268 }
269
270 inline capy::results<native_handle_type>
271 5x win_stream_file::release() noexcept
272 {
273 5x return internal_.release();
274 }
275
276 inline std::error_code
277 208x win_stream_file::assign(native_handle_type handle) noexcept
278 {
279 208x return internal_.assign(handle, handle_kind::stream_file);
280 }
281
282 inline capy::results<std::uint64_t>
283 217x win_stream_file::seek(
284 std::int64_t offset, file_base::seek_basis origin) noexcept
285 {
286 217x return internal_.seek(offset, origin);
287 }
288
289 inline win_stream_file_internal*
290 1431x win_stream_file::get_internal() noexcept
291 {
292 1431x return &internal_;
293 }
294
295 // ---------------------------------------------------------------------------
296 // win_file_service
297 // ---------------------------------------------------------------------------
298
299 286x inline win_file_service::win_file_service(capy::execution_context& ctx)
300 572x : sched_(ctx.use_service<win_scheduler>())
301 286x , iocp_(sched_.native_handle())
302
1/1
✓ Branch 3 → 4 taken 286 times.
286x , nt_flush_buffers_file_ex_(nullptr)
303 {
304
3/4
✓ Branch 6 → 7 taken 286 times.
✓ Branch 7 → 8 taken 286 times.
✓ Branch 8 → 9 taken 286 times.
✗ Branch 8 → 10 not taken.
286x if (FARPROC p = ::GetProcAddress(
305 ::GetModuleHandleA("NTDLL"), "NtFlushBuffersFileEx"))
306 {
307 286x nt_flush_buffers_file_ex_ =
308 reinterpret_cast<nt_flush_fn>(reinterpret_cast<void*>(p));
309 }
310 286x }
311
312 572x inline win_file_service::~win_file_service() = default;
313
314 inline io_object::implementation*
315 315x win_file_service::construct()
316 {
317 315x return pool_.acquire(*this);
318 }
319
320 inline void
321 315x win_file_service::destroy(io_object::implementation* p)
322 {
323
1/2
✗ Branch 2 → 3 not taken.
✓ Branch 2 → 4 taken 315 times.
315x if (!p)
324 ✗ return;
325 315x auto* f = static_cast<win_stream_file*>(p);
326 315x f->get_internal()->close_handle();
327 315x release(f);
328 }
329
330 inline void
331 810x win_file_service::close(io_object::handle& h)
332 {
333 810x static_cast<win_stream_file&>(*h.get()).get_internal()->close_handle();
334 810x }
335
336 inline void
337 286x win_file_service::shutdown()
338 {
339
1/1
✓ Branch 2 → 3 taken 286 times.
286x pool_.shutdown(
340 1x [](win_stream_file* f) { f->get_internal()->close_handle(); });
341 286x }
342
343 inline std::error_code
344 326x win_file_service::open_file(
345 stream_file::implementation& impl,
346 std::filesystem::path const& path,
347 file_base::flags mode)
348 {
349 // Build access mask
350 326x DWORD access = 0;
351 326x unsigned a = static_cast<unsigned>(mode) & 3u;
352
2/2
✓ Branch 2 → 3 taken 23 times.
✓ Branch 2 → 4 taken 303 times.
326x if (a == 3)
353 23x access = GENERIC_READ | GENERIC_WRITE;
354
2/2
✓ Branch 4 → 5 taken 48 times.
✓ Branch 4 → 6 taken 255 times.
303x else if (a == 2)
355 48x access = GENERIC_WRITE;
356 else
357 255x access = GENERIC_READ;
358
359 // Build creation disposition
360 326x DWORD disposition = OPEN_EXISTING;
361
6/6
✓ Branch 8 → 9 taken 41 times.
✓ Branch 8 → 12 taken 285 times.
✓ Branch 10 → 11 taken 1 time.
✓ Branch 10 → 12 taken 40 times.
✓ Branch 13 → 14 taken 1 time.
✓ Branch 13 → 15 taken 325 times.
326x if ((mode & file_base::create) && (mode & file_base::exclusive))
362 1x disposition = CREATE_NEW;
363
6/6
✓ Branch 16 → 17 taken 40 times.
✓ Branch 16 → 20 taken 285 times.
✓ Branch 18 → 19 taken 17 times.
✓ Branch 18 → 20 taken 23 times.
✓ Branch 21 → 22 taken 17 times.
✓ Branch 21 → 23 taken 308 times.
325x else if ((mode & file_base::create) && (mode & file_base::truncate))
364 17x disposition = OPEN_ALWAYS;
365
2/2
✓ Branch 24 → 25 taken 23 times.
✓ Branch 24 → 26 taken 285 times.
308x else if (mode & file_base::create)
366 23x disposition = OPEN_ALWAYS;
367
2/2
✓ Branch 27 → 28 taken 1 time.
✓ Branch 27 → 29 taken 284 times.
285x else if (mode & file_base::truncate)
368 1x disposition = TRUNCATE_EXISTING;
369
370 // Build flags — FILE_FLAG_OVERLAPPED is required for IOCP
371 326x DWORD flags = FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED |
372 FILE_FLAG_SEQUENTIAL_SCAN;
373
2/2
✓ Branch 30 → 31 taken 1 time.
✓ Branch 30 → 32 taken 325 times.
326x if (mode & file_base::sync_all_on_write)
374 1x flags |= FILE_FLAG_WRITE_THROUGH;
375
376 326x HANDLE h = ::CreateFileW(
377 path.c_str(), access, FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr,
378 disposition, flags, nullptr);
379
380
2/2
✓ Branch 34 → 35 taken 3 times.
✓ Branch 34 → 37 taken 323 times.
326x if (h == INVALID_HANDLE_VALUE)
381 3x return make_err(::GetLastError());
382
383 // Register with IOCP
384
2/2
✓ Branch 38 → 39 taken 9 times.
✓ Branch 38 → 42 taken 314 times.
323x if (!::CreateIoCompletionPort(h, static_cast<HANDLE>(iocp_), key_io, 0))
385 {
386 9x DWORD err = ::GetLastError();
387 9x ::CloseHandle(h);
388 9x return make_err(err);
389 }
390
391 // Handle truncation for create|truncate combo
392
7/8
✓ Branch 43 → 44 taken 30 times.
✓ Branch 43 → 48 taken 284 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 297 times.
314x if ((mode & file_base::create) && (mode & file_base::truncate) &&
393 disposition == OPEN_ALWAYS)
394 {
395
2/2
✓ Branch 51 → 52 taken 9 times.
✓ Branch 51 → 55 taken 8 times.
17x if (!::SetEndOfFile(h))
396 {
397 9x DWORD err = ::GetLastError();
398 9x ::CloseHandle(h);
399 9x return make_err(err);
400 }
401 }
402
403 305x auto& internal = *static_cast<win_stream_file&>(impl).get_internal();
404 305x internal.handle_ = h;
405 305x internal.offset_.store(0, std::memory_order_release);
406
407 // Handle append: seek to end
408
2/2
✓ Branch 77 → 78 taken 11 times.
✓ Branch 77 → 105 taken 294 times.
305x if (mode & file_base::append)
409 {
410 LARGE_INTEGER sz;
411
3/3
✓ Branch 78 → 79 taken 11 times.
✓ Branch 79 → 80 taken 9 times.
✓ Branch 79 → 83 taken 2 times.
11x if (!::GetFileSizeEx(h, &sz))
412 {
413
1/1
✓ Branch 80 → 81 taken 9 times.
9x DWORD err = ::GetLastError();
414 9x internal.handle_ = INVALID_HANDLE_VALUE;
415
1/1
✓ Branch 81 → 82 taken 9 times.
9x ::CloseHandle(h);
416 9x return make_err(err);
417 }
418 2x internal.offset_.store(
419 2x static_cast<std::uint64_t>(sz.QuadPart), std::memory_order_release);
420 }
421
422 296x return {};
423 }
424
425 inline bool
426 4x win_file_service::try_flush_data(HANDLE h) noexcept
427 {
428
1/2
✓ Branch 2 → 3 taken 4 times.
✗ Branch 2 → 7 not taken.
4x if (nt_flush_buffers_file_ex_)
429 {
430 4x io_status_block status = {};
431 4x if (nt_flush_buffers_file_ex_(
432
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)
433 2x return true;
434 }
435 2x return false;
436 }
437
438 } // namespace boost::corosio::detail
439
440 #endif // BOOST_COROSIO_HAS_IOCP
441
442 #endif // BOOST_COROSIO_NATIVE_DETAIL_IOCP_WIN_FILE_SERVICE_HPP
443