Bug 58931 - condition_variable::wait_until overflowed by large time_point<steady_clock>
Summary: condition_variable::wait_until overflowed by large time_point<steady_clock>
Status: NEW
Alias: None
Product: gcc
Classification: Unclassified
Component: libstdc++ (show other bugs)
Version: 4.8.2
: P3 normal
Target Milestone: ---
Assignee: Not yet assigned to anyone
URL:
Keywords:
Depends on:
Blocks:
 
Reported: 2013-10-30 22:46 UTC by Johan Lundberg
Modified: 2025-10-14 20:47 UTC (History)
5 users (show)

See Also:
Host:
Target:
Build:
Known to work:
Known to fail:
Last reconfirmed: 2025-10-14 00:00:00


Attachments
Patch to check for overflow (344 bytes, patch)
2018-01-27 01:28 UTC, Aaron Graham
Details | Diff

Note You need to log in before you can comment on or make changes to this bug.
Description Johan Lundberg 2013-10-30 22:46:21 UTC
With valid but large steady clock time_points, condition_variable.wait_until does not sleep at all, but instead continues as if the time was passed. 

Perhaps related to http://gcc.gnu.org/bugzilla/show_bug.cgi?id=54562

Example: 

#include <chrono>
#include <mutex>
#include <condition_variable>
int main(){
  std::mutex m;
  std::condition_variable cv;
  std::unique_lock<std::mutex> lk(m);
  // does not sleep at all:
  cv.wait_until(lk,
     std::chrono::time_point<std::chrono::steady_clock>::max());
  // sleeps fine:
  //cv.wait_until(lk,
  //   std::chrono::steady_clock::now()+10000*std::chrono::hours{24*365});
}

    cheers / Johan -thanks for a great compiler! 

PS.
* I compiled gcc with --enable-libstdcxx-time=yes. Using 64 bit linux 3.5.0
* The bug does not occur with system_clock.
* I used time_point max() to let a worker thread wait when a queue of delayed events was empty.
Comment 1 Jonathan Wakely 2013-10-31 13:29:30 UTC
I have a fix for PR 54562 so I'll see if it solves this.

N.B. --enable-libstdcxx-time=yes should not be necessary for 4.8 if you have glibc 2.17 or later.
Comment 2 Jonathan Wakely 2013-11-11 12:55:02 UTC
Fixing PR 54562 doesn't help. This can be reduced to

#include <chrono>
#include <cassert>

int main()
{
  using StClock = std::chrono::steady_clock;
  using SysClock = std::chrono::system_clock;
  auto st_atime = std::chrono::time_point<StClock>::max();
  const StClock::time_point st_now = StClock::now();
  const SysClock::time_point sys_now = SysClock::now();
  const auto delta = st_atime - st_now;
  const auto sys_atime = sys_now + delta;
  assert( sys_atime > sys_now );
}
Comment 3 Aaron Graham 2015-05-09 21:29:22 UTC
This is still a problem in current gcc trunk.

The bug is in the condition_variable::wait_until clock conversion. It doesn't check for overflow in that math. Since the steady_clock and system_clock epochs can be very different, it's likely to overflow with values much less than max().

    template<typename _Clock, typename _Duration>
      cv_status
      wait_until(unique_lock<mutex>& __lock,
                 const chrono::time_point<_Clock, _Duration>& __atime)
      {
        // DR 887 - Sync unknown clock to known clock.
        const typename _Clock::time_point __c_entry = _Clock::now();
        const __clock_t::time_point __s_entry = __clock_t::now();
        const auto __delta = __atime - __c_entry;
        const auto __s_atime = __s_entry + __delta;

        return __wait_until_impl(__lock, __s_atime);
      }

I modified my version of gcc to use steady_clock as condition_variable's "known clock" (__clock_t). This is more correct according to the C++ standard and most importantly it makes condition_variable resilient to clock changes when used in conjunction with steady_clock. Because of this, in my case, it works fine with steady_clock::time_point::max(), but fails with system_clock::time_point::max().

Because I made that change and since I don't do timed waits on system_clock (which is unsafe), the overflow hasn't been a problem for me and I haven't fixed it.
Comment 4 Aaron Graham 2015-05-09 22:45:19 UTC
See bug 41861 for discussion of steady_clock wrt condition_variable.
Comment 5 Aaron Graham 2018-01-27 01:28:53 UTC
Created attachment 43261 [details]
Patch to check for overflow
Comment 6 Mike Crowe 2020-10-03 17:55:49 UTC
This problem isn't as serious now that waiting on std::chrono::steady_clock doesn't use the generic wait_until implemenation, but it's worth fixing regardless.

I have a reproduction case and Aaron's fix in attachment 43261 [details] (with some minor tweaks to accommodate the recent addition of __detail::ceil) still appears to work.
Comment 7 Jonathan Wakely 2025-04-30 11:19:07 UTC
#include <thread>
#include <condition_variable>
#include <chrono>
#include <cstdlib>

using namespace std::chrono;

int main()
{
  std::mutex mx;
  std::condition_variable cv;
  std::thread sleepy_abs([&] {
    time_point<system_clock, minutes> end_of_time(minutes::max());
    std::unique_lock<std::mutex> lk(mx);
    // Rather than overflowing to a negative value, the timeout should be
    // truncated to seconds::max() and so sleep for 292 billion years.
    cv.wait_until(lk, end_of_time);
    // This should not happen:
    throw 1;
  });
  std::thread sleepy_rel([&] {
    std::unique_lock<std::mutex> lk(mx);
    // Rather than overflowing to a negative value, the timeout should be
    // truncated to seconds::max() and so sleep for 292 billion years.
    cv.wait_for(lk, minutes::max());
    // This should not happen:
    throw 1;
  });
  // Give the new threads a chance to start sleeping:
  std::this_thread::yield();
  std::this_thread::sleep_for(seconds(2));
  // If we get here without the other threads throwing an exception
  // then it should be sleeping peacefully, so the test passed.
  std::_Exit(0);
}
Comment 8 Jonathan Wakely 2025-04-30 11:19:37 UTC
You don't need to use the steady_clock, overflows are possible with any clock  :-(
Comment 9 GCC Commits 2025-10-14 16:34:48 UTC
The master branch has been updated by Jonathan Wakely <redi@gcc.gnu.org>:

https://gcc.gnu.org/g:5dba17a3e709859968f939354e6e5e8d796012d3

commit r16-4425-g5dba17a3e709859968f939354e6e5e8d796012d3
Author: Jonathan Wakely <jwakely@redhat.com>
Date:   Thu Oct 9 11:09:34 2025 +0100

    libstdc++: Avoid overflow in timeout conversions [PR113327]
    
    When converting from a coarse duration with a very large value, the
    existing code scales that up to chrono::seconds which overflows the
    chrono::seconds::rep type. For example, sleep_for(chrono::hours::max())
    tries to calculate LLONG_MAX * 3600, which overflows to -3600 and so the
    sleep returns immediately.
    
    The solution in this commit is inspired by this_thread::sleep_for in
    libc++ which compares the duration argument to
    chrono::duration<long double>(nanoseconds::max()) and limits the
    duration to nanoseconds::max(). Because we split the duration into
    seconds and nanoseconds, we can use seconds::max() as our upper limit.
    
    We might need to limit further if seconds::max() doesn't fit in the
    type used for sleeping, which is one of std::time_t, unsigned int, or
    chrono::milliseconds.
    
    To fix this everywhere that uses timeouts, new functions are introduced
    for converting from a chrono::duration or chrono::time_point to a
    timespec (or __gthread_time_t which is just a timespec on Linux). These
    functions provide one central place where we can avoid overflow and also
    handle negative timeouts (as these produce errors when passed to OS
    functions that do not accept absolute times before the epoch). All
    negative durations are converted to zero, and negative time_points are
    converted to the epoch.
    
    The new __to_timeout_gthread_time_t function in <bits/std_mutex.h>
    requires adding <bits/chrono.h> to that header, but that only affects
    <syncstream>. All other consumers of <bits/std_mutex.h> were already
    including <bits/chrono.h> for timeouts (e.g. <shared_mutex> and
    <condition_variable>).
    
    libstdc++-v3/ChangeLog:
    
            PR libstdc++/113327
            PR libstdc++/116586
            PR libstdc++/119258
            PR libstdc++/58931
            * include/bits/chrono.h (__to_timeout_timespec): New overloaded
            function templates for converting chrono types to timespec.
            * include/bits/std_mutex.h (__to_timeout_gthread_time_t): New
            function template for converting time_point to __gthread_time_t.
            * include/bits/this_thread_sleep.h (sleep_for): Use
            __to_timeout_timespec.
            (__sleep_for): Remove namespace-scope declaration.
            * include/std/condition_variable: Likewise.
            * include/std/mutex: Likewise.
            * include/std/shared_mutex: Likewise.
            * src/c++11/thread.cc (limit): New helper function.
            (__sleep_for): Use limit to prevent overflow when converting
            chrono::seconds to time_t, unsigned, or chrono::milliseconds.
            * src/c++20/atomic.cc: Use __to_timeout_timespec and
            __to_timeout_gthread_time_t for timeouts.
            * testsuite/30_threads/this_thread/113327.cc: New test.
    
    Reviewed-by: Mike Crowe <mac@mcrowe.com>
    Reviewed-by: Tomasz KamiÅski <tkaminsk@redhat.com>
Comment 10 Jonathan Wakely 2025-10-14 20:47:49 UTC
Comment 0 has been fixed since GCC 10.1 (presumably by Mike's r10-2968-gad4d1d21ad5c51 fix for Bug 41861).

Although many uses of timeout now defend against overflow, the example in comment 7 still has a problem when doing:

	return wait_until(__lock,
			  steady_clock::now() +
			  chrono::__detail::ceil<__dur>(__rtime));

The chrono::ceil call converts minutes::max() to nanoseconds, which overflows.