@@ -122,9 +122,76 @@ template <class Rep, class Period>
122
122
inline bool cond_wait (cond_handle &handle,
123
123
std::chrono::duration<Rep, Period> duration) {
124
124
auto ms = chrono_utils::ceil<std::chrono::milliseconds>(duration);
125
+
126
+ /* If you are paying attention to the next line, you are now asking
127
+
128
+ "Why are you adding 16 - that seems mad?"
129
+
130
+ To explain, we need to understand how the Sleep...() APIs in
131
+ Windows work.
132
+
133
+ The Windows kernel runs a timer, the system tick, which is
134
+ used for scheduling; the timer in question, for historical
135
+ reasons, by default runs at 64Hz. Every time the timer fires,
136
+ Windows updates the tick count, schedules processes and so on.
137
+
138
+ When you ask to sleep, there are two cases:
139
+
140
+ 1. You've asked to sleep for less than a tick, or
141
+
142
+ 2. You've asked to sleep for at least a tick.
143
+
144
+ In case 1, the sleep functions appear to run a delay loop; this
145
+ is by its very nature inaccurate and you may or may not wait less
146
+ than the time you requested. You might also get rescheduled,
147
+ in which case you'll wait at least a whole tick.
148
+
149
+ In case 2, Windows appears to be adding the requested wait to
150
+ its current tick count to work out when to wake your thread.
151
+ That *sounds* sensible, until you realise that if it does this
152
+ towards the end of a tick period, you will wait for that much
153
+ less time. i.e. the sleep functions will return *early* by
154
+ up to one tick period.
155
+
156
+ This is especially unfortunate if you ask to wait for exactly
157
+ one tick period, because you will end up waiting for anything
158
+ between no time at all and a full tick period.
159
+
160
+ To complicate matters, the system tick rate might not be 64Hz;
161
+ the multimedia timer API can cause it to change to as high as
162
+ 1kHz, and on *some* machines this happens globally for all
163
+ processes, while on *other* machines the kernel is actually using
164
+ a separate system tick rate and merely pretending to Win32
165
+ processes that things still work this way.
166
+
167
+ On other platforms, these kinds of functions are guaranteed to
168
+ wait for at least the time you requested, and we'd like for that
169
+ to be true for the Threading package's APIs here. One way to
170
+ achieve that is to add a whole tick's worth of milliseconds
171
+ to the time we're requesting to wait for. In that case, we'll
172
+ avoid the delay loop from case (1), and we'll also guarantee that
173
+ we wait for at least the amount of time the caller of this
174
+ function expected.
175
+
176
+ It would be nice if there were a Windows API we could call to
177
+ obtain the current system tick period. The Internet suggests
178
+ that the undocumented call NtQueryTimerResolution() might be of
179
+ some use for this, but it turns out that that just gives you
180
+ the kernel's idea of the system tick period, which isn't the
181
+ same as Win32's idea necessarily. e.g. on my test system, I
182
+ can see that the tick period is 15.625ms, while that call tells
183
+ me 1ms.
184
+
185
+ We don't want to change the system tick rate using the multimedia
186
+ timer API mentioned earlier, because on some machines that is
187
+ global and will use extra power and CPU cycles.
188
+
189
+ The upshot is that the best choice here appears to be to add
190
+ 15.625ms (1/64s) to the time we're asking to wait. That rounds
191
+ up to 16ms, which is why there's a +16. */
125
192
return SleepConditionVariableSRW (&handle.condition ,
126
193
&handle.lock ,
127
- DWORD (ms.count ()),
194
+ DWORD (ms.count ()) + 16 ,
128
195
0 );
129
196
}
130
197
inline bool cond_wait (cond_handle &handle,
0 commit comments