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

0.0% Lines (0/37) 0.0% List of functions (0/5) 0.0% Branches (0/24)
win_timers_nt.hpp
f(x) Functions (5)
Line TLA Hits Source Code
1 //
2 // Copyright (c) 2025 Vinnie Falco ([email protected])
3 // Copyright (c) 2026 Steve Gerbino
4 //
5 // Distributed under the Boost Software License, Version 1.0. (See accompanying
6 // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
7 //
8 // Official repository: https://github.com/cppalliance/corosio
9 //
10
11 #ifndef BOOST_COROSIO_NATIVE_DETAIL_IOCP_WIN_TIMERS_NT_HPP
12 #define BOOST_COROSIO_NATIVE_DETAIL_IOCP_WIN_TIMERS_NT_HPP
13
14 #include <boost/corosio/detail/platform.hpp>
15
16 #if BOOST_COROSIO_HAS_IOCP
17
18 #include <boost/corosio/detail/config.hpp>
19 #include <boost/corosio/native/detail/iocp/win_timers.hpp>
20 #include <boost/corosio/native/detail/iocp/win_completion_key.hpp>
21 #include <boost/corosio/native/detail/iocp/win_windows.hpp>
22
23 namespace boost::corosio::detail {
24
25 // NT API type definitions
26 using NTSTATUS = LONG;
27
28 using NtAssociateWaitCompletionPacketFn = NTSTATUS(NTAPI*)(
29 void* WaitCompletionPacketHandle,
30 void* IoCompletionHandle,
31 void* TargetObjectHandle,
32 void* KeyContext,
33 void* ApcContext,
34 NTSTATUS IoStatus,
35 ULONG_PTR IoStatusInformation,
36 BOOLEAN* AlreadySignaled);
37
38 using NtCancelWaitCompletionPacketFn = NTSTATUS(NTAPI*)(
39 void* WaitCompletionPacketHandle, BOOLEAN RemoveSignaledPacket);
40
41 class win_timers_nt final : public win_timers
42 {
43 void* iocp_;
44 void* waitable_timer_ = nullptr;
45 void* wait_packet_ = nullptr;
46 NtAssociateWaitCompletionPacketFn nt_associate_;
47 NtCancelWaitCompletionPacketFn nt_cancel_;
48
49 win_timers_nt(
50 void* iocp_handle,
51 long* dispatch_required,
52 NtAssociateWaitCompletionPacketFn nt_assoc,
53 NtCancelWaitCompletionPacketFn nt_cancel);
54
55 public:
56 // Returns nullptr if NT APIs unavailable (pre-Windows 8)
57 static std::unique_ptr<win_timers_nt>
58 try_create(void* iocp_handle, long* dispatch_required);
59
60 ~win_timers_nt();
61
62 win_timers_nt(win_timers_nt const&) = delete;
63 win_timers_nt& operator=(win_timers_nt const&) = delete;
64
65 void start() override;
66 void stop() override;
67 void update_timeout(time_point next_expiry) override;
68
69 private:
70 void associate_timer();
71 };
72
73 /*
74 NT Wait Completion Packet Timer Implementation
75 ==============================================
76
77 This uses undocumented NT APIs to integrate waitable timers directly with
78 IOCP, avoiding the need for a dedicated timer thread.
79
80 CRITICAL: THE ASSOCIATION IS ONE-SHOT
81 -------------------------------------
82
83 When NtAssociateWaitCompletionPacket associates a timer with IOCP, the
84 association is consumed when the timer fires. After the completion packet
85 is posted to IOCP, the wait packet is "spent" and must be re-associated
86 before it can fire again.
87
88 This means update_timeout() MUST be called after every timer wakeup to
89 re-associate the wait packet, even if the timer expiry hasn't changed.
90 The scheduler calls update_timeout() unconditionally in do_one() after
91 processing expired timers for this reason.
92
93 WHY THIS IMPLEMENTATION IS SLOW
94 --------------------------------
95
96 The re-association must happen on every scheduler iteration, even for
97 timer-free workloads. This causes ~60% CPU overhead in benchmarks because
98 SetWaitableTimer + NtAssociateWaitCompletionPacket are called repeatedly.
99
100 DO NOT OPTIMIZE BY SKIPPING RE-ASSOCIATION
101 ------------------------------------------
102
103 It may seem obvious to skip re-association when no timers exist or when the
104 expiry hasn't changed. However, skipping breaks the timer mechanism:
105
106 1. Timer fires -> posts key_wake_dispatch to IOCP
107 2. do_one() processes the completion, calls process_expired()
108 3. If update_timeout() is skipped, the wait packet is not re-associated
109 4. Future timers will never fire -> scheduler hangs
110
111 The correct optimization (if needed) would be at the waitable timer level
112 (caching due_time to avoid redundant SetWaitableTimer calls), but the
113 NtAssociateWaitCompletionPacket call cannot be skipped after any wakeup.
114 */
115
116 inline constexpr NTSTATUS STATUS_SUCCESS = 0;
117
118 using NtCreateWaitCompletionPacketFn = NTSTATUS(NTAPI*)(
119 void** WaitCompletionPacketHandle,
120 ULONG DesiredAccess,
121 void* ObjectAttributes);
122
123 inline win_timers_nt::win_timers_nt(
124 void* iocp_handle,
125 long* dispatch_required,
126 NtAssociateWaitCompletionPacketFn nt_assoc,
127 NtCancelWaitCompletionPacketFn nt_cancel)
128 : win_timers(dispatch_required)
129 , iocp_(iocp_handle)
130 , nt_associate_(nt_assoc)
131 , nt_cancel_(nt_cancel)
132 {
133 waitable_timer_ = ::CreateWaitableTimerW(nullptr, FALSE, nullptr);
134 }
135
136 inline std::unique_ptr<win_timers_nt>
137 win_timers_nt::try_create(void* iocp_handle, long* dispatch_required)
138 {
139 HMODULE ntdll = ::GetModuleHandleW(L"ntdll.dll");
140 if (!ntdll)
141 return nullptr;
142
143 // GetProcAddress returns FARPROC; cast through void* to the specific NT
144 // entry-point signature (same idiom as win_file_service and
145 // win_random_access_file_service). The void* hop avoids GCC/Clang's
146 // -Wcast-function-type without a compiler-specific pragma.
147 auto nt_create = reinterpret_cast<NtCreateWaitCompletionPacketFn>(
148 reinterpret_cast<void*>(
149 ::GetProcAddress(ntdll, "NtCreateWaitCompletionPacket")));
150 auto nt_assoc = reinterpret_cast<NtAssociateWaitCompletionPacketFn>(
151 reinterpret_cast<void*>(
152 ::GetProcAddress(ntdll, "NtAssociateWaitCompletionPacket")));
153 auto nt_cancel = reinterpret_cast<NtCancelWaitCompletionPacketFn>(
154 reinterpret_cast<void*>(
155 ::GetProcAddress(ntdll, "NtCancelWaitCompletionPacket")));
156
157 if (!nt_create || !nt_assoc || !nt_cancel)
158 return nullptr;
159
160 auto p = std::unique_ptr<win_timers_nt>(
161 new win_timers_nt(iocp_handle, dispatch_required, nt_assoc, nt_cancel));
162
163 if (!p->waitable_timer_)
164 return nullptr;
165
166 // Create the wait completion packet
167 NTSTATUS status = nt_create(&p->wait_packet_, MAXIMUM_ALLOWED, nullptr);
168 if (status != STATUS_SUCCESS || !p->wait_packet_)
169 return nullptr;
170
171 return p;
172 }
173
174 inline win_timers_nt::~win_timers_nt()
175 {
176 if (wait_packet_)
177 ::CloseHandle(wait_packet_);
178 if (waitable_timer_)
179 ::CloseHandle(waitable_timer_);
180 }
181
182 inline void
183 win_timers_nt::start()
184 {
185 associate_timer();
186 }
187
188 inline void
189 win_timers_nt::stop()
190 {
191 nt_cancel_(wait_packet_, TRUE);
192 }
193
194 inline void
195 win_timers_nt::update_timeout(time_point next_expiry)
196 {
197 BOOST_COROSIO_ASSERT(waitable_timer_);
198
199 // Cancel pending association
200 nt_cancel_(wait_packet_, FALSE);
201
202 auto now = std::chrono::steady_clock::now();
203 LARGE_INTEGER due_time;
204
205 if (next_expiry <= now)
206 {
207 // Already expired - fire immediately
208 due_time.QuadPart = 0;
209 }
210 else if (next_expiry == (time_point::max)())
211 {
212 // No timers - set far future
213 due_time.QuadPart = -LONGLONG(49) * 24 * 60 * 60 * 10000000LL;
214 }
215 else
216 {
217 // Convert duration to 100ns units (negative = relative)
218 auto duration = next_expiry - now;
219 auto ns = std::chrono::duration_cast<std::chrono::nanoseconds>(duration)
220 .count();
221 due_time.QuadPart = -(ns / 100);
222 if (due_time.QuadPart == 0)
223 due_time.QuadPart = -1;
224 }
225
226 ::SetWaitableTimer(waitable_timer_, &due_time, 0, nullptr, nullptr, FALSE);
227 associate_timer();
228 }
229
230 inline void
231 win_timers_nt::associate_timer()
232 {
233 // Set dispatch flag before associating
234 ::InterlockedExchange(dispatch_required_, 1);
235
236 BOOLEAN already_signaled = FALSE;
237 NTSTATUS status = nt_associate_(
238 wait_packet_, iocp_, waitable_timer_,
239 reinterpret_cast<void*>(key_wake_dispatch), nullptr, STATUS_SUCCESS, 0,
240 &already_signaled);
241
242 if (status == STATUS_SUCCESS && already_signaled)
243 {
244 ::PostQueuedCompletionStatus(
245 static_cast<HANDLE>(iocp_), 0, key_wake_dispatch, nullptr);
246 }
247 }
248
249 } // namespace boost::corosio::detail
250
251 #endif // BOOST_COROSIO_HAS_IOCP
252
253 #endif // BOOST_COROSIO_NATIVE_DETAIL_IOCP_WIN_TIMERS_NT_HPP
254