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 d39a06f962f5df72ae4f2235777f9fa69a7f7216..a67fb0c23ea1fd6ef603094123751f167c6e825c 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 26ee084b9793e48078e758610d515d5dc331b74c..10e1ea5a61bd8f89d140fdc77b0465f8ecf86c0b 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 cbac3fa1932e4530d5d1443773ded89d0806b46c..f821d3fe6a5cf6e13fa0d1efb4d4efa767089259 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 162d0f8542b6a2dcfff315434756600057c5a932..182c60b11ad88385148abb9795c72667af3fba2a 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 fd210335440c39657cd22ec7c9ca7f43779a9fc3..1cf191c452f3eaa54527860d0d5f1e77911fbe29 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 f0bf90f9b9e703363ec0f2ebe6e93dd19d698491..a28dfd971f5273bb9460e25250104e5db13f4d6d 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};
+  }
+};