c++sleepc++-chrono

Why can I not wait for 10^12 picoseconds using std::chrono and long long as a representation?


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

Solution

  • 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.