aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLouis Dionne <ldionne.2@gmail.com>2024-05-21 15:08:47 -0700
committerGitHub <noreply@github.com>2024-05-21 15:08:47 -0700
commit0012b1ea84e671a9e0c9f7f2d1564315ed9cbcca (patch)
tree17a9405611ece4a1887f12eab6f61a078dffc388
parent3a913d30bef061be9786740e14bacd3fa6d76adc (diff)
downloadllvm-0012b1ea84e671a9e0c9f7f2d1564315ed9cbcca.zip
llvm-0012b1ea84e671a9e0c9f7f2d1564315ed9cbcca.tar.gz
llvm-0012b1ea84e671a9e0c9f7f2d1564315ed9cbcca.tar.bz2
[libc++] Refactor flaky tests for std::shared_lock (#91779)
This makes the tests non-flaky.
-rw-r--r--libcxx/test/std/thread/thread.mutex/thread.lock/thread.lock.shared/thread.lock.shared.cons/mutex.pass.cpp132
-rw-r--r--libcxx/test/std/thread/thread.mutex/thread.lock/thread.lock.shared/thread.lock.shared.locking/lock.pass.cpp140
-rw-r--r--libcxx/test/std/thread/thread.mutex/thread.lock/thread.lock.shared/thread.lock.shared.locking/try_lock.pass.cpp135
3 files changed, 242 insertions, 165 deletions
diff --git a/libcxx/test/std/thread/thread.mutex/thread.lock/thread.lock.shared/thread.lock.shared.cons/mutex.pass.cpp b/libcxx/test/std/thread/thread.mutex/thread.lock/thread.lock.shared/thread.lock.shared.cons/mutex.pass.cpp
index 4940041..ece3301 100644
--- a/libcxx/test/std/thread/thread.mutex/thread.lock/thread.lock.shared/thread.lock.shared.cons/mutex.pass.cpp
+++ b/libcxx/test/std/thread/thread.mutex/thread.lock/thread.lock.shared/thread.lock.shared.cons/mutex.pass.cpp
@@ -5,10 +5,9 @@
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
-//
+
// UNSUPPORTED: no-threads
// UNSUPPORTED: c++03, c++11
-// ALLOW_RETRIES: 2
// <shared_mutex>
@@ -19,9 +18,8 @@
// template<class _Mutex> shared_lock(shared_lock<_Mutex>)
// -> shared_lock<_Mutex>; // C++17
+#include <atomic>
#include <cassert>
-#include <chrono>
-#include <cstdlib>
#include <shared_mutex>
#include <thread>
#include <vector>
@@ -29,77 +27,77 @@
#include "make_test_thread.h"
#include "test_macros.h"
-typedef std::chrono::system_clock Clock;
-typedef Clock::time_point time_point;
-typedef Clock::duration duration;
-typedef std::chrono::milliseconds ms;
-typedef std::chrono::nanoseconds ns;
-
-ms WaitTime = ms(250);
-
-// Thread sanitizer causes more overhead and will sometimes cause this test
-// to fail. To prevent this we give Thread sanitizer more time to complete the
-// test.
-#if !defined(TEST_IS_EXECUTED_IN_A_SLOW_ENVIRONMENT)
-ms Tolerance = ms(50);
-#else
-ms Tolerance = ms(50 * 5);
-#endif
+struct Monitor {
+ bool lock_shared_called = false;
+ bool unlock_shared_called = false;
+};
-std::shared_timed_mutex m;
+struct TrackedMutex {
+ Monitor* monitor = nullptr;
-void f()
-{
- time_point t0 = Clock::now();
- time_point t1;
- {
- std::shared_lock<std::shared_timed_mutex> ul(m);
- t1 = Clock::now();
- }
- ns d = t1 - t0 - WaitTime;
- assert(d < Tolerance); // within tolerance
-}
+ void lock_shared() {
+ if (monitor != nullptr)
+ monitor->lock_shared_called = true;
+ }
+ void unlock_shared() {
+ if (monitor != nullptr)
+ monitor->unlock_shared_called = true;
+ }
+};
-void g()
-{
- time_point t0 = Clock::now();
- time_point t1;
- {
- std::shared_lock<std::shared_timed_mutex> ul(m);
- t1 = Clock::now();
- }
- ns d = t1 - t0;
- assert(d < Tolerance); // within tolerance
-}
+template <class Mutex>
+void test() {
+ // Basic sanity test
+ {
+ Mutex mutex;
+ std::vector<std::thread> threads;
+ std::atomic<bool> ready(false);
+ for (int i = 0; i != 5; ++i) {
+ threads.push_back(support::make_test_thread([&] {
+ while (!ready) {
+ // spin
+ }
-int main(int, char**)
-{
- std::vector<std::thread> v;
- {
- m.lock();
- for (int i = 0; i < 5; ++i)
- v.push_back(support::make_test_thread(f));
- std::this_thread::sleep_for(WaitTime);
- m.unlock();
- for (auto& t : v)
- t.join();
- }
- {
- m.lock_shared();
- for (auto& t : v)
- t = support::make_test_thread(g);
- std::thread q = support::make_test_thread(f);
- std::this_thread::sleep_for(WaitTime);
- m.unlock_shared();
- for (auto& t : v)
- t.join();
- q.join();
+ std::shared_lock<Mutex> lock(mutex);
+ assert(lock.owns_lock());
+ }));
}
+ ready = true;
+ for (auto& t : threads)
+ t.join();
+ }
+
+ // Test CTAD
+ {
+#if TEST_STD_VER >= 17
+ Mutex mutex;
+ std::shared_lock lock(mutex);
+ static_assert(std::is_same<decltype(lock), std::shared_lock<Mutex>>::value);
+#endif
+ }
+}
+
+int main(int, char**) {
#if TEST_STD_VER >= 17
- std::shared_lock sl(m);
- static_assert((std::is_same<decltype(sl), std::shared_lock<decltype(m)>>::value), "" );
+ test<std::shared_mutex>();
#endif
+ test<std::shared_timed_mutex>();
+ test<TrackedMutex>();
+
+ // Use shared_lock with a dummy mutex class that tracks whether each
+ // operation has been called or not.
+ {
+ Monitor monitor;
+ TrackedMutex mutex{&monitor};
+
+ std::shared_lock<TrackedMutex> lock(mutex);
+ assert(monitor.lock_shared_called);
+ assert(lock.owns_lock());
+
+ lock.unlock();
+ assert(monitor.unlock_shared_called);
+ }
return 0;
}
diff --git a/libcxx/test/std/thread/thread.mutex/thread.lock/thread.lock.shared/thread.lock.shared.locking/lock.pass.cpp b/libcxx/test/std/thread/thread.mutex/thread.lock/thread.lock.shared/thread.lock.shared.locking/lock.pass.cpp
index edb7c42..d36ca1d 100644
--- a/libcxx/test/std/thread/thread.mutex/thread.lock/thread.lock.shared/thread.lock.shared.locking/lock.pass.cpp
+++ b/libcxx/test/std/thread/thread.mutex/thread.lock/thread.lock.shared/thread.lock.shared.locking/lock.pass.cpp
@@ -5,10 +5,9 @@
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
-//
+
// UNSUPPORTED: no-threads
// UNSUPPORTED: c++03, c++11
-// ALLOW_RETRIES: 2
// <shared_mutex>
@@ -16,10 +15,9 @@
// void lock();
+#include <atomic>
#include <cassert>
-#include <chrono>
-#include <cstdlib>
-#include <mutex>
+#include <mutex> // std::defer_lock
#include <shared_mutex>
#include <system_error>
#include <thread>
@@ -28,71 +26,99 @@
#include "make_test_thread.h"
#include "test_macros.h"
-std::shared_timed_mutex m;
+struct Monitor {
+ bool lock_shared_called = false;
+ bool unlock_shared_called = false;
+};
-typedef std::chrono::system_clock Clock;
-typedef Clock::time_point time_point;
-typedef Clock::duration duration;
-typedef std::chrono::milliseconds ms;
-typedef std::chrono::nanoseconds ns;
+struct TrackedMutex {
+ Monitor* monitor = nullptr;
-ms WaitTime = ms(250);
+ void lock_shared() {
+ if (monitor != nullptr)
+ monitor->lock_shared_called = true;
+ }
+ void unlock_shared() {
+ if (monitor != nullptr)
+ monitor->unlock_shared_called = true;
+ }
+};
-// Thread sanitizer causes more overhead and will sometimes cause this test
-// to fail. To prevent this we give Thread sanitizer more time to complete the
-// test.
-#if !defined(TEST_IS_EXECUTED_IN_A_SLOW_ENVIRONMENT)
-ms Tolerance = ms(25);
-#else
-ms Tolerance = ms(25 * 5);
-#endif
+template <class Mutex>
+void test() {
+ // Basic sanity test
+ {
+ Mutex mutex;
+ std::vector<std::thread> threads;
+ std::atomic<bool> ready(false);
+ for (int i = 0; i != 5; ++i) {
+ threads.push_back(support::make_test_thread([&] {
+ while (!ready) {
+ // spin
+ }
+ std::shared_lock<Mutex> lock(mutex, std::defer_lock);
+ lock.lock();
+ assert(lock.owns_lock());
+ }));
+ }
+
+ ready = true;
+ for (auto& t : threads)
+ t.join();
+ }
-void f()
-{
- std::shared_lock<std::shared_timed_mutex> lk(m, std::defer_lock);
- time_point t0 = Clock::now();
- lk.lock();
- time_point t1 = Clock::now();
- assert(lk.owns_lock() == true);
- ns d = t1 - t0 - WaitTime;
- assert(d < Tolerance); // within tolerance
+ // Try locking the same shared_lock again in the same thread. This should throw an exception.
+ {
+ Mutex mutex;
+ std::shared_lock<Mutex> lock(mutex, std::defer_lock);
+ lock.lock();
+ assert(lock.owns_lock());
#ifndef TEST_HAS_NO_EXCEPTIONS
- try
- {
- lk.lock();
- assert(false);
- }
- catch (std::system_error& e)
- {
- assert(e.code().value() == EDEADLK);
+ try {
+ lock.lock();
+ assert(false);
+ } catch (std::system_error const& e) {
+ assert(e.code() == std::errc::resource_deadlock_would_occur);
}
#endif
- lk.unlock();
- lk.release();
+ }
+
+ // Try locking a shared_lock that isn't associated to any mutex. This should throw an exception.
+ {
+ std::shared_lock<Mutex> lock; // no associated mutex
#ifndef TEST_HAS_NO_EXCEPTIONS
- try
- {
- lk.lock();
- assert(false);
- }
- catch (std::system_error& e)
- {
- assert(e.code().value() == EPERM);
+ try {
+ lock.lock();
+ assert(false);
+ } catch (std::system_error const& e) {
+ assert(e.code() == std::errc::operation_not_permitted);
}
#endif
+ }
}
-int main(int, char**)
-{
- m.lock();
- std::vector<std::thread> v;
- for (int i = 0; i < 5; ++i)
- v.push_back(support::make_test_thread(f));
- std::this_thread::sleep_for(WaitTime);
- m.unlock();
- for (auto& t : v)
- t.join();
+int main(int, char**) {
+#if TEST_STD_VER >= 17
+ test<std::shared_mutex>();
+#endif
+ test<std::shared_timed_mutex>();
+ test<TrackedMutex>();
+
+ // Use shared_lock with a dummy mutex class that tracks whether each
+ // operation has been called or not.
+ {
+ Monitor monitor;
+ TrackedMutex mutex{&monitor};
+
+ std::shared_lock<TrackedMutex> lock(mutex, std::defer_lock);
+ lock.lock();
+ assert(monitor.lock_shared_called);
+ assert(lock.owns_lock());
+
+ lock.unlock();
+ assert(monitor.unlock_shared_called);
+ }
return 0;
}
diff --git a/libcxx/test/std/thread/thread.mutex/thread.lock/thread.lock.shared/thread.lock.shared.locking/try_lock.pass.cpp b/libcxx/test/std/thread/thread.mutex/thread.lock/thread.lock.shared/thread.lock.shared.locking/try_lock.pass.cpp
index 0e707fc..b614668 100644
--- a/libcxx/test/std/thread/thread.mutex/thread.lock/thread.lock.shared/thread.lock.shared.locking/try_lock.pass.cpp
+++ b/libcxx/test/std/thread/thread.mutex/thread.lock/thread.lock.shared/thread.lock.shared.locking/try_lock.pass.cpp
@@ -5,11 +5,9 @@
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
-//
+
// UNSUPPORTED: no-threads
// UNSUPPORTED: c++03, c++11
-//
-// ALLOW_RETRIES: 2
// <shared_mutex>
@@ -17,60 +15,115 @@
// bool try_lock();
+#include <atomic>
#include <cassert>
-#include <mutex>
+#include <mutex> // std::defer_lock
#include <shared_mutex>
#include <system_error>
+#include <thread>
+#include <vector>
+#include "make_test_thread.h"
#include "test_macros.h"
-bool try_lock_called = false;
+struct Monitor {
+ bool try_lock_shared_called = false;
+ bool unlock_shared_called = false;
+};
-struct mutex
-{
- bool try_lock_shared()
- {
- try_lock_called = !try_lock_called;
- return try_lock_called;
- }
- void unlock_shared() {}
+struct TrackedMutex {
+ Monitor* monitor = nullptr;
+
+ bool try_lock_shared() {
+ if (monitor != nullptr)
+ monitor->try_lock_shared_called = true;
+ return true;
+ }
+ void unlock_shared() {
+ if (monitor != nullptr)
+ monitor->unlock_shared_called = true;
+ }
};
-mutex m;
+template <class Mutex>
+void test() {
+ // Basic sanity test
+ {
+ Mutex mutex;
+ std::vector<std::thread> threads;
+ std::atomic<bool> ready(false);
+ for (int i = 0; i != 5; ++i) {
+ threads.push_back(support::make_test_thread([&] {
+ while (!ready) {
+ // spin
+ }
-int main(int, char**)
-{
- std::shared_lock<mutex> lk(m, std::defer_lock);
- assert(lk.try_lock() == true);
- assert(try_lock_called == true);
- assert(lk.owns_lock() == true);
-#ifndef TEST_HAS_NO_EXCEPTIONS
- try
- {
- TEST_IGNORE_NODISCARD lk.try_lock();
- assert(false);
+ std::shared_lock<Mutex> lock(mutex, std::defer_lock);
+ bool result = lock.try_lock();
+ assert(result);
+ assert(lock.owns_lock());
+ }));
}
- catch (std::system_error& e)
- {
- assert(e.code().value() == EDEADLK);
+
+ ready = true;
+ for (auto& t : threads)
+ t.join();
+ }
+
+ // Make sure that we throw an exception if we try to re-lock a mutex that is
+ // already locked by the current thread.
+ {
+ Mutex mutex;
+
+ std::shared_lock<Mutex> lock(mutex, std::defer_lock);
+ assert(lock.try_lock());
+ assert(lock.owns_lock());
+#ifndef TEST_HAS_NO_EXCEPTIONS
+ try {
+ TEST_IGNORE_NODISCARD lock.try_lock();
+ assert(false);
+ } catch (std::system_error const& e) {
+ assert(e.code() == std::errc::resource_deadlock_would_occur);
}
#endif
- lk.unlock();
- assert(lk.try_lock() == false);
- assert(try_lock_called == false);
- assert(lk.owns_lock() == false);
- lk.release();
+ }
+
+ // Make sure that we throw an exception if we try to lock a shared_lock
+ // that is not associated to any mutex.
+ {
+ std::shared_lock<Mutex> lock; // not associated to a mutex
#ifndef TEST_HAS_NO_EXCEPTIONS
- try
- {
- TEST_IGNORE_NODISCARD lk.try_lock();
- assert(false);
- }
- catch (std::system_error& e)
- {
- assert(e.code().value() == EPERM);
+ try {
+ TEST_IGNORE_NODISCARD lock.try_lock();
+ assert(false);
+ } catch (std::system_error const& e) {
+ assert(e.code() == std::errc::operation_not_permitted);
}
#endif
+ }
+}
+
+int main(int, char**) {
+#if TEST_STD_VER >= 17
+ test<std::shared_mutex>();
+#endif
+ test<std::shared_timed_mutex>();
+ test<TrackedMutex>();
+
+ // Use shared_lock with a dummy mutex class that tracks whether each
+ // operation has been called or not.
+ {
+ Monitor monitor;
+ TrackedMutex mutex{&monitor};
+
+ std::shared_lock<TrackedMutex> lock(mutex, std::defer_lock);
+ bool result = lock.try_lock();
+ assert(result);
+ assert(monitor.try_lock_shared_called);
+ assert(lock.owns_lock());
+ lock.unlock();
+ assert(monitor.unlock_shared_called);
+ }
return 0;
}