[v5,5/8] libstdc++ futex: Loop when waiting against arbitrary clock

Message ID 3e4c0ec6864a254cbc58c32abe80983162ea7f16.1590732962.git-series.mac@mcrowe.com
State New
Headers show
Series
  • std::future::wait_* and std::condition_variable improvements
Related show

Commit Message

Kees Cook via Gcc-patches May 29, 2020, 6:17 a.m.
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 the
caller's clock, 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:
	(__atomic_futex_unsigned) Add loop to _M_load_when_equal_until on
	generic _Clock to check the timeout against _Clock again after
	_M_load_when_equal_until returns indicating a timeout.

	* libstdc++-v3/testsuite/30_threads/async/async.cc: Invent
	slow_clock that runs at an eleventh of steady_clock's speed. Use it
	to test the user-supplied-clock variant of
	__atomic_futex_unsigned::_M_load_when_equal_until works generally
	with test03 and loops correctly when the timeout time hasn't been
	reached in test04.
---
 libstdc++-v3/include/bits/atomic_futex.h         | 15 ++--
 libstdc++-v3/testsuite/30_threads/async/async.cc | 70 +++++++++++++++++-
 2 files changed, 80 insertions(+), 5 deletions(-)

-- 
git-series 0.9.1

Patch

diff --git a/libstdc++-v3/include/bits/atomic_futex.h b/libstdc++-v3/include/bits/atomic_futex.h
index 4375129..5f95ade 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 84d94cf..ee117f4 100644
--- a/libstdc++-v3/testsuite/30_threads/async/async.cc
+++ b/libstdc++-v3/testsuite/30_threads/async/async.cc
@@ -63,6 +63,24 @@  void test02()
   VERIFY( status == std::future_status::ready );
 }
 
+// This clock behaves exactly the same as steady_clock, but it is not
+// steady_clock which means that the generic clock overload of
+// future::wait_until is used.
+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()
+  {
+    const 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 +108,63 @@  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. It is used to exercise the
+// __atomic_futex_unsigned::_M_load_when_equal_until overload that
+// takes an arbitrary clock.
+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()
+  {
+    const auto steady = std::chrono::steady_clock::now();
+    return time_point{steady.time_since_epoch() / 11};
+  }
+};
+
+void test04()
+{
+  using namespace std::chrono;
+
+  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 = steady_clock::now();
+    auto const status = f1.wait_until(slow_start + milliseconds(100));
+    VERIFY(status == std::future_status::timeout);
+    auto const elapsed = steady_clock::now() - steady_begin;
+    VERIFY(elapsed >= seconds(1));
+    VERIFY(elapsed < seconds(2));
+  }
+
+  // Wait for up to ~2s more
+  {
+    auto const steady_begin = steady_clock::now();
+    auto const status = f1.wait_until(slow_start + milliseconds(300));
+    VERIFY(status == std::future_status::ready);
+    auto const elapsed = steady_clock::now() - steady_begin;
+    VERIFY(elapsed < seconds(2));
+  }
+}
+
 int main()
 {
   test01();
   test02();
   test03<std::chrono::system_clock>();
   test03<std::chrono::steady_clock>();
+  test03<steady_clock_copy>();
+  test04();
   return 0;
 }