Report early wakeup of condition_variable::wait_until as no_timeout
authorMike Crowe <mac@mcrowe.com>
Wed, 1 Aug 2018 15:39:45 +0000 (15:39 +0000)
committerJonathan Wakely <redi@gcc.gnu.org>
Wed, 1 Aug 2018 15:39:45 +0000 (16:39 +0100)
As currently implemented, condition_variable always ultimately waits
against std::chrono::system_clock. This clock can be changed in arbitrary
ways by the user which may result in us waking up too early or too late
when measured against the caller-supplied clock.

We can't (yet) do much about waking up too late (PR 41861), but
if we wake up too early we must return cv_status::no_timeout to indicate a
spurious wakeup rather than incorrectly returning cv_status::timeout.

2018-08-01  Mike Crowe  <mac@mcrowe.com>

* include/std/condition_variable (wait_until): Only report timeout
if we really have timed out when measured against the
caller-supplied clock.
* testsuite/30_threads/condition_variable/members/2.cc: Add test
case to confirm above behaviour.

From-SVN: r263224

libstdc++-v3/ChangeLog
libstdc++-v3/include/std/condition_variable
libstdc++-v3/testsuite/30_threads/condition_variable/members/2.cc

index 283babfe25c07e21c7aa6983bc479c0efc9b9621..dc4cc14153044e1becaa1d46f01aeae8885e1c71 100644 (file)
@@ -1,3 +1,11 @@
+2018-08-01  Mike Crowe  <mac@mcrowe.com>
+
+       * include/std/condition_variable (wait_until): Only report timeout
+       if we really have timed out when measured against the
+       caller-supplied clock.
+       * testsuite/30_threads/condition_variable/members/2.cc: Add test
+       case to confirm above behaviour.
+
 2018-08-01  Jonathan Wakely  <jwakely@redhat.com>
 
        PR libstdc++/60555
index 3f690c81799f05e4ee0b9ea53373a814fd7a4216..c00afa2b7ae2169f7ba81ade07544381a7ec050a 100644 (file)
@@ -117,7 +117,14 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
        const auto __delta = __atime - __c_entry;
        const auto __s_atime = __s_entry + __delta;
 
-       return __wait_until_impl(__lock, __s_atime);
+       if (__wait_until_impl(__lock, __s_atime) == cv_status::no_timeout)
+         return cv_status::no_timeout;
+       // We got a timeout when measured against __clock_t but
+       // we need to check against the caller-supplied clock
+       // to tell whether we should return a timeout.
+       if (_Clock::now() < __atime)
+         return cv_status::no_timeout;
+       return cv_status::timeout;
       }
 
     template<typename _Clock, typename _Duration, typename _Predicate>
index 0a44c4fa7cfd1a29cf245adccc38d23772c7c673..09a717801e1e589e881b1961b7b136d5784edb3f 100644 (file)
@@ -51,8 +51,60 @@ void test01()
     }
 }
 
+struct slow_clock
+{
+  using rep = std::chrono::system_clock::rep;
+  using period = std::chrono::system_clock::period;
+  using duration = std::chrono::system_clock::duration;
+  using time_point = std::chrono::time_point<slow_clock, duration>;
+  static constexpr bool is_steady = false;
+
+  static time_point now()
+  {
+    auto real = std::chrono::system_clock::now();
+    return time_point{real.time_since_epoch() / 3};
+  }
+};
+
+
+void test01_alternate_clock()
+{
+  try
+    {
+      std::condition_variable c1;
+      std::mutex m;
+      std::unique_lock<std::mutex> l(m);
+      auto const expire = slow_clock::now() + std::chrono::seconds(1);
+
+      while (slow_clock::now() < expire)
+       {
+         auto const result = c1.wait_until(l, expire);
+
+         // If wait_until returns before the timeout has expired when
+         // measured against the supplied clock, then wait_until must
+         // return no_timeout.
+         if (slow_clock::now() < expire)
+           VERIFY(result == std::cv_status::no_timeout);
+
+         // If wait_until returns timeout then the timeout must have
+         // expired.
+         if (result == std::cv_status::timeout)
+           VERIFY(slow_clock::now() >= expire);
+       }
+    }
+  catch (const std::system_error& e)
+    {
+      VERIFY( false );
+    }
+  catch (...)
+    {
+      VERIFY( false );
+    }
+}
+
 int main()
 {
   test01();
+  test01_alternate_clock();
   return 0;
 }