KEMBAR78
diff --git a/libcxx/test/std/thread/thread.mutex/thread.mutex.requirements/thread.mutex.requirements.mutex/thread.mutex.class/assign.compile.fail.cpp b/libcxx/test/std/thread/thread.mutex/thread.mutex.requirements/thread.mutex.requirements.mutex/thread.mutex.class/assign.compile.pass.cpp similarity index 80% rename from libcxx/test/std/thread/thread.mutex/thread.mutex.requirements/thread.mutex.requirements.mutex/thread.mutex.class/assign.compile.fail.cpp rename to libcxx/test/std/thread/thread.mutex/thread.mutex.requirements/thread.mutex.requirements.mutex/thread.mutex.class/assign.compile.pass.cpp index ba09ed1a706ea..5f5274a6c0027 100644 --- a/libcxx/test/std/thread/thread.mutex/thread.mutex.requirements/thread.mutex.requirements.mutex/thread.mutex.class/assign.compile.fail.cpp +++ b/libcxx/test/std/thread/thread.mutex/thread.mutex.requirements/thread.mutex.requirements.mutex/thread.mutex.class/assign.compile.pass.cpp @@ -6,6 +6,8 @@ // //===----------------------------------------------------------------------===// +// UNSUPPORTED: no-threads + // // class mutex; @@ -13,12 +15,6 @@ // mutex& operator=(const mutex&) = delete; #include +#include -int main(int, char**) -{ - std::mutex m0; - std::mutex m1; - m1 = m0; - - return 0; -} +static_assert(!std::is_copy_assignable::value, ""); diff --git a/libcxx/test/std/thread/thread.mutex/thread.mutex.requirements/thread.mutex.requirements.mutex/thread.mutex.class/copy.compile.fail.cpp b/libcxx/test/std/thread/thread.mutex/thread.mutex.requirements/thread.mutex.requirements.mutex/thread.mutex.class/ctor.copy.compile.pass.cpp similarity index 79% rename from libcxx/test/std/thread/thread.mutex/thread.mutex.requirements/thread.mutex.requirements.mutex/thread.mutex.class/copy.compile.fail.cpp rename to libcxx/test/std/thread/thread.mutex/thread.mutex.requirements/thread.mutex.requirements.mutex/thread.mutex.class/ctor.copy.compile.pass.cpp index 9edfb7267dee6..74d0dfda41ad1 100644 --- a/libcxx/test/std/thread/thread.mutex/thread.mutex.requirements/thread.mutex.requirements.mutex/thread.mutex.class/copy.compile.fail.cpp +++ b/libcxx/test/std/thread/thread.mutex/thread.mutex.requirements/thread.mutex.requirements.mutex/thread.mutex.class/ctor.copy.compile.pass.cpp @@ -6,6 +6,8 @@ // //===----------------------------------------------------------------------===// +// UNSUPPORTED: no-threads + // // class mutex; @@ -13,11 +15,6 @@ // mutex(const mutex&) = delete; #include +#include -int main(int, char**) -{ - std::mutex m0; - std::mutex m1(m0); - - return 0; -} +static_assert(!std::is_copy_constructible::value, ""); diff --git a/libcxx/test/std/thread/thread.mutex/thread.mutex.requirements/thread.mutex.requirements.mutex/thread.mutex.class/default.pass.cpp b/libcxx/test/std/thread/thread.mutex/thread.mutex.requirements/thread.mutex.requirements.mutex/thread.mutex.class/ctor.default.pass.cpp similarity index 64% rename from libcxx/test/std/thread/thread.mutex/thread.mutex.requirements/thread.mutex.requirements.mutex/thread.mutex.class/default.pass.cpp rename to libcxx/test/std/thread/thread.mutex/thread.mutex.requirements/thread.mutex.requirements.mutex/thread.mutex.class/ctor.default.pass.cpp index d8115cd1bf3a0..7bda43087063a 100644 --- a/libcxx/test/std/thread/thread.mutex/thread.mutex.requirements/thread.mutex.requirements.mutex/thread.mutex.class/default.pass.cpp +++ b/libcxx/test/std/thread/thread.mutex/thread.mutex.requirements/thread.mutex.requirements.mutex/thread.mutex.class/ctor.default.pass.cpp @@ -5,24 +5,28 @@ // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception // //===----------------------------------------------------------------------===// -// + // UNSUPPORTED: no-threads // // class mutex; -// mutex(); +// mutex() noexcept; #include +#include #include -#include "test_macros.h" +static_assert(std::is_nothrow_default_constructible::value, ""); + +int main(int, char**) { + // The mutex is unlocked after default construction + { + std::mutex m; + assert(m.try_lock()); + m.unlock(); + } -int main(int, char**) -{ - static_assert(std::is_nothrow_default_constructible::value, ""); - std::mutex m; - ((void)m); return 0; } diff --git a/libcxx/test/std/thread/thread.mutex/thread.mutex.requirements/thread.mutex.requirements.mutex/thread.mutex.class/lock.pass.cpp b/libcxx/test/std/thread/thread.mutex/thread.mutex.requirements/thread.mutex.requirements.mutex/thread.mutex.class/lock.pass.cpp index b3e76cf886c4d..e2bd2de84c33c 100644 --- a/libcxx/test/std/thread/thread.mutex/thread.mutex.requirements/thread.mutex.requirements.mutex/thread.mutex.class/lock.pass.cpp +++ b/libcxx/test/std/thread/thread.mutex/thread.mutex.requirements/thread.mutex.requirements.mutex/thread.mutex.class/lock.pass.cpp @@ -5,9 +5,9 @@ // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception // //===----------------------------------------------------------------------===// -// + +// UNSUPPORTED: c++03 // UNSUPPORTED: no-threads -// ALLOW_RETRIES: 2 // @@ -15,40 +15,67 @@ // void lock(); -#include -#include -#include #include +#include +#include #include +#include #include "make_test_thread.h" -#include "test_macros.h" -std::mutex m; - -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; - -void f() -{ - time_point t0 = Clock::now(); +int main(int, char**) { + // Lock a mutex that is not locked yet. This should succeed. + { + std::mutex m; m.lock(); - time_point t1 = Clock::now(); m.unlock(); - ns d = t1 - t0 - ms(250); - assert(d < ms(50)); // within 50ms -} + } -int main(int, char**) -{ + // Lock a mutex that is already locked. This should block until it is unlocked. + { + std::atomic ready(false); + std::mutex m; m.lock(); - std::thread t = support::make_test_thread(f); - std::this_thread::sleep_for(ms(250)); + std::atomic is_locked_from_main(true); + + std::thread t = support::make_test_thread([&] { + ready = true; + m.lock(); + assert(!is_locked_from_main); + m.unlock(); + }); + + while (!ready) + /* spin */; + + // We would rather signal this after we unlock, but that would create a race condition. + // We instead signal it before we unlock, which means that it's technically possible for + // the thread to take the lock while main is still holding it yet for the test to still pass. + is_locked_from_main = false; m.unlock(); + t.join(); + } + + // Make sure that at most one thread can acquire the mutex concurrently. + { + std::atomic counter(0); + std::mutex mutex; + + std::vector threads; + for (int i = 0; i != 10; ++i) { + threads.push_back(support::make_test_thread([&] { + mutex.lock(); + counter++; + assert(counter == 1); + counter--; + mutex.unlock(); + })); + } + + for (auto& t : threads) + t.join(); + } return 0; } diff --git a/libcxx/test/std/thread/thread.mutex/thread.mutex.requirements/thread.mutex.requirements.mutex/thread.mutex.class/try_lock.pass.cpp b/libcxx/test/std/thread/thread.mutex/thread.mutex.requirements/thread.mutex.requirements.mutex/thread.mutex.class/try_lock.pass.cpp index bf3cb6530b3b9..db8b809c08d36 100644 --- a/libcxx/test/std/thread/thread.mutex/thread.mutex.requirements/thread.mutex.requirements.mutex/thread.mutex.class/try_lock.pass.cpp +++ b/libcxx/test/std/thread/thread.mutex/thread.mutex.requirements/thread.mutex.requirements.mutex/thread.mutex.class/try_lock.pass.cpp @@ -5,9 +5,9 @@ // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception // //===----------------------------------------------------------------------===// -// + +// UNSUPPORTED: c++03 // UNSUPPORTED: no-threads -// ALLOW_RETRIES: 2 // @@ -15,44 +15,36 @@ // bool try_lock(); -#include -#include -#include #include +#include #include #include "make_test_thread.h" -#include "test_macros.h" - -std::mutex m; - -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; - -void f() -{ - time_point t0 = Clock::now(); - assert(!m.try_lock()); - assert(!m.try_lock()); - assert(!m.try_lock()); - while(!m.try_lock()) - ; - time_point t1 = Clock::now(); + +int main(int, char**) { + // Try to lock a mutex that is not locked yet. This should succeed. + { + std::mutex m; + bool succeeded = m.try_lock(); + assert(succeeded); m.unlock(); - ns d = t1 - t0 - ms(250); - assert(d < ms(200)); // within 200ms -} + } -int main(int, char**) -{ + // Try to lock a mutex that is already locked. This should fail. + { + std::mutex m; m.lock(); - std::thread t = support::make_test_thread(f); - std::this_thread::sleep_for(ms(250)); - m.unlock(); + + std::thread t = support::make_test_thread([&] { + for (int i = 0; i != 10; ++i) { + bool succeeded = m.try_lock(); + assert(!succeeded); + } + }); t.join(); + m.unlock(); + } + return 0; } diff --git a/libcxx/test/std/thread/thread.mutex/thread.mutex.requirements/thread.mutex.requirements.mutex/thread.mutex.recursive/assign.compile.fail.cpp b/libcxx/test/std/thread/thread.mutex/thread.mutex.requirements/thread.mutex.requirements.mutex/thread.mutex.recursive/assign.compile.pass.cpp similarity index 79% rename from libcxx/test/std/thread/thread.mutex/thread.mutex.requirements/thread.mutex.requirements.mutex/thread.mutex.recursive/assign.compile.fail.cpp rename to libcxx/test/std/thread/thread.mutex/thread.mutex.requirements/thread.mutex.requirements.mutex/thread.mutex.recursive/assign.compile.pass.cpp index 0cf3c5bca1e1b..fadd9a7cae28c 100644 --- a/libcxx/test/std/thread/thread.mutex/thread.mutex.requirements/thread.mutex.requirements.mutex/thread.mutex.recursive/assign.compile.fail.cpp +++ b/libcxx/test/std/thread/thread.mutex/thread.mutex.requirements/thread.mutex.requirements.mutex/thread.mutex.recursive/assign.compile.pass.cpp @@ -6,6 +6,8 @@ // //===----------------------------------------------------------------------===// +// UNSUPPORTED: no-threads + // // class recursive_mutex; @@ -13,12 +15,6 @@ // recursive_mutex& operator=(const recursive_mutex&) = delete; #include +#include -int main(int, char**) -{ - std::recursive_mutex m0; - std::recursive_mutex m1; - m1 = m0; - - return 0; -} +static_assert(!std::is_copy_assignable::value, ""); diff --git a/libcxx/test/std/thread/thread.mutex/thread.mutex.requirements/thread.mutex.requirements.mutex/thread.mutex.recursive/copy.compile.fail.cpp b/libcxx/test/std/thread/thread.mutex/thread.mutex.requirements/thread.mutex.requirements.mutex/thread.mutex.recursive/ctor.copy.compile.pass.cpp similarity index 79% rename from libcxx/test/std/thread/thread.mutex/thread.mutex.requirements/thread.mutex.requirements.mutex/thread.mutex.recursive/copy.compile.fail.cpp rename to libcxx/test/std/thread/thread.mutex/thread.mutex.requirements/thread.mutex.requirements.mutex/thread.mutex.recursive/ctor.copy.compile.pass.cpp index 454d7797373ca..bd63224f35d70 100644 --- a/libcxx/test/std/thread/thread.mutex/thread.mutex.requirements/thread.mutex.requirements.mutex/thread.mutex.recursive/copy.compile.fail.cpp +++ b/libcxx/test/std/thread/thread.mutex/thread.mutex.requirements/thread.mutex.requirements.mutex/thread.mutex.recursive/ctor.copy.compile.pass.cpp @@ -6,6 +6,8 @@ // //===----------------------------------------------------------------------===// +// UNSUPPORTED: no-threads + // // class recursive_mutex; @@ -13,11 +15,6 @@ // recursive_mutex(const recursive_mutex&) = delete; #include +#include -int main(int, char**) -{ - std::recursive_mutex m0; - std::recursive_mutex m1(m0); - - return 0; -} +static_assert(!std::is_copy_constructible::value, ""); diff --git a/libcxx/test/std/thread/thread.mutex/thread.mutex.requirements/thread.mutex.requirements.mutex/thread.mutex.recursive/default.pass.cpp b/libcxx/test/std/thread/thread.mutex/thread.mutex.requirements/thread.mutex.requirements.mutex/thread.mutex.recursive/ctor.default.pass.cpp similarity index 75% rename from libcxx/test/std/thread/thread.mutex/thread.mutex.requirements/thread.mutex.requirements.mutex/thread.mutex.recursive/default.pass.cpp rename to libcxx/test/std/thread/thread.mutex/thread.mutex.requirements/thread.mutex.requirements.mutex/thread.mutex.recursive/ctor.default.pass.cpp index 43dc38d7cab51..cd2694e8c43c8 100644 --- a/libcxx/test/std/thread/thread.mutex/thread.mutex.requirements/thread.mutex.requirements.mutex/thread.mutex.recursive/default.pass.cpp +++ b/libcxx/test/std/thread/thread.mutex/thread.mutex.requirements/thread.mutex.requirements.mutex/thread.mutex.recursive/ctor.default.pass.cpp @@ -5,7 +5,7 @@ // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception // //===----------------------------------------------------------------------===// -// + // UNSUPPORTED: no-threads // @@ -15,12 +15,16 @@ // recursive_mutex(); #include +#include +#include -#include "test_macros.h" - -int main(int, char**) -{ +int main(int, char**) { + // The mutex is unlocked after default construction + { std::recursive_mutex m; + assert(m.try_lock()); + m.unlock(); + } return 0; } diff --git a/libcxx/test/std/thread/thread.mutex/thread.mutex.requirements/thread.mutex.requirements.mutex/thread.mutex.recursive/lock.pass.cpp b/libcxx/test/std/thread/thread.mutex/thread.mutex.requirements/thread.mutex.requirements.mutex/thread.mutex.recursive/lock.pass.cpp index d9bff9b3cbda5..344667fa705d2 100644 --- a/libcxx/test/std/thread/thread.mutex/thread.mutex.requirements/thread.mutex.requirements.mutex/thread.mutex.recursive/lock.pass.cpp +++ b/libcxx/test/std/thread/thread.mutex/thread.mutex.requirements/thread.mutex.requirements.mutex/thread.mutex.recursive/lock.pass.cpp @@ -6,8 +6,8 @@ // //===----------------------------------------------------------------------===// +// UNSUPPORTED: c++03 // UNSUPPORTED: no-threads -// ALLOW_RETRIES: 2 // @@ -15,42 +15,96 @@ // void lock(); -#include -#include -#include #include +#include +#include #include +#include #include "make_test_thread.h" -#include "test_macros.h" -std::recursive_mutex m; +bool is_lockable(std::recursive_mutex& m) { + bool did_lock; + std::thread t = support::make_test_thread([&] { + did_lock = m.try_lock(); + if (did_lock) + m.unlock(); // undo side effects + }); + t.join(); -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; + return did_lock; +} -void f() -{ - time_point t0 = Clock::now(); - m.lock(); - time_point t1 = Clock::now(); +int main(int, char**) { + // Lock a mutex that is not locked yet. This should succeed. + { + std::recursive_mutex m; m.lock(); m.unlock(); - m.unlock(); - ns d = t1 - t0 - ms(250); - assert(d < ms(200)); // within 200ms -} + } + + // Lock a mutex that is already locked by this thread. This should succeed and the mutex should only + // be unlocked after a matching number of calls to unlock() on the same thread. + { + std::recursive_mutex m; + int lock_count = 0; + for (int i = 0; i != 10; ++i) { + m.lock(); + ++lock_count; + } + while (lock_count != 0) { + assert(!is_lockable(m)); + m.unlock(); + --lock_count; + } + assert(is_lockable(m)); + } -int main(int, char**) -{ + // Lock a mutex that is already locked by another thread. This should block until it is unlocked. + { + std::atomic ready(false); + std::recursive_mutex m; m.lock(); - std::thread t = support::make_test_thread(f); - std::this_thread::sleep_for(ms(250)); + std::atomic is_locked_from_main(true); + + std::thread t = support::make_test_thread([&] { + ready = true; + m.lock(); + assert(!is_locked_from_main); + m.unlock(); + }); + + while (!ready) + /* spin */; + + // We would rather signal this after we unlock, but that would create a race condition. + // We instead signal it before we unlock, which means that it's technically possible for + // the thread to take the lock while main is still holding it yet for the test to still pass. + is_locked_from_main = false; m.unlock(); + t.join(); + } + + // Make sure that at most one thread can acquire the mutex concurrently. + { + std::atomic counter(0); + std::recursive_mutex mutex; + + std::vector threads; + for (int i = 0; i != 10; ++i) { + threads.push_back(support::make_test_thread([&] { + mutex.lock(); + counter++; + assert(counter == 1); + counter--; + mutex.unlock(); + })); + } + + for (auto& t : threads) + t.join(); + } return 0; } diff --git a/libcxx/test/std/thread/thread.mutex/thread.mutex.requirements/thread.mutex.requirements.mutex/thread.mutex.recursive/try_lock.pass.cpp b/libcxx/test/std/thread/thread.mutex/thread.mutex.requirements/thread.mutex.requirements.mutex/thread.mutex.recursive/try_lock.pass.cpp index 1247c1ce1ba5f..96073eb345306 100644 --- a/libcxx/test/std/thread/thread.mutex/thread.mutex.requirements/thread.mutex.requirements.mutex/thread.mutex.recursive/try_lock.pass.cpp +++ b/libcxx/test/std/thread/thread.mutex/thread.mutex.requirements/thread.mutex.requirements.mutex/thread.mutex.recursive/try_lock.pass.cpp @@ -5,9 +5,9 @@ // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception // //===----------------------------------------------------------------------===// -// + +// UNSUPPORTED: c++03 // UNSUPPORTED: no-threads -// ALLOW_RETRIES: 2 // @@ -15,46 +15,67 @@ // bool try_lock(); +#include +#include #include #include -#include -#include #include #include "make_test_thread.h" -#include "test_macros.h" - -std::recursive_mutex m; - -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; - -void f() -{ - time_point t0 = Clock::now(); - assert(!m.try_lock()); - assert(!m.try_lock()); - assert(!m.try_lock()); - while(!m.try_lock()) - ; - time_point t1 = Clock::now(); - assert(m.try_lock()); - m.unlock(); - m.unlock(); - ns d = t1 - t0 - ms(250); - assert(d < ms(200)); // within 200ms + +bool is_lockable(std::recursive_mutex& m) { + bool did_lock; + std::thread t = support::make_test_thread([&] { + did_lock = m.try_lock(); + if (did_lock) + m.unlock(); // undo side effects + }); + t.join(); + + return did_lock; } -int main(int, char**) -{ - m.lock(); - std::thread t = support::make_test_thread(f); - std::this_thread::sleep_for(ms(250)); +int main(int, char**) { + // Try to lock a mutex that is not locked yet. This should succeed. + { + std::recursive_mutex m; + bool succeeded = m.try_lock(); + assert(succeeded); m.unlock(); + } + + // Try to lock a mutex that is already locked by this thread. This should succeed and the mutex should only + // be unlocked after a matching number of calls to unlock() on the same thread. + { + std::recursive_mutex m; + int lock_count = 0; + for (int i = 0; i != 10; ++i) { + assert(m.try_lock()); + ++lock_count; + } + while (lock_count != 0) { + assert(!is_lockable(m)); + m.unlock(); + --lock_count; + } + assert(is_lockable(m)); + } + + // Try to lock a mutex that is already locked by another thread. This should fail. + { + std::recursive_mutex m; + m.lock(); + + std::thread t = support::make_test_thread([&] { + for (int i = 0; i != 10; ++i) { + bool succeeded = m.try_lock(); + assert(!succeeded); + } + }); t.join(); + m.unlock(); + } + return 0; } diff --git a/libcxx/test/std/thread/thread.mutex/thread.mutex.requirements/thread.timedmutex.requirements/thread.timedmutex.class/assign.compile.fail.cpp b/libcxx/test/std/thread/thread.mutex/thread.mutex.requirements/thread.timedmutex.requirements/thread.timedmutex.class/assign.compile.pass.cpp similarity index 80% rename from libcxx/test/std/thread/thread.mutex/thread.mutex.requirements/thread.timedmutex.requirements/thread.timedmutex.class/assign.compile.fail.cpp rename to libcxx/test/std/thread/thread.mutex/thread.mutex.requirements/thread.timedmutex.requirements/thread.timedmutex.class/assign.compile.pass.cpp index d0fabc678f26c..a046a875b3df5 100644 --- a/libcxx/test/std/thread/thread.mutex/thread.mutex.requirements/thread.timedmutex.requirements/thread.timedmutex.class/assign.compile.fail.cpp +++ b/libcxx/test/std/thread/thread.mutex/thread.mutex.requirements/thread.timedmutex.requirements/thread.timedmutex.class/assign.compile.pass.cpp @@ -6,6 +6,8 @@ // //===----------------------------------------------------------------------===// +// UNSUPPORTED: no-threads + // // class timed_mutex; @@ -13,12 +15,6 @@ // timed_mutex& operator=(const timed_mutex&) = delete; #include +#include -int main(int, char**) -{ - std::timed_mutex m0; - std::timed_mutex m1; - m1 = m0; - - return 0; -} +static_assert(!std::is_copy_assignable::value, ""); diff --git a/libcxx/test/std/thread/thread.mutex/thread.mutex.requirements/thread.timedmutex.requirements/thread.timedmutex.class/copy.compile.fail.cpp b/libcxx/test/std/thread/thread.mutex/thread.mutex.requirements/thread.timedmutex.requirements/thread.timedmutex.class/ctor.copy.compile.pass.cpp similarity index 79% rename from libcxx/test/std/thread/thread.mutex/thread.mutex.requirements/thread.timedmutex.requirements/thread.timedmutex.class/copy.compile.fail.cpp rename to libcxx/test/std/thread/thread.mutex/thread.mutex.requirements/thread.timedmutex.requirements/thread.timedmutex.class/ctor.copy.compile.pass.cpp index a3efb2feeeeda..3e3a01c6b5b19 100644 --- a/libcxx/test/std/thread/thread.mutex/thread.mutex.requirements/thread.timedmutex.requirements/thread.timedmutex.class/copy.compile.fail.cpp +++ b/libcxx/test/std/thread/thread.mutex/thread.mutex.requirements/thread.timedmutex.requirements/thread.timedmutex.class/ctor.copy.compile.pass.cpp @@ -6,6 +6,8 @@ // //===----------------------------------------------------------------------===// +// UNSUPPORTED: no-threads + // // class timed_mutex; @@ -13,11 +15,6 @@ // timed_mutex(const timed_mutex&) = delete; #include +#include -int main(int, char**) -{ - std::timed_mutex m0; - std::timed_mutex m1(m0); - - return 0; -} +static_assert(!std::is_copy_constructible::value, ""); diff --git a/libcxx/test/std/thread/thread.mutex/thread.mutex.requirements/thread.timedmutex.requirements/thread.timedmutex.class/default.pass.cpp b/libcxx/test/std/thread/thread.mutex/thread.mutex.requirements/thread.timedmutex.requirements/thread.timedmutex.class/ctor.default.pass.cpp similarity index 77% rename from libcxx/test/std/thread/thread.mutex/thread.mutex.requirements/thread.timedmutex.requirements/thread.timedmutex.class/default.pass.cpp rename to libcxx/test/std/thread/thread.mutex/thread.mutex.requirements/thread.timedmutex.requirements/thread.timedmutex.class/ctor.default.pass.cpp index c7f207372ac42..9bb2d93c5b210 100644 --- a/libcxx/test/std/thread/thread.mutex/thread.mutex.requirements/thread.timedmutex.requirements/thread.timedmutex.class/default.pass.cpp +++ b/libcxx/test/std/thread/thread.mutex/thread.mutex.requirements/thread.timedmutex.requirements/thread.timedmutex.class/ctor.default.pass.cpp @@ -15,12 +15,15 @@ // timed_mutex(); #include - -#include "test_macros.h" +#include int main(int, char**) { - std::timed_mutex m; - (void)m; + // The mutex is unlocked after default construction + { + std::timed_mutex m; + assert(m.try_lock()); + m.unlock(); + } return 0; } diff --git a/libcxx/test/std/thread/thread.mutex/thread.mutex.requirements/thread.timedmutex.requirements/thread.timedmutex.class/lock.pass.cpp b/libcxx/test/std/thread/thread.mutex/thread.mutex.requirements/thread.timedmutex.requirements/thread.timedmutex.class/lock.pass.cpp index a71bd3d38b2c3..8893d389becef 100644 --- a/libcxx/test/std/thread/thread.mutex/thread.mutex.requirements/thread.timedmutex.requirements/thread.timedmutex.class/lock.pass.cpp +++ b/libcxx/test/std/thread/thread.mutex/thread.mutex.requirements/thread.timedmutex.requirements/thread.timedmutex.class/lock.pass.cpp @@ -6,8 +6,8 @@ // //===----------------------------------------------------------------------===// +// UNSUPPORTED: c++03 // UNSUPPORTED: no-threads -// ALLOW_RETRIES: 2 // @@ -15,40 +15,67 @@ // void lock(); -#include -#include -#include #include +#include +#include #include +#include #include "make_test_thread.h" -#include "test_macros.h" - -std::timed_mutex m; -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; - -void f() -{ - time_point t0 = Clock::now(); +int main(int, char**) { + // Lock a mutex that is not locked yet. This should succeed. + { + std::timed_mutex m; m.lock(); - time_point t1 = Clock::now(); m.unlock(); - ns d = t1 - t0 - ms(250); - assert(d < ms(50)); // within 50ms -} + } -int main(int, char**) -{ + // Lock a mutex that is already locked. This should block until it is unlocked. + { + std::atomic ready(false); + std::timed_mutex m; m.lock(); - std::thread t = support::make_test_thread(f); - std::this_thread::sleep_for(ms(250)); + std::atomic is_locked_from_main(true); + + std::thread t = support::make_test_thread([&] { + ready = true; + m.lock(); + assert(!is_locked_from_main); + m.unlock(); + }); + + while (!ready) + /* spin */; + + // We would rather signal this after we unlock, but that would create a race condition. + // We instead signal it before we unlock, which means that it's technically possible for + // the thread to take the lock while main is still holding it yet for the test to still pass. + is_locked_from_main = false; m.unlock(); + t.join(); + } + + // Make sure that at most one thread can acquire the mutex concurrently. + { + std::atomic counter(0); + std::timed_mutex mutex; + + std::vector threads; + for (int i = 0; i != 10; ++i) { + threads.push_back(support::make_test_thread([&] { + mutex.lock(); + counter++; + assert(counter == 1); + counter--; + mutex.unlock(); + })); + } + + for (auto& t : threads) + t.join(); + } return 0; } diff --git a/libcxx/test/std/thread/thread.mutex/thread.mutex.requirements/thread.timedmutex.requirements/thread.timedmutex.class/try_lock.pass.cpp b/libcxx/test/std/thread/thread.mutex/thread.mutex.requirements/thread.timedmutex.requirements/thread.timedmutex.class/try_lock.pass.cpp index f3942ccb9d860..9a4c68bc12093 100644 --- a/libcxx/test/std/thread/thread.mutex/thread.mutex.requirements/thread.timedmutex.requirements/thread.timedmutex.class/try_lock.pass.cpp +++ b/libcxx/test/std/thread/thread.mutex/thread.mutex.requirements/thread.timedmutex.requirements/thread.timedmutex.class/try_lock.pass.cpp @@ -6,8 +6,8 @@ // //===----------------------------------------------------------------------===// +// UNSUPPORTED: c++03 // UNSUPPORTED: no-threads -// ALLOW_RETRIES: 2 // @@ -15,44 +15,36 @@ // bool try_lock(); -#include -#include -#include #include +#include #include #include "make_test_thread.h" -#include "test_macros.h" - -std::timed_mutex m; - -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; - -void f() -{ - time_point t0 = Clock::now(); - assert(!m.try_lock()); - assert(!m.try_lock()); - assert(!m.try_lock()); - while(!m.try_lock()) - ; - time_point t1 = Clock::now(); + +int main(int, char**) { + // Try to lock a mutex that is not locked yet. This should succeed. + { + std::timed_mutex m; + bool succeeded = m.try_lock(); + assert(succeeded); m.unlock(); - ns d = t1 - t0 - ms(250); - assert(d < ms(200)); // within 200ms -} + } -int main(int, char**) -{ + // Try to lock a mutex that is already locked. This should fail. + { + std::timed_mutex m; m.lock(); - std::thread t = support::make_test_thread(f); - std::this_thread::sleep_for(ms(250)); - m.unlock(); + + std::thread t = support::make_test_thread([&] { + for (int i = 0; i != 10; ++i) { + bool succeeded = m.try_lock(); + assert(!succeeded); + } + }); t.join(); + m.unlock(); + } + return 0; } diff --git a/libcxx/test/std/thread/thread.mutex/thread.mutex.requirements/thread.timedmutex.requirements/thread.timedmutex.class/try_lock_for.pass.cpp b/libcxx/test/std/thread/thread.mutex/thread.mutex.requirements/thread.timedmutex.requirements/thread.timedmutex.class/try_lock_for.pass.cpp index acfa5560962ae..b1882ad61c603 100644 --- a/libcxx/test/std/thread/thread.mutex/thread.mutex.requirements/thread.timedmutex.requirements/thread.timedmutex.class/try_lock_for.pass.cpp +++ b/libcxx/test/std/thread/thread.mutex/thread.mutex.requirements/thread.timedmutex.requirements/thread.timedmutex.class/try_lock_for.pass.cpp @@ -6,8 +6,8 @@ // //===----------------------------------------------------------------------===// +// UNSUPPORTED: c++03 // UNSUPPORTED: no-threads -// ALLOW_RETRIES: 2 // @@ -17,56 +17,89 @@ // bool try_lock_for(const chrono::duration& rel_time); #include -#include -#include +#include #include +#include +#include #include "make_test_thread.h" -#include "test_macros.h" - -std::timed_mutex m; -typedef std::chrono::steady_clock Clock; -typedef Clock::time_point time_point; -typedef Clock::duration duration; -typedef std::chrono::milliseconds ms; -typedef std::chrono::nanoseconds ns; +template +std::chrono::microseconds measure(Function f) { + std::chrono::high_resolution_clock::time_point start = std::chrono::high_resolution_clock::now(); + f(); + std::chrono::high_resolution_clock::time_point end = std::chrono::high_resolution_clock::now(); + return std::chrono::duration_cast(end - start); +} -void f1() -{ - time_point t0 = Clock::now(); - assert(m.try_lock_for(ms(300)) == true); - time_point t1 = Clock::now(); +int main(int, char**) { + // Try to lock a mutex that is not locked yet. This should succeed immediately. + { + std::timed_mutex m; + bool succeeded = m.try_lock_for(std::chrono::milliseconds(1)); + assert(succeeded); m.unlock(); - ns d = t1 - t0 - ms(250); - assert(d < ms(50)); // within 50ms -} + } -void f2() -{ - time_point t0 = Clock::now(); - assert(m.try_lock_for(ms(250)) == false); - time_point t1 = Clock::now(); - ns d = t1 - t0 - ms(250); - assert(d < ms(50)); // within 50ms -} + // Try to lock an already-locked mutex for a long enough amount of time and succeed. + // This is technically flaky, but we use such long durations that it should pass even + // in slow or contended environments. + { + std::chrono::milliseconds const wait_time(500); + std::chrono::milliseconds const tolerance = wait_time * 3; + std::atomic ready(false); -int main(int, char**) -{ - { - m.lock(); - std::thread t = support::make_test_thread(f1); - std::this_thread::sleep_for(ms(250)); - m.unlock(); - t.join(); - } - { - m.lock(); - std::thread t = support::make_test_thread(f2); - std::this_thread::sleep_for(ms(300)); + std::timed_mutex m; + m.lock(); + + std::thread t = support::make_test_thread([&] { + auto elapsed = measure([&] { + ready = true; + bool succeeded = m.try_lock_for(wait_time); + assert(succeeded); m.unlock(); - t.join(); - } + }); + + // Ensure we didn't wait significantly longer than our timeout. This is technically + // flaky and non-conforming because an implementation is free to block for arbitrarily + // long, but any decent quality implementation should pass this test. + assert(elapsed - wait_time < tolerance); + }); + + // Wait for the thread to be ready to take the lock before we unlock it from here, otherwise + // there's a high chance that we're not testing the "locking an already locked" mutex use case. + // There is still technically a race condition here. + while (!ready) + /* spin */; + std::this_thread::sleep_for(wait_time / 5); + + m.unlock(); // this should allow the thread to lock 'm' + t.join(); + } + + // Try to lock an already-locked mutex for a short amount of time and fail. + // Again, this is technically flaky but we use such long durations that it should work. + { + std::chrono::milliseconds const wait_time(10); + std::chrono::milliseconds const tolerance(750); // in case the thread we spawned goes to sleep or something + + std::timed_mutex m; + m.lock(); + + std::thread t = support::make_test_thread([&] { + auto elapsed = measure([&] { + bool succeeded = m.try_lock_for(wait_time); + assert(!succeeded); + }); + + // Ensure we failed within some bounded time. + assert(elapsed - wait_time < tolerance); + }); + + t.join(); + + m.unlock(); + } return 0; } diff --git a/libcxx/test/std/thread/thread.mutex/thread.mutex.requirements/thread.timedmutex.requirements/thread.timedmutex.class/try_lock_until.pass.cpp b/libcxx/test/std/thread/thread.mutex/thread.mutex.requirements/thread.timedmutex.requirements/thread.timedmutex.class/try_lock_until.pass.cpp index 23385c100807d..72471ed07dcff 100644 --- a/libcxx/test/std/thread/thread.mutex/thread.mutex.requirements/thread.timedmutex.requirements/thread.timedmutex.class/try_lock_until.pass.cpp +++ b/libcxx/test/std/thread/thread.mutex/thread.mutex.requirements/thread.timedmutex.requirements/thread.timedmutex.class/try_lock_until.pass.cpp @@ -6,8 +6,8 @@ // //===----------------------------------------------------------------------===// +// UNSUPPORTED: c++03 // UNSUPPORTED: no-threads -// ALLOW_RETRIES: 2 // @@ -17,56 +17,89 @@ // bool try_lock_until(const chrono::time_point& abs_time); #include -#include -#include +#include #include +#include +#include #include "make_test_thread.h" -#include "test_macros.h" - -std::timed_mutex m; -typedef std::chrono::steady_clock Clock; -typedef Clock::time_point time_point; -typedef Clock::duration duration; -typedef std::chrono::milliseconds ms; -typedef std::chrono::nanoseconds ns; +template +std::chrono::microseconds measure(Function f) { + std::chrono::high_resolution_clock::time_point start = std::chrono::high_resolution_clock::now(); + f(); + std::chrono::high_resolution_clock::time_point end = std::chrono::high_resolution_clock::now(); + return std::chrono::duration_cast(end - start); +} -void f1() -{ - time_point t0 = Clock::now(); - assert(m.try_lock_until(Clock::now() + ms(300)) == true); - time_point t1 = Clock::now(); +int main(int, char**) { + // Try to lock a mutex that is not locked yet. This should succeed immediately. + { + std::timed_mutex m; + bool succeeded = m.try_lock_until(std::chrono::steady_clock::now() + std::chrono::milliseconds(1)); + assert(succeeded); m.unlock(); - ns d = t1 - t0 - ms(250); - assert(d < ms(50)); // within 50ms -} + } -void f2() -{ - time_point t0 = Clock::now(); - assert(m.try_lock_until(Clock::now() + ms(250)) == false); - time_point t1 = Clock::now(); - ns d = t1 - t0 - ms(250); - assert(d < ms(50)); // within 50ms -} + // Try to lock an already-locked mutex for a long enough amount of time and succeed. + // This is technically flaky, but we use such long durations that it should pass even + // in slow or contended environments. + { + std::chrono::milliseconds const wait_time(500); + std::chrono::milliseconds const tolerance = wait_time * 3; + std::atomic ready(false); -int main(int, char**) -{ - { - m.lock(); - std::thread t = support::make_test_thread(f1); - std::this_thread::sleep_for(ms(250)); - m.unlock(); - t.join(); - } - { - m.lock(); - std::thread t = support::make_test_thread(f2); - std::this_thread::sleep_for(ms(300)); + std::timed_mutex m; + m.lock(); + + std::thread t = support::make_test_thread([&] { + auto elapsed = measure([&] { + ready = true; + bool succeeded = m.try_lock_until(std::chrono::steady_clock::now() + wait_time); + assert(succeeded); m.unlock(); - t.join(); - } + }); + + // Ensure we didn't wait significantly longer than our timeout. This is technically + // flaky and non-conforming because an implementation is free to block for arbitrarily + // long, but any decent quality implementation should pass this test. + assert(elapsed - wait_time < tolerance); + }); + + // Wait for the thread to be ready to take the lock before we unlock it from here, otherwise + // there's a high chance that we're not testing the "locking an already locked" mutex use case. + // There is still technically a race condition here. + while (!ready) + /* spin */; + std::this_thread::sleep_for(wait_time / 5); + + m.unlock(); // this should allow the thread to lock 'm' + t.join(); + } + + // Try to lock an already-locked mutex for a short amount of time and fail. + // Again, this is technically flaky but we use such long durations that it should work. + { + std::chrono::milliseconds const wait_time(10); + std::chrono::milliseconds const tolerance(750); // in case the thread we spawned goes to sleep or something + + std::timed_mutex m; + m.lock(); + + std::thread t = support::make_test_thread([&] { + auto elapsed = measure([&] { + bool succeeded = m.try_lock_until(std::chrono::steady_clock::now() + wait_time); + assert(!succeeded); + }); + + // Ensure we failed within some bounded time. + assert(elapsed - wait_time < tolerance); + }); + + t.join(); + + m.unlock(); + } return 0; } diff --git a/libcxx/test/std/thread/thread.mutex/thread.mutex.requirements/thread.timedmutex.requirements/thread.timedmutex.recursive/assign.compile.fail.cpp b/libcxx/test/std/thread/thread.mutex/thread.mutex.requirements/thread.timedmutex.requirements/thread.timedmutex.recursive/assign.compile.pass.cpp similarity index 79% rename from libcxx/test/std/thread/thread.mutex/thread.mutex.requirements/thread.timedmutex.requirements/thread.timedmutex.recursive/assign.compile.fail.cpp rename to libcxx/test/std/thread/thread.mutex/thread.mutex.requirements/thread.timedmutex.requirements/thread.timedmutex.recursive/assign.compile.pass.cpp index 44be06d675413..681679e006235 100644 --- a/libcxx/test/std/thread/thread.mutex/thread.mutex.requirements/thread.timedmutex.requirements/thread.timedmutex.recursive/assign.compile.fail.cpp +++ b/libcxx/test/std/thread/thread.mutex/thread.mutex.requirements/thread.timedmutex.requirements/thread.timedmutex.recursive/assign.compile.pass.cpp @@ -6,6 +6,8 @@ // //===----------------------------------------------------------------------===// +// UNSUPPORTED: no-threads + // // class recursive_timed_mutex; @@ -13,12 +15,6 @@ // recursive_timed_mutex& operator=(const recursive_timed_mutex&) = delete; #include +#include -int main(int, char**) -{ - std::recursive_timed_mutex m0; - std::recursive_timed_mutex m1; - m1 = m0; - - return 0; -} +static_assert(!std::is_copy_assignable::value, ""); diff --git a/libcxx/test/std/thread/thread.mutex/thread.mutex.requirements/thread.timedmutex.requirements/thread.timedmutex.recursive/copy.compile.fail.cpp b/libcxx/test/std/thread/thread.mutex/thread.mutex.requirements/thread.timedmutex.requirements/thread.timedmutex.recursive/ctor.copy.compile.pass.cpp similarity index 78% rename from libcxx/test/std/thread/thread.mutex/thread.mutex.requirements/thread.timedmutex.requirements/thread.timedmutex.recursive/copy.compile.fail.cpp rename to libcxx/test/std/thread/thread.mutex/thread.mutex.requirements/thread.timedmutex.requirements/thread.timedmutex.recursive/ctor.copy.compile.pass.cpp index 154a0192d14db..1ac287e08d7f9 100644 --- a/libcxx/test/std/thread/thread.mutex/thread.mutex.requirements/thread.timedmutex.requirements/thread.timedmutex.recursive/copy.compile.fail.cpp +++ b/libcxx/test/std/thread/thread.mutex/thread.mutex.requirements/thread.timedmutex.requirements/thread.timedmutex.recursive/ctor.copy.compile.pass.cpp @@ -6,6 +6,8 @@ // //===----------------------------------------------------------------------===// +// UNSUPPORTED: no-threads + // // class recursive_timed_mutex; @@ -13,11 +15,6 @@ // recursive_timed_mutex(const recursive_timed_mutex&) = delete; #include +#include -int main(int, char**) -{ - std::recursive_timed_mutex m0; - std::recursive_timed_mutex m1(m0); - - return 0; -} +static_assert(!std::is_copy_constructible::value, ""); diff --git a/libcxx/test/std/thread/thread.mutex/thread.mutex.requirements/thread.timedmutex.requirements/thread.timedmutex.recursive/default.pass.cpp b/libcxx/test/std/thread/thread.mutex/thread.mutex.requirements/thread.timedmutex.requirements/thread.timedmutex.recursive/ctor.default.pass.cpp similarity index 77% rename from libcxx/test/std/thread/thread.mutex/thread.mutex.requirements/thread.timedmutex.requirements/thread.timedmutex.recursive/default.pass.cpp rename to libcxx/test/std/thread/thread.mutex/thread.mutex.requirements/thread.timedmutex.requirements/thread.timedmutex.recursive/ctor.default.pass.cpp index 3096e031855a2..dede7f44bc196 100644 --- a/libcxx/test/std/thread/thread.mutex/thread.mutex.requirements/thread.timedmutex.requirements/thread.timedmutex.recursive/default.pass.cpp +++ b/libcxx/test/std/thread/thread.mutex/thread.mutex.requirements/thread.timedmutex.requirements/thread.timedmutex.recursive/ctor.default.pass.cpp @@ -15,12 +15,15 @@ // recursive_timed_mutex(); #include - -#include "test_macros.h" +#include int main(int, char**) { - std::recursive_timed_mutex m; - (void)m; + // The mutex is unlocked after default construction + { + std::recursive_timed_mutex m; + assert(m.try_lock()); + m.unlock(); + } return 0; } diff --git a/libcxx/test/std/thread/thread.mutex/thread.mutex.requirements/thread.timedmutex.requirements/thread.timedmutex.recursive/lock.pass.cpp b/libcxx/test/std/thread/thread.mutex/thread.mutex.requirements/thread.timedmutex.requirements/thread.timedmutex.recursive/lock.pass.cpp index bad5a4457e516..695ce508cf7c6 100644 --- a/libcxx/test/std/thread/thread.mutex/thread.mutex.requirements/thread.timedmutex.requirements/thread.timedmutex.recursive/lock.pass.cpp +++ b/libcxx/test/std/thread/thread.mutex/thread.mutex.requirements/thread.timedmutex.requirements/thread.timedmutex.recursive/lock.pass.cpp @@ -6,8 +6,8 @@ // //===----------------------------------------------------------------------===// +// UNSUPPORTED: c++03 // UNSUPPORTED: no-threads -// ALLOW_RETRIES: 2 // @@ -15,42 +15,96 @@ // void lock(); -#include -#include -#include #include +#include +#include #include +#include #include "make_test_thread.h" -#include "test_macros.h" -std::recursive_timed_mutex m; +bool is_lockable(std::recursive_timed_mutex& m) { + bool did_lock; + std::thread t = support::make_test_thread([&] { + did_lock = m.try_lock(); + if (did_lock) + m.unlock(); // undo side effects + }); + t.join(); -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; + return did_lock; +} -void f() -{ - time_point t0 = Clock::now(); - m.lock(); - time_point t1 = Clock::now(); +int main(int, char**) { + // Lock a mutex that is not locked yet. This should succeed. + { + std::recursive_timed_mutex m; m.lock(); m.unlock(); - m.unlock(); - ns d = t1 - t0 - ms(250); - assert(d < ms(50)); // within 50ms -} + } + + // Lock a mutex that is already locked by this thread. This should succeed and the mutex should only + // be unlocked after a matching number of calls to unlock() on the same thread. + { + std::recursive_timed_mutex m; + int lock_count = 0; + for (int i = 0; i != 10; ++i) { + m.lock(); + ++lock_count; + } + while (lock_count != 0) { + assert(!is_lockable(m)); + m.unlock(); + --lock_count; + } + assert(is_lockable(m)); + } -int main(int, char**) -{ + // Lock a mutex that is already locked by another thread. This should block until it is unlocked. + { + std::atomic ready(false); + std::recursive_timed_mutex m; m.lock(); - std::thread t = support::make_test_thread(f); - std::this_thread::sleep_for(ms(250)); + std::atomic is_locked_from_main(true); + + std::thread t = support::make_test_thread([&] { + ready = true; + m.lock(); + assert(!is_locked_from_main); + m.unlock(); + }); + + while (!ready) + /* spin */; + + // We would rather signal this after we unlock, but that would create a race condition. + // We instead signal it before we unlock, which means that it's technically possible for + // the thread to take the lock while main is still holding it yet for the test to still pass. + is_locked_from_main = false; m.unlock(); + t.join(); + } + + // Make sure that at most one thread can acquire the mutex concurrently. + { + std::atomic counter(0); + std::recursive_timed_mutex mutex; + + std::vector threads; + for (int i = 0; i != 10; ++i) { + threads.push_back(support::make_test_thread([&] { + mutex.lock(); + counter++; + assert(counter == 1); + counter--; + mutex.unlock(); + })); + } + + for (auto& t : threads) + t.join(); + } return 0; } diff --git a/libcxx/test/std/thread/thread.mutex/thread.mutex.requirements/thread.timedmutex.requirements/thread.timedmutex.recursive/try_lock.pass.cpp b/libcxx/test/std/thread/thread.mutex/thread.mutex.requirements/thread.timedmutex.requirements/thread.timedmutex.recursive/try_lock.pass.cpp index 63be0ac713f8b..848db63a003cb 100644 --- a/libcxx/test/std/thread/thread.mutex/thread.mutex.requirements/thread.timedmutex.requirements/thread.timedmutex.recursive/try_lock.pass.cpp +++ b/libcxx/test/std/thread/thread.mutex/thread.mutex.requirements/thread.timedmutex.requirements/thread.timedmutex.recursive/try_lock.pass.cpp @@ -6,8 +6,8 @@ // //===----------------------------------------------------------------------===// +// UNSUPPORTED: c++03 // UNSUPPORTED: no-threads -// ALLOW_RETRIES: 2 // @@ -15,46 +15,67 @@ // bool try_lock(); +#include +#include #include #include -#include -#include #include #include "make_test_thread.h" -#include "test_macros.h" - -std::recursive_timed_mutex m; - -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; - -void f() -{ - time_point t0 = Clock::now(); - assert(!m.try_lock()); - assert(!m.try_lock()); - assert(!m.try_lock()); - while(!m.try_lock()) - ; - time_point t1 = Clock::now(); - assert(m.try_lock()); - m.unlock(); - m.unlock(); - ns d = t1 - t0 - ms(250); - assert(d < ms(200)); // within 200ms + +bool is_lockable(std::recursive_timed_mutex& m) { + bool did_lock; + std::thread t = support::make_test_thread([&] { + did_lock = m.try_lock(); + if (did_lock) + m.unlock(); // undo side effects + }); + t.join(); + + return did_lock; } -int main(int, char**) -{ - m.lock(); - std::thread t = support::make_test_thread(f); - std::this_thread::sleep_for(ms(250)); +int main(int, char**) { + // Try to lock a mutex that is not locked yet. This should succeed. + { + std::recursive_timed_mutex m; + bool succeeded = m.try_lock(); + assert(succeeded); m.unlock(); + } + + // Try to lock a mutex that is already locked by this thread. This should succeed and the mutex should only + // be unlocked after a matching number of calls to unlock() on the same thread. + { + std::recursive_timed_mutex m; + int lock_count = 0; + for (int i = 0; i != 10; ++i) { + assert(m.try_lock()); + ++lock_count; + } + while (lock_count != 0) { + assert(!is_lockable(m)); + m.unlock(); + --lock_count; + } + assert(is_lockable(m)); + } + + // Try to lock a mutex that is already locked by another thread. This should fail. + { + std::recursive_timed_mutex m; + m.lock(); + + std::thread t = support::make_test_thread([&] { + for (int i = 0; i != 10; ++i) { + bool succeeded = m.try_lock(); + assert(!succeeded); + } + }); t.join(); + m.unlock(); + } + return 0; } diff --git a/libcxx/test/std/thread/thread.mutex/thread.mutex.requirements/thread.timedmutex.requirements/thread.timedmutex.recursive/try_lock_for.pass.cpp b/libcxx/test/std/thread/thread.mutex/thread.mutex.requirements/thread.timedmutex.requirements/thread.timedmutex.recursive/try_lock_for.pass.cpp index b0b27801c8c74..c9192f6fad78c 100644 --- a/libcxx/test/std/thread/thread.mutex/thread.mutex.requirements/thread.timedmutex.requirements/thread.timedmutex.recursive/try_lock_for.pass.cpp +++ b/libcxx/test/std/thread/thread.mutex/thread.mutex.requirements/thread.timedmutex.requirements/thread.timedmutex.recursive/try_lock_for.pass.cpp @@ -6,8 +6,8 @@ // //===----------------------------------------------------------------------===// +// UNSUPPORTED: c++03 // UNSUPPORTED: no-threads -// ALLOW_RETRIES: 2 // @@ -17,58 +17,118 @@ // bool try_lock_for(const chrono::duration& rel_time); #include -#include -#include +#include #include +#include +#include #include "make_test_thread.h" -#include "test_macros.h" - -std::recursive_timed_mutex m; - -typedef std::chrono::steady_clock Clock; -typedef Clock::time_point time_point; -typedef Clock::duration duration; -typedef std::chrono::milliseconds ms; -typedef std::chrono::nanoseconds ns; - -void f1() -{ - time_point t0 = Clock::now(); - assert(m.try_lock_for(ms(300)) == true); - time_point t1 = Clock::now(); - assert(m.try_lock()); - m.unlock(); - m.unlock(); - ns d = t1 - t0 - ms(250); - assert(d < ns(50000000)); // within 50ms + +bool is_lockable(std::recursive_timed_mutex& m) { + bool did_lock; + std::thread t = support::make_test_thread([&] { + did_lock = m.try_lock(); + if (did_lock) + m.unlock(); // undo side effects + }); + t.join(); + + return did_lock; } -void f2() -{ - time_point t0 = Clock::now(); - assert(m.try_lock_for(ms(250)) == false); - time_point t1 = Clock::now(); - ns d = t1 - t0 - ms(250); - assert(d < ns(50000000)); // within 50ms +template +std::chrono::microseconds measure(Function f) { + std::chrono::high_resolution_clock::time_point start = std::chrono::high_resolution_clock::now(); + f(); + std::chrono::high_resolution_clock::time_point end = std::chrono::high_resolution_clock::now(); + return std::chrono::duration_cast(end - start); } -int main(int, char**) -{ - { - m.lock(); - std::thread t = support::make_test_thread(f1); - std::this_thread::sleep_for(ms(250)); - m.unlock(); - t.join(); +int main(int, char**) { + // Try to lock a mutex that is not locked yet. This should succeed immediately. + { + std::recursive_timed_mutex m; + bool succeeded = m.try_lock_for(std::chrono::milliseconds(1)); + assert(succeeded); + m.unlock(); + } + + // Lock a mutex that is already locked by this thread. This should succeed immediately and the mutex + // should only be unlocked after a matching number of calls to unlock() on the same thread. + { + std::recursive_timed_mutex m; + int lock_count = 0; + for (int i = 0; i != 10; ++i) { + assert(m.try_lock_for(std::chrono::milliseconds(1))); + ++lock_count; } - { - m.lock(); - std::thread t = support::make_test_thread(f2); - std::this_thread::sleep_for(ms(300)); - m.unlock(); - t.join(); + while (lock_count != 0) { + assert(!is_lockable(m)); + m.unlock(); + --lock_count; } + assert(is_lockable(m)); + } + + // Try to lock an already-locked mutex for a long enough amount of time and succeed. + // This is technically flaky, but we use such long durations that it should pass even + // in slow or contended environments. + { + std::chrono::milliseconds const wait_time(500); + std::chrono::milliseconds const tolerance = wait_time * 3; + std::atomic ready(false); + + std::recursive_timed_mutex m; + m.lock(); + + std::thread t = support::make_test_thread([&] { + auto elapsed = measure([&] { + ready = true; + bool succeeded = m.try_lock_for(wait_time); + assert(succeeded); + m.unlock(); + }); + + // Ensure we didn't wait significantly longer than our timeout. This is technically + // flaky and non-conforming because an implementation is free to block for arbitrarily + // long, but any decent quality implementation should pass this test. + assert(elapsed - wait_time < tolerance); + }); + + // Wait for the thread to be ready to take the lock before we unlock it from here, otherwise + // there's a high chance that we're not testing the "locking an already locked" mutex use case. + // There is still technically a race condition here. + while (!ready) + /* spin */; + std::this_thread::sleep_for(wait_time / 5); + + m.unlock(); // this should allow the thread to lock 'm' + t.join(); + } + + // Try to lock an already-locked mutex for a short amount of time and fail. + // Again, this is technically flaky but we use such long durations that it should work. + { + std::chrono::milliseconds const wait_time(10); + std::chrono::milliseconds const tolerance(750); // in case the thread we spawned goes to sleep or something + + std::recursive_timed_mutex m; + m.lock(); + + std::thread t = support::make_test_thread([&] { + auto elapsed = measure([&] { + bool succeeded = m.try_lock_for(wait_time); + assert(!succeeded); + }); + + // Ensure we failed within some bounded time. + assert(elapsed - wait_time < tolerance); + }); + + t.join(); + + m.unlock(); + } return 0; } diff --git a/libcxx/test/std/thread/thread.mutex/thread.mutex.requirements/thread.timedmutex.requirements/thread.timedmutex.recursive/try_lock_until.pass.cpp b/libcxx/test/std/thread/thread.mutex/thread.mutex.requirements/thread.timedmutex.requirements/thread.timedmutex.recursive/try_lock_until.pass.cpp index 5c5807d6736c9..6579b21a38a26 100644 --- a/libcxx/test/std/thread/thread.mutex/thread.mutex.requirements/thread.timedmutex.requirements/thread.timedmutex.recursive/try_lock_until.pass.cpp +++ b/libcxx/test/std/thread/thread.mutex/thread.mutex.requirements/thread.timedmutex.requirements/thread.timedmutex.recursive/try_lock_until.pass.cpp @@ -6,8 +6,8 @@ // //===----------------------------------------------------------------------===// +// UNSUPPORTED: c++03 // UNSUPPORTED: no-threads -// ALLOW_RETRIES: 2 // @@ -17,58 +17,118 @@ // bool try_lock_until(const chrono::time_point& abs_time); #include -#include -#include +#include #include +#include +#include #include "make_test_thread.h" -#include "test_macros.h" - -std::recursive_timed_mutex m; - -typedef std::chrono::steady_clock Clock; -typedef Clock::time_point time_point; -typedef Clock::duration duration; -typedef std::chrono::milliseconds ms; -typedef std::chrono::nanoseconds ns; - -void f1() -{ - time_point t0 = Clock::now(); - assert(m.try_lock_until(Clock::now() + ms(300)) == true); - time_point t1 = Clock::now(); - assert(m.try_lock()); - m.unlock(); - m.unlock(); - ns d = t1 - t0 - ms(250); - assert(d < ms(50)); // within 50ms + +bool is_lockable(std::recursive_timed_mutex& m) { + bool did_lock; + std::thread t = support::make_test_thread([&] { + did_lock = m.try_lock(); + if (did_lock) + m.unlock(); // undo side effects + }); + t.join(); + + return did_lock; } -void f2() -{ - time_point t0 = Clock::now(); - assert(m.try_lock_until(Clock::now() + ms(250)) == false); - time_point t1 = Clock::now(); - ns d = t1 - t0 - ms(250); - assert(d < ms(50)); // within 50ms +template +std::chrono::microseconds measure(Function f) { + std::chrono::high_resolution_clock::time_point start = std::chrono::high_resolution_clock::now(); + f(); + std::chrono::high_resolution_clock::time_point end = std::chrono::high_resolution_clock::now(); + return std::chrono::duration_cast(end - start); } -int main(int, char**) -{ - { - m.lock(); - std::thread t = support::make_test_thread(f1); - std::this_thread::sleep_for(ms(250)); - m.unlock(); - t.join(); +int main(int, char**) { + // Try to lock a mutex that is not locked yet. This should succeed immediately. + { + std::recursive_timed_mutex m; + bool succeeded = m.try_lock_until(std::chrono::steady_clock::now() + std::chrono::milliseconds(1)); + assert(succeeded); + m.unlock(); + } + + // Lock a mutex that is already locked by this thread. This should succeed immediately and the mutex + // should only be unlocked after a matching number of calls to unlock() on the same thread. + { + std::recursive_timed_mutex m; + int lock_count = 0; + for (int i = 0; i != 10; ++i) { + assert(m.try_lock_until(std::chrono::steady_clock::now() + std::chrono::milliseconds(1))); + ++lock_count; } - { - m.lock(); - std::thread t = support::make_test_thread(f2); - std::this_thread::sleep_for(ms(300)); - m.unlock(); - t.join(); + while (lock_count != 0) { + assert(!is_lockable(m)); + m.unlock(); + --lock_count; } + assert(is_lockable(m)); + } + + // Try to lock an already-locked mutex for a long enough amount of time and succeed. + // This is technically flaky, but we use such long durations that it should pass even + // in slow or contended environments. + { + std::chrono::milliseconds const wait_time(500); + std::chrono::milliseconds const tolerance = wait_time * 3; + std::atomic ready(false); + + std::recursive_timed_mutex m; + m.lock(); + + std::thread t = support::make_test_thread([&] { + auto elapsed = measure([&] { + ready = true; + bool succeeded = m.try_lock_until(std::chrono::steady_clock::now() + wait_time); + assert(succeeded); + m.unlock(); + }); + + // Ensure we didn't wait significantly longer than our timeout. This is technically + // flaky and non-conforming because an implementation is free to block for arbitrarily + // long, but any decent quality implementation should pass this test. + assert(elapsed - wait_time < tolerance); + }); + + // Wait for the thread to be ready to take the lock before we unlock it from here, otherwise + // there's a high chance that we're not testing the "locking an already locked" mutex use case. + // There is still technically a race condition here. + while (!ready) + /* spin */; + std::this_thread::sleep_for(wait_time / 5); + + m.unlock(); // this should allow the thread to lock 'm' + t.join(); + } + + // Try to lock an already-locked mutex for a short amount of time and fail. + // Again, this is technically flaky but we use such long durations that it should work. + { + std::chrono::milliseconds const wait_time(10); + std::chrono::milliseconds const tolerance(750); // in case the thread we spawned goes to sleep or something + + std::recursive_timed_mutex m; + m.lock(); + + std::thread t = support::make_test_thread([&] { + auto elapsed = measure([&] { + bool succeeded = m.try_lock_until(std::chrono::steady_clock::now() + wait_time); + assert(!succeeded); + }); + + // Ensure we failed within some bounded time. + assert(elapsed - wait_time < tolerance); + }); + + t.join(); + + m.unlock(); + } return 0; }