Avoid fairly easy to trigger overflow in Windows Time.nanos code #4227
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
For millis, micros, and nanos support on Windows, we use the QueryPerformanceCounter as a monotonically increasing "source of time". Given the counter is 64-bits, for an imaginary 2ghz processor that probably gives us about 290ish years of ticks before we would wrap around and our monotonic nature would be broken. All this means, its an excellent "source of time".
To turn the result from the QueryPerformanceCounter into seconds, you divide its value by the QueryPerformanceFrequency. One my computer, the qpf value is 10_000_000.
The previous code for determining nanos on Windows was unfortunately, far more prone to wrapping around than the QueryPerformanceCounter. Previously, we did the following for nanos:
(qpc * 1_000_000_000) / qpf
Because we were multiplying the qpc (a 64 bit integer) by a large integer (1_000_000_000), we were greatly increasing the chance of wraparound.
I discovered this was happening in the wild because we had timers that would "hang" on Windows. It turns out they weren't hanging. What would happen is that eventually, the value from nanos would wrap around and based on the logic in Timers, this would take a 30 second timer and turn it into a 30 minute timer. (Numbers not exact, given more for a feel of the change). This looked like a runtime hang or otherwise a runtime bug, when in actuality, the timer was "doing the correct" thing based on the values it was given.
The change made to millis, micros, and nanos in this commit will greatly decrease our overflow chance with nanos as we are no longer doing the "first multiple by really big number".
Note that we change our algo to use based on whether the qpf value is greater than then number of subseconds to calculate. It is important that we do this so that we don't start returning incorrect values. If we
didn't do that we could end up with a situation like this for millis:
QPC * (1_000 / 10_000_000) which for integer math is
QPC * 0
aka "not good"