Skip to content

<thread>: this_thread::sleep_until uses system time even if based on steady_clock #718

@AlexGuteniev

Description

@AlexGuteniev

Describe the bug
Internally, sleep_until calls _To_xtime_10_day_clamped to convert time point of any clock to time point of System clock. Then _Thrd_sleep sleeps on this system time point:

STL/stl/inc/thread

Lines 154 to 166 in 3141965

template <class _Clock, class _Duration>
void sleep_until(const chrono::time_point<_Clock, _Duration>& _Abs_time) {
for (;;) {
const auto _Now = _Clock::now();
if (_Abs_time <= _Now) {
return;
}
_CSTD xtime _Tgt;
(void) _To_xtime_10_day_clamped(_Tgt, _Abs_time - _Now);
_Thrd_sleep(&_Tgt);
}
}

_To_xtime_10_day_clamped uses system_clock

STL/stl/inc/chrono

Lines 629 to 652 in 3141965

// HELPERS
template <class _Rep, class _Period>
_NODISCARD bool _To_xtime_10_day_clamped(_CSTD xtime& _Xt, const chrono::duration<_Rep, _Period>& _Rel_time) noexcept(
is_arithmetic_v<_Rep>) {
// Convert duration to xtime, maximum 10 days from now, returns whether clamping occurred.
// If clamped, timeouts will be transformed into spurious non-timeout wakes, due to ABI restrictions where
// the other side of the DLL boundary overflows int32_t milliseconds.
// Every function calling this one is TRANSITION, ABI
constexpr chrono::nanoseconds _Ten_days{chrono::hours{24} * 10};
constexpr chrono::duration<double> _Ten_days_d{_Ten_days};
chrono::nanoseconds _T0 = chrono::system_clock::now().time_since_epoch();
const bool _Clamped = _Ten_days_d < _Rel_time;
if (_Clamped) {
_T0 += _Ten_days;
} else {
_T0 += chrono::duration_cast<chrono::nanoseconds>(_Rel_time);
}
const auto _Whole_seconds = chrono::duration_cast<chrono::seconds>(_T0);
_Xt.sec = _Whole_seconds.count();
_T0 -= _Whole_seconds;
_Xt.nsec = static_cast<long>(_T0.count());
return _Clamped;
}

If system_clock is adjusted between _To_xtime_10_day_clamped and _Thrd_sleep, the sleep interval is wrong.

sleep_for is also subject to this problem as it is based on sleep_until

STL/stl/inc/thread

Lines 168 to 171 in 3141965

template <class _Rep, class _Period>
void sleep_for(const chrono::duration<_Rep, _Period>& _Rel_time) {
sleep_until(chrono::steady_clock::now() + _Rel_time);
}

Though standard says

The functions whose names end in _­for take an argument that specifies a duration. These functions produce relative timeouts. Implementations should use a steady clock to measure time for these functions.

Command-line test case
Hard to build stable repro, but will try if needed.

Expected behavior
Neither sleep_until relative to a steady clock nor sleep_for should depend on system_clock.

GetTickCount64 is good enough steady clock, as Sleep API does not have granularity less than a millisecond anyway (calling undocumented NtDelayExecution is unacceptable). GetTickCount64 is very fast clock, it is basically reading a global variable.

STL version

Microsoft Visual Studio Professional 2019 Preview
Version 16.6.0 Preview 2.1

Additional context
Suspect that tests for #593 are failing due to timeout because other tests simultaneously tamper with system clock. Ok, this theory is defeated now, but the bug still looks valid

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingfixedSomething works now, yay!

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions