[PATCHv3 5/6] libstdc++ futex: Loop when waiting against arbitrary clock

Mike Crowe mac@mcrowe.com
Wed Aug 1 13:19:00 GMT 2018


If std::future::wait_until is passed a time point measured against a clock
that is neither std::chrono::steady_clock nor std::chrono::system_clock
then the generic implementation of
__atomic_futex_unsigned::_M_load_when_equal_until is called which
calculates the timeout based on __clock_t and calls the
_M_load_when_equal_until method for that clock to perform the actual wait.

There's no guarantee that __clock_t is running at the same speed as
__clock_t, so if the underlying wait times out timeout we need to check the
timeout against the caller's clock again before potentially looping.

Also add two extra tests to the testsuite's async.cc:

* run test03 with steady_clock_copy, which behaves identically to
  std::chrono::steady_clock, but isn't std::chrono::steady_clock. This
  causes the overload of __atomic_futex_unsigned::_M_load_when_equal_until
  that takes an arbitrary clock to be called.

* invent test04 which uses a deliberately slow running clock in order to
  exercise the looping behaviour o
  __atomic_futex_unsigned::_M_load_when_equal_until described above.
---
 libstdc++-v3/include/bits/atomic_futex.h         | 15 ++++--
 libstdc++-v3/testsuite/30_threads/async/async.cc | 69 ++++++++++++++++++++++++
 2 files changed, 79 insertions(+), 5 deletions(-)

diff --git a/libstdc++-v3/include/bits/atomic_futex.h b/libstdc++-v3/include/bits/atomic_futex.h
index a35020aef4f..b7ffb7fb191 100644
--- a/libstdc++-v3/include/bits/atomic_futex.h
+++ b/libstdc++-v3/include/bits/atomic_futex.h
@@ -229,11 +229,16 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
       _M_load_when_equal_until(unsigned __val, memory_order __mo,
          const chrono::time_point<_Clock, _Duration>& __atime)
       {
-       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 _M_load_when_equal_until(__val, __mo, __s_atime);
+       typename _Clock::time_point __c_entry = _Clock::now();
+       do {
+         const __clock_t::time_point __s_entry = __clock_t::now();
+         const auto __delta = __atime - __c_entry;
+         const auto __s_atime = __s_entry + __delta;
+         if (_M_load_when_equal_until(__val, __mo, __s_atime))
+           return true;
+         __c_entry = _Clock::now();
+       } while (__c_entry < __atime);
+       return false;
       }

     // Returns false iff a timeout occurred.
diff --git a/libstdc++-v3/testsuite/30_threads/async/async.cc b/libstdc++-v3/testsuite/30_threads/async/async.cc
index 015bcce0c2c..755c95cbea6 100644
--- a/libstdc++-v3/testsuite/30_threads/async/async.cc
+++ b/libstdc++-v3/testsuite/30_threads/async/async.cc
@@ -63,6 +63,27 @@ void test02()
   VERIFY( status == std::future_status::ready );
 }

+// This clock is used to ensure that the
+// __atomic_futex_unsigned::_M_load_when_equal_until overload that
+// takes an arbitrary clock is exercised. Without it, only the
+// specific std::chrono::steady_clock and std::chrono::realtime_clock
+// overloads are exercised.
+
+struct steady_clock_copy
+{
+  using rep = std::chrono::steady_clock::rep;
+  using period = std::chrono::steady_clock::period;
+  using duration = std::chrono::steady_clock::duration;
+  using time_point = std::chrono::time_point<steady_clock_copy, duration>;
+  static constexpr bool is_steady = true;
+
+  static time_point now()
+  {
+    auto steady = std::chrono::steady_clock::now();
+    return time_point{steady.time_since_epoch()};
+  }
+};
+
 // This test is prone to failures if run on a loaded machine where the
 // kernel decides not to schedule us for several seconds. It also
 // assumes that no-one will warp CLOCK whilst the test is
@@ -90,11 +111,59 @@ void test03()
   VERIFY( elapsed < std::chrono::seconds(5) );
 }

+// This clock is supposed to run at a tenth of normal speed, but we
+// don't have to worry about rounding errors causing us to wake up
+// slightly too early below if we actually run it at an eleventh of
+// normal speed.
+struct slow_clock
+{
+  using rep = std::chrono::steady_clock::rep;
+  using period = std::chrono::steady_clock::period;
+  using duration = std::chrono::steady_clock::duration;
+  using time_point = std::chrono::time_point<slow_clock, duration>;
+  static constexpr bool is_steady = true;
+
+  static time_point now()
+  {
+    auto steady = std::chrono::steady_clock::now();
+    return time_point{steady.time_since_epoch() / 11};
+  }
+};
+
+void test04()
+{
+  auto const slow_start = slow_clock::now();
+  future<void> f1 = async(launch::async, []() {
+      std::this_thread::sleep_for(std::chrono::seconds(2));
+    });
+
+  // Wait for ~1s
+  {
+    auto const steady_begin = std::chrono::steady_clock::now();
+    auto const status = f1.wait_until(slow_start + std::chrono::milliseconds(100));
+    VERIFY(status == std::future_status::timeout);
+    auto const elapsed = std::chrono::steady_clock::now() - steady_begin;
+    VERIFY(elapsed >= std::chrono::seconds(1));
+    VERIFY(elapsed < std::chrono::seconds(2));
+  }
+
+  // Wait for up to ~2s more
+  {
+    auto const steady_begin = std::chrono::steady_clock::now();
+    auto const status = f1.wait_until(slow_start + std::chrono::milliseconds(300));
+    auto const elapsed = std::chrono::steady_clock::now() - steady_begin;
+    VERIFY(status == std::future_status::ready);
+    VERIFY(elapsed < std::chrono::seconds(2));
+  }
+}
+
 int main()
 {
   test01();
   test02();
   test03<std::chrono::system_clock>();
   test03<std::chrono::steady_clock>();
+  test03<steady_clock_copy>();
+  test04();
   return 0;
 }
--
2.11.0

BrightSign considers your privacy to be very important. The emails you send to us will be protected and secured. Furthermore, we will only use your email and contact information for the reasons you sent them to us and for tracking how effectively we respond to your requests.



More information about the Libstdc++ mailing list