libstdc++: Fix timed_mutex::try_lock_until on arbitrary clock (PR 91906)
authorMike Crowe <mac@mcrowe.com>
Mon, 2 Dec 2019 16:23:06 +0000 (16:23 +0000)
committerJonathan Wakely <redi@gcc.gnu.org>
Mon, 2 Dec 2019 16:23:06 +0000 (16:23 +0000)
A non-standard clock may tick more slowly than
std::chrono::steady_clock.  This means that we risk returning false
early when the specified timeout may not have expired. This can be
avoided by looping until the timeout time as reported by the
non-standard clock has been reached.

Unfortunately, we have no way to tell whether the non-standard clock
ticks more quickly that std::chrono::steady_clock. If it does then we
risk returning later than would be expected, but that is unavoidable and
permitted by the standard.

2019-12-02  Mike Crowe  <mac@mcrowe.com>

PR libstdc++/91906 Fix timed_mutex::try_lock_until on arbitrary clock
* include/std/mutex (__timed_mutex_impl::_M_try_lock_until): Loop
until the absolute timeout time is reached as measured against the
appropriate clock.
* testsuite/util/slow_clock.h: New file. Move implementation of
slow_clock test class.
* testsuite/30_threads/condition_variable/members/2.cc: Include
slow_clock from header.
* testsuite/30_threads/shared_timed_mutex/try_lock/3.cc: Convert
existing test to templated function so that it can be called with
both system_clock and steady_clock.
* testsuite/30_threads/timed_mutex/try_lock_until/3.cc: Also run test
using slow_clock to test above fix.
* testsuite/30_threads/recursive_timed_mutex/try_lock_until/3.cc:
Likewise.
* testsuite/30_threads/recursive_timed_mutex/try_lock_until/4.cc: Add
new test that try_lock_until behaves as try_lock if the timeout has
already expired or exactly matches the current time.

From-SVN: r278902

libstdc++-v3/ChangeLog
libstdc++-v3/include/std/mutex
libstdc++-v3/testsuite/30_threads/condition_variable/members/2.cc
libstdc++-v3/testsuite/30_threads/recursive_timed_mutex/try_lock_until/3.cc
libstdc++-v3/testsuite/30_threads/shared_timed_mutex/try_lock/3.cc
libstdc++-v3/testsuite/30_threads/timed_mutex/try_lock_until/3.cc
libstdc++-v3/testsuite/30_threads/timed_mutex/try_lock_until/4.cc [new file with mode: 0644]
libstdc++-v3/testsuite/util/slow_clock.h [new file with mode: 0644]

index d39a06f..a67fb0c 100644 (file)
@@ -1,5 +1,24 @@
 2019-12-02  Mike Crowe  <mac@mcrowe.com>
 
+       PR libstdc++/91906 Fix timed_mutex::try_lock_until on arbitrary clock
+       * include/std/mutex (__timed_mutex_impl::_M_try_lock_until): Loop
+       until the absolute timeout time is reached as measured against the
+       appropriate clock.
+       * testsuite/util/slow_clock.h: New file. Move implementation of
+       slow_clock test class.
+       * testsuite/30_threads/condition_variable/members/2.cc: Include
+       slow_clock from header.
+       * testsuite/30_threads/shared_timed_mutex/try_lock/3.cc: Convert
+       existing test to templated function so that it can be called with
+       both system_clock and steady_clock.
+       * testsuite/30_threads/timed_mutex/try_lock_until/3.cc: Also run test
+       using slow_clock to test above fix.
+       * testsuite/30_threads/recursive_timed_mutex/try_lock_until/3.cc:
+       Likewise.
+       * testsuite/30_threads/recursive_timed_mutex/try_lock_until/4.cc: Add
+       new test that try_lock_until behaves as try_lock if the timeout has
+       already expired or exactly matches the current time.
+
        PR libstdc++/78237 Add full steady_clock support to timed_mutex
        * acinclude.m4 (GLIBCXX_CHECK_PTHREAD_MUTEX_CLOCKLOCK): Define to
        detect presence of pthread_mutex_clocklock function.
index 26ee084..10e1ea5 100644 (file)
@@ -189,8 +189,17 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
        bool
        _M_try_lock_until(const chrono::time_point<_Clock, _Duration>& __atime)
        {
-         auto __rtime = __atime - _Clock::now();
-         return _M_try_lock_for(__rtime);
+         // The user-supplied clock may not tick at the same rate as
+         // steady_clock, so we must loop in order to guarantee that
+         // the timeout has expired before returning false.
+         auto __now = _Clock::now();
+         do {
+           auto __rtime = __atime - __now;
+           if (_M_try_lock_for(__rtime))
+             return true;
+           __now = _Clock::now();
+         } while (__atime > __now);
+         return false;
        }
     };
 
index cbac3fa..f821d3f 100644 (file)
@@ -25,6 +25,7 @@
 #include <condition_variable>
 #include <system_error>
 #include <testsuite_hooks.h>
+#include <slow_clock.h>
 
 template <typename ClockType>
 void test01()
@@ -52,22 +53,6 @@ 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
index 162d0f8..182c60b 100644 (file)
@@ -26,6 +26,7 @@
 #include <thread>
 #include <system_error>
 #include <testsuite_hooks.h>
+#include <slow_clock.h>
 
 template <typename clock_type>
 void test()
@@ -71,4 +72,5 @@ int main()
 {
   test<std::chrono::system_clock>();
   test<std::chrono::steady_clock>();
+  test<slow_clock>();
 }
index fd21033..1cf191c 100644 (file)
@@ -27,7 +27,8 @@
 #include <system_error>
 #include <testsuite_hooks.h>
 
-int main()
+template <typename clock_type>
+void test()
 {
   typedef std::shared_timed_mutex mutex_type;
 
@@ -42,15 +43,15 @@ int main()
           {
             using namespace std::chrono;
             auto timeout = 100ms;
-            auto start = system_clock::now();
+            auto start = clock_type::now();
             b = m.try_lock_for(timeout);
-            auto t = system_clock::now() - start;
+            auto t = clock_type::now() - start;
             VERIFY( !b );
             VERIFY( t >= timeout );
 
-            start = system_clock::now();
+            start = clock_type::now();
             b = m.try_lock_until(start + timeout);
-            t = system_clock::now() - start;
+            t = clock_type::now() - start;
             VERIFY( !b );
             VERIFY( t >= timeout );
           }
@@ -71,3 +72,9 @@ int main()
       VERIFY( false );
     }
 }
+
+int main()
+{
+  test<std::chrono::system_clock>();
+  test<std::chrono::steady_clock>();
+}
index f0bf90f..a28dfd9 100644 (file)
@@ -26,6 +26,7 @@
 #include <thread>
 #include <system_error>
 #include <testsuite_hooks.h>
+#include <slow_clock.h>
 
 template <typename clock_type>
 void test()
@@ -71,4 +72,5 @@ int main()
 {
   test<std::chrono::system_clock>();
   test<std::chrono::steady_clock>();
+  test<slow_clock>();
 }
diff --git a/libstdc++-v3/testsuite/30_threads/timed_mutex/try_lock_until/4.cc b/libstdc++-v3/testsuite/30_threads/timed_mutex/try_lock_until/4.cc
new file mode 100644 (file)
index 0000000..cd94ede
--- /dev/null
@@ -0,0 +1,68 @@
+// { dg-do run }
+// { dg-options "-pthread"  }
+// { dg-require-effective-target c++14 }
+// { dg-require-effective-target pthread }
+// { dg-require-gthreads "" }
+
+// Copyright (C) 2019 Free Software Foundation, Inc.
+//
+// This file is part of the GNU ISO C++ Library.  This library is free
+// software; you can redistribute it and/or modify it under the
+// terms of the GNU General Public License as published by the
+// Free Software Foundation; either version 3, or (at your option)
+// any later version.
+
+// This library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+
+// You should have received a copy of the GNU General Public License along
+// with this library; see the file COPYING3.  If not see
+// <http://www.gnu.org/licenses/>.
+
+
+#include <mutex>
+#include <thread>
+#include <system_error>
+#include <testsuite_hooks.h>
+#include <slow_clock.h>
+
+template <typename clock_type>
+void test()
+{
+  typedef std::timed_mutex mutex_type;
+
+  try
+    {
+      using namespace std::chrono;
+      mutex_type m;
+
+      // Confirm that try_lock_until acts like try_lock if the timeout has
+      // already passed.
+
+      // First test with a timeout that is definitely in the past.
+      VERIFY( m.try_lock_until( clock_type::now() - 1s ) );
+      m.unlock();
+
+      // Then attempt to test with a timeout that might exactly match the
+      // current time.
+      VERIFY( m.try_lock_until( clock_type::now() ) );
+      m.unlock();
+    }
+  catch (const std::system_error& e)
+    {
+      VERIFY( false );
+    }
+  catch (...)
+    {
+      VERIFY( false );
+    }
+}
+
+int main()
+{
+  test<std::chrono::system_clock>();
+  test<std::chrono::steady_clock>();
+  test<slow_clock>();
+}
diff --git a/libstdc++-v3/testsuite/util/slow_clock.h b/libstdc++-v3/testsuite/util/slow_clock.h
new file mode 100644 (file)
index 0000000..b81754e
--- /dev/null
@@ -0,0 +1,38 @@
+// -*- C++ -*-
+
+// Copyright (C) 2019 Free Software Foundation, Inc.
+
+// This library is free software; you can redistribute it and/or
+// modify it under the terms of the GNU General Public License as
+// published by the Free Software Foundation; either version 3, or (at
+// your option) any later version.
+
+// This library is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+// General Public License for more details.
+
+// You should have received a copy of the GNU General Public License
+// along with this library; see the file COPYING3.  If not see
+// <http://www.gnu.org/licenses/>.
+
+
+// A clock that ticks at a third of the speed of system_clock that can be used
+// to ensure that functions with timeouts don't erroneously return early.
+
+#include <chrono>
+
+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};
+  }
+};