[PATCH v5 6/8] libstdc++ atomic_futex: Avoid rounding errors in std::future::wait_* [PR91486]

Jonathan Wakely jwakely@redhat.com
Fri Sep 11 17:22:04 GMT 2020


On 11/09/20 15:41 +0100, Jonathan Wakely wrote:
>On 29/05/20 07:17 +0100, Mike Crowe via Libstdc++ wrote:
>>Convert the specified duration to the target clock's duration type
>>before adding it to the current time in
>>__atomic_futex_unsigned::_M_load_when_equal_for and
>>_M_load_when_equal_until.  This removes the risk of the timeout being
>>rounded down to the current time resulting in there being no wait at
>>all when the duration type lacks sufficient precision to hold the
>>steady_clock current time.
>>
>>Rather than using the style of fix from PR68519, let's expose the C++17
>>std::chrono::ceil function as std::chrono::__detail::ceil so that it can
>>be used in code compiled with earlier standards versions and simplify
>>the fix. This was suggested by John Salmon in
>>https://gcc.gnu.org/bugzilla/show_bug.cgi?id=91486#c5 .
>>
>>This problem has become considerably less likely to trigger since I
>>switched the __atomic__futex_unsigned::__clock_t reference clock from
>>system_clock to steady_clock and added the loop, but the consequences of
>>triggering it have changed too.
>>
>>By my calculations it takes just over 194 days from the epoch for the
>>current time not to be representable in a float. This means that
>>system_clock is always subject to the problem (with the standard 1970
>>epoch) whereas steady_clock with float duration only runs out of
>>resolution machine has been running for that long (assuming the Linux
>>implementation of CLOCK_MONOTONIC.)
>>
>>The recently-added loop in
>>__atomic_futex_unsigned::_M_load_when_equal_until turns this scenario
>>into a busy wait.
>>
>>Unfortunately the combination of both of these things means that it's
>>not possible to write a test case for this occurring in
>>_M_load_when_equal_until as it stands.
>>
>>	* libstdc++-v3/include/std/chrono: (__detail::ceil) Move
>>         implementation of std::chrono::ceil into private namespace so
>>         that it's available to pre-C++17 code.
>>
>>	* libstdc++-v3/include/bits/atomic_futex.h:
>>	  (__atomic_futex_unsigned::_M_load_when_equal_for,
>>	  __atomic_futex_unsigned::_M_load_when_equal_until): Use
>>	  __detail::ceil to convert delta to the reference clock
>>	  duration type to avoid resolution problems
>>
>>	* libstdc++-v3/testsuite/30_threads/async/async.cc: (test_pr91486):
>>         New test for __atomic_futex_unsigned::_M_load_when_equal_for.
>>---
>>libstdc++-v3/include/bits/atomic_futex.h         |  6 +++--
>>libstdc++-v3/include/std/chrono                  | 19 +++++++++++++----
>>libstdc++-v3/testsuite/30_threads/async/async.cc | 15 +++++++++++++-
>>3 files changed, 34 insertions(+), 6 deletions(-)
>>
>>diff --git a/libstdc++-v3/include/bits/atomic_futex.h b/libstdc++-v3/include/bits/atomic_futex.h
>>index 5f95ade..aa137a7 100644
>>--- a/libstdc++-v3/include/bits/atomic_futex.h
>>+++ b/libstdc++-v3/include/bits/atomic_futex.h
>>@@ -219,8 +219,9 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
>>      _M_load_when_equal_for(unsigned __val, memory_order __mo,
>>	  const chrono::duration<_Rep, _Period>& __rtime)
>>      {
>>+	using __dur = typename __clock_t::duration;
>>	return _M_load_when_equal_until(__val, __mo,
>>-					__clock_t::now() + __rtime);
>>+		    __clock_t::now() + chrono::__detail::ceil<__dur>(__rtime));
>>      }
>>
>>    // Returns false iff a timeout occurred.
>>@@ -233,7 +234,8 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
>>	do {
>>	  const __clock_t::time_point __s_entry = __clock_t::now();
>>	  const auto __delta = __atime - __c_entry;
>>-	  const auto __s_atime = __s_entry + __delta;
>>+	  const auto __s_atime = __s_entry +
>>+	      chrono::__detail::ceil<_Duration>(__delta);

I'm testing the attached patch to fix the C++11 constexpr error, but
while re-looking at the uses of __detail::ceil I noticed this is using
_Duration as the target type. Shouldn't that be __clock_t::duration
instead? Why do we care about the duration of the user's time_point
here, rather than the preferred duration of the clock we're about to
wait against?


>>	  if (_M_load_when_equal_until(__val, __mo, __s_atime))
>>	    return true;
>>	  __c_entry = _Clock::now();
>>diff --git a/libstdc++-v3/include/std/chrono b/libstdc++-v3/include/std/chrono
>>index 6d78f32..4257c7c 100644
>>--- a/libstdc++-v3/include/std/chrono
>>+++ b/libstdc++-v3/include/std/chrono
>>@@ -299,6 +299,20 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
>>#endif
>>#endif // C++20
>>
>>+    // We want to use ceil even when compiling for earlier standards versions
>>+    namespace __detail
>>+    {
>>+      template<typename _ToDur, typename _Rep, typename _Period>
>>+        constexpr __enable_if_is_duration<_ToDur>
>>+        ceil(const duration<_Rep, _Period>& __d)
>>+        {
>>+	  auto __to = chrono::duration_cast<_ToDur>(__d);
>>+	  if (__to < __d)
>>+	    return __to + _ToDur{1};
>>+	  return __to;
>>+	}
>>+    }
>>+
>>#if __cplusplus >= 201703L
>># define __cpp_lib_chrono 201611
>>
>>@@ -316,10 +330,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
>>      constexpr __enable_if_is_duration<_ToDur>
>>      ceil(const duration<_Rep, _Period>& __d)
>>      {
>>-	auto __to = chrono::duration_cast<_ToDur>(__d);
>>-	if (__to < __d)
>>-	  return __to + _ToDur{1};
>>-	return __to;
>>+	return __detail::ceil<_ToDur>(__d);
>
>This isn't valid in C++11, a constexpr function needs to be just a
>return statement. Fix incoming ...



-------------- next part --------------
A non-text attachment was scrubbed...
Name: patch.txt
Type: text/x-patch
Size: 3379 bytes
Desc: not available
URL: <https://gcc.gnu.org/pipermail/libstdc++/attachments/20200911/282d3a60/attachment-0001.bin>


More information about the Libstdc++ mailing list