Passing a timeout from before the epoch to __platform_wait_until()
currently results in an exception being thrown because futex(2) doesn't
support negative timeouts.  Let's just immediately return indicating a
timeout has happened.

Add test cases to prove that this bug is fixed for std::binary_semaphore
(which is just an alias for std::counting_semaphore<1>).  The tests
exercise cases that aren't problematic with the current code since
system_clock is converted to steady_clock before calling
__platform_wait_until() is called but they will protect against changes
in the implementation reintroducing this bug.

        PR libstdc++/116586

    libstdc++-v3/ChangeLog:
        * src/c++20/atomic.cc (__platform_wait_until): Return false on
          timeout before epoch.
        * testsuite/30_threads/semaphore/try_acquire_for.cc: Add tests.
        * testsuite/30_threads/semaphore/try_acquire_until.cc: Add tests.

Signed-off-by: Mike Crowe <m...@mcrowe.com>
---
 libstdc++-v3/src/c++20/atomic.cc              |  4 ++++
 .../30_threads/semaphore/try_acquire_for.cc   | 20 +++++++++++++++++
 .../30_threads/semaphore/try_acquire_until.cc | 22 +++++++++++++++++++
 3 files changed, 46 insertions(+)

diff --git a/libstdc++-v3/src/c++20/atomic.cc b/libstdc++-v3/src/c++20/atomic.cc
index 4120e1a0817..1e987213d80 100644
--- a/libstdc++-v3/src/c++20/atomic.cc
+++ b/libstdc++-v3/src/c++20/atomic.cc
@@ -359,6 +359,10 @@ __platform_wait_until(const __platform_wait_t* __addr,
     static_cast<long>(__ns.count())
   };
 
+  // SYS_futex returns EINVAL if timeout is negative, act as if we timed out 
immediately
+  if (__rt.tv_sec < 0 || __rt.tv_nsec < 0)
+    return false;
+
   if (syscall (SYS_futex, __addr,
               static_cast<int>(__futex_wait_flags::__wait_bitset_private),
               __old, &__rt, nullptr,
diff --git a/libstdc++-v3/testsuite/30_threads/semaphore/try_acquire_for.cc 
b/libstdc++-v3/testsuite/30_threads/semaphore/try_acquire_for.cc
index 39681c7ee56..fe0d704baf8 100644
--- a/libstdc++-v3/testsuite/30_threads/semaphore/try_acquire_for.cc
+++ b/libstdc++-v3/testsuite/30_threads/semaphore/try_acquire_for.cc
@@ -90,9 +90,29 @@ test03()
   s.try_acquire_for(timeout);
 }
 
+// Prove semaphore doesn't suffer from PR116586
+template <typename Clock>
+void
+test_relative(std::chrono::nanoseconds offset)
+{
+  std::binary_semaphore sem(1);
+  VERIFY(sem.try_acquire_for(offset));
+  VERIFY(!sem.try_acquire_for(offset));
+}
+
 int main()
 {
   test01();
   test02();
   test03();
+  for (const std::chrono::nanoseconds offset : {
+      std::chrono::nanoseconds{0},
+      
std::chrono::duration_cast<std::chrono::nanoseconds>(std::chrono::milliseconds{-10}),
+      
std::chrono::duration_cast<std::chrono::nanoseconds>(std::chrono::seconds{-10})
+    }) {
+    test_relative<std::chrono::system_clock>(offset);
+    test_relative<std::chrono::system_clock>(offset - 
std::chrono::system_clock::now().time_since_epoch());
+    test_relative<std::chrono::steady_clock>(offset);
+    test_relative<std::chrono::steady_clock>(offset - 
std::chrono::steady_clock::now().time_since_epoch());
+  }
 }
diff --git a/libstdc++-v3/testsuite/30_threads/semaphore/try_acquire_until.cc 
b/libstdc++-v3/testsuite/30_threads/semaphore/try_acquire_until.cc
index de0068d670a..a00f124b147 100644
--- a/libstdc++-v3/testsuite/30_threads/semaphore/try_acquire_until.cc
+++ b/libstdc++-v3/testsuite/30_threads/semaphore/try_acquire_until.cc
@@ -87,8 +87,30 @@ void test02()
   b.wait(1);
 }
 
+// Prove semaphore doesn't suffer from PR116586
+template <typename Clock>
+void
+test_absolute(std::chrono::nanoseconds offset)
+{
+  std::binary_semaphore sem(1);
+  std::chrono::time_point<Clock> tp(offset);
+  VERIFY(sem.try_acquire_until(tp));
+  VERIFY(!sem.try_acquire_until(tp));
+}
+
 int main()
 {
   test01();
   test02();
+  for (const std::chrono::nanoseconds offset : {
+      // tv_sec == 0, tv_nsec == 0
+      std::chrono::nanoseconds{0},
+      // tv_sec == 0, tv_nsec < 0
+      
std::chrono::duration_cast<std::chrono::nanoseconds>(std::chrono::milliseconds{-10}),
+      // tv_sec < 0
+      
std::chrono::duration_cast<std::chrono::nanoseconds>(std::chrono::seconds{-10})
+    }) {
+    test_absolute<std::chrono::system_clock>(offset);
+    test_absolute<std::chrono::steady_clock>(offset);
+  }
 }
-- 
2.39.5

Reply via email to