The following code fails to wait for picoseconds_longlong(1'000'000'000'000)
. Why?
#include <chrono>
#include <iostream>
#include <thread>
using namespace std;
using picoseconds_longlong = chrono::duration<long long, std::ratio<1l, 1'000'000'000'000l>>;
using picoseconds_double = chrono::duration<double, std::ratio<1l, 1'000'000'000'000l>>;
int main() {
cout << chrono::duration_cast<chrono::milliseconds>(chrono::high_resolution_clock::now().time_since_epoch()) << endl;
this_thread::sleep_for(picoseconds_longlong(1'000'000'000'000));
cout << chrono::duration_cast<chrono::milliseconds>(chrono::high_resolution_clock::now().time_since_epoch()) << endl;
cout << chrono::duration_cast<chrono::milliseconds>(chrono::high_resolution_clock::now().time_since_epoch()) << endl;
this_thread::sleep_for(picoseconds_double(1'000'000'000'000));
cout << chrono::duration_cast<chrono::milliseconds>(chrono::high_resolution_clock::now().time_since_epoch()) << endl;
}
Interestingly, waiting for picoseconds_double(1'000'000'000'000)
seems to work fine. Example output:
354170836ms
354170836ms
354170836ms
354171836ms
This is a bug (or possibly the expected behaviour) in MSVC's implementation. sleep_for(time)
is implemented as sleep_until(std::chrono::steady_clock::now() + time)
. This conversion is implemented via the following function:
template <class _Rep, class _Period>
_NODISCARD auto _To_absolute_time(const chrono::duration<_Rep, _Period>& _Rel_time) noexcept {
constexpr auto _Zero = chrono::duration<_Rep, _Period>::zero();
const auto _Now = chrono::steady_clock::now();
decltype(_Now + _Rel_time) _Abs_time = _Now; // return common type
if (_Rel_time > _Zero) {
constexpr auto _Forever = (chrono::steady_clock::time_point::max)();
if (_Abs_time < _Forever - _Rel_time) {
_Abs_time += _Rel_time;
} else {
_Abs_time = _Forever;
}
}
return _Abs_time;
}
chrono::steady_clock::time_point
is implemented in int64_t
nanoseconds. _Forever
is therefore 2^63-1
nanoseconds. When _Forever - _Rel_time
is evaluated, as _Rel_time
is higher precision, _Forever
is automatically converted to picoseconds, this value overflows long long
and _Abs_time < _Forever - _Rel_time
will probably not produce the expected result, resulting in _Abs_time
being set to _Forever
(via another overflowing cast, for me the final value of _Abs_time
is -1000ps
). Inside sleep_until
the time is then compared with std::chrono::steady_clock::now()
and is before that value leading to the sleep being ignored.
A demo of similar code showing the issue: https://godbolt.org/z/GMh9crMe4
A probable fix for the code is to change the type of _Forever
to use the _Rel_time
duration type: https://godbolt.org/z/EPPPcKv8b
Raised as STL bug at https://github.com/microsoft/STL/issues/5234 which has now been closed as fixed so should be fixed in a future MSVC version.