Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What is the maximum value I can pass to std::thread::sleep_for() and sleep_until()?

This question on sleeping forever has an answer that mentions this:

std::this_thread::sleep_until(
    std::chrono::time_point<std::chrono::system_clock>::max());

and this:

std::this_thread::sleep_for(
    std::chrono::system_clock::durat‌​ion::max());

Running this code on Visual C++ 2017 RC actually doesn't sleep at all. I haven't checked out the sleep_until() case, so I'm not sure what's going on there.

In the sleep_for() case, the given duration seems to be converted to an absolute time by adding it to system_clock::now()which is then forwarded to sleep_until(). The problem is that the addition overflows, giving a time in the past.

Looking at the C++17 draft in 30.3.2, neither sleep_until() nor sleep_for() seem to mention limits. There is nothing relevant in Timing specifications (30.2.4). As for duration::max(), it is described in duration_values (20.17.4.3) as: "The value returned shall compare greater than zero()", which isn't helpful at all.

Honestly, I was rather surprised to see sleep_for() fail for system_clock::duration::max(), as it is a construct that make perfect sense to me.

What is the highest value I can pass to those functions that has a well-defined behaviour?

like image 842
isanae Avatar asked Jan 05 '23 09:01

isanae


1 Answers

Technically speaking std::chrono::system_clock::durat‌​ion::max() should sleep for a very long time (longer than you will or your grandchildren will live). And the standard enforces that.

But practically, implementors are still learning how to deal with overflow induced by chrono conversions among durations of different precisions. So bugs are common.

It might be more practical to sleep for 9'000h (a little over a year). There's no way this is going to cause overflow. And it is surely "forever" for your application.

However, don't hesitate to send a bug report to your vendor complaining that std::chrono::system_clock::durat‌​ion::max() doesn't work. It should. It is just tricky to make it work correctly. And making it work isn't portable, so it isn't reasonable to ask you to write some wrapper to do it.


Motivated by isanae's excellent comment below which asks for references:

30.3.3 [thread.thread.this]/p7 which describes sleep_for says:

Effects: Blocks the calling thread for the relative timeout (30.2.4) specified by rel_time.

30.2.4 [thread.req.timing] which is a specification of all the timing requirements in the thread support library, says:

2 Implementations necessarily have some delay in returning from a timeout. Any overhead in interrupt response, function return, and scheduling induces a “quality of implementation” delay, expressed as duration Di. Ideally, this delay would be zero. Further, any contention for processor and memory resources induces a “quality of management” delay, expressed as duration Dm. The delay durations may vary from timeout to timeout, but in all cases shorter is better.

3 The member 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.330 Given a duration argument Dt, the real-time duration of the timeout is Dt + Di + Dm .

Ok, so now I'm amused, because we aren't talking about a member function. We're talking about a namespace-scope function. This is a defect. Feel free to submit one.

But the spec provides no grace to overflow. The spec (nearly) clearly says that the implementation can't return until after the specified delay. It is vague on how much after, but clear on that it can't return before.

If you "bug" STL and he isn't cooperative, just refer him to me, and we will work it out. :-) Perhaps there is a standards bug I'm not seeing, and should be fixed. If so, I can help you file the bug against the standard instead of against VS. Or maybe VS has already addressed this issue, and the fix is available in an upgrade.

If this is a bug in VS, please let STL know that I am more than happy to assist in fixing it. There are different tradeoffs in addressing this issue on different platforms.

At the moment, I can't swear that there isn't a bug of this class in my own implementation (libc++). So no high-horse here. It is a difficult area for a std::lib to get right.

Update

I've looked at the libc++ sleep_for and sleep_until. sleep_for correctly handles the overflow by sleeping for a "long time" (as much as the OS can handle). sleep_until has the overflow bug.

Here is a very lightly tested fixed sleep_until:

template <class _Clock, class _Duration>
void
sleep_until(const chrono::time_point<_Clock, _Duration>& __t)
{
    using namespace chrono;
    using __ldsec = duration<long double>;
    _LIBCPP_CONSTEXPR time_point<_Clock, __ldsec> _Max =
                          time_point<_Clock, nanoseconds>::max();
    time_point<_Clock, nanoseconds> __ns;
    if (__t < _Max)
    {
        __ns = time_point_cast<nanoseconds>(__t);
        if (__ns < __t)
            __ns += nanoseconds{1};
    }
    else
        __ns = time_point<_Clock, nanoseconds>::max();
    mutex __mut;
    condition_variable __cv;
    unique_lock<mutex> __lk(__mut);
    while (_Clock::now() < __ns)
        __cv.wait_until(__lk, __ns);
}

The basic strategy is to do the overflow check using a long double representation which not only has a very large maximum representable value, but also uses saturation arithmetic (has an infinity). If the input value is too big for the OS to handle, truncate it down to something the OS can handle.

On some platforms it might not be desirable to resort to floating point arithmetic. One might use __int128_t instead. Or there is a more involved trick of converting to the "least common multiple" of the input and the native duration before doing the comparison. That conversion will only involve division (not multiplication) and so can't overflow. However it will not always give accurate answers for two values that are nearly equal. But it should work well enough for this use case.

For those interested in the latter (lcm) strategy, here is how to compute that type:

namespace detail
{

template <class Duration0, class ...Durations>
struct lcm_type;

template <class Duration>
struct lcm_type<Duration>
{
    using type = Duration;
};

template <class Duration1, class Duration2>
struct lcm_type<Duration1, Duration2>
{
    template <class D>
    using invert = std::chrono::duration
                   <
                       typename D::rep,
                       std::ratio_divide<std::ratio<1>, typename D::period>
                   >;

    using type = invert<typename std::common_type<invert<Duration1>,
                                                  invert<Duration2>>::type>;
};

template <class Duration0, class Duration1, class Duration2, class ...Durations>
struct lcm_type<Duration0, Duration1, Duration2, Durations...>
{
    using type = typename lcm_type<
                     typename lcm_type<Duration0, Duration1>::type,
                     Duration2, Durations...>::type;
};

}  // namespace detail

One can think of lcm_type<duration1, duration2> as the opposite of common_type<duration1, duration2>. The former finds a duration which the conversion to only divides. The latter finds a duration which the conversion to only multiplies.

like image 127
Howard Hinnant Avatar answered Jan 06 '23 22:01

Howard Hinnant