-
Notifications
You must be signed in to change notification settings - Fork 1.6k
Description
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:
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
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
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