[libcxx-commits] [libcxx] [libc++] Refactor the tests for mutex, recursive mutex and their timed counterparts (PR #104852)

Louis Dionne via libcxx-commits libcxx-commits at lists.llvm.org
Mon Aug 19 13:40:22 PDT 2024


https://github.com/ldionne created https://github.com/llvm/llvm-project/pull/104852

None

>From 7daa966d958643f636a29c1506eb1dd30a2744a8 Mon Sep 17 00:00:00 2001
From: Louis Dionne <ldionne.2 at gmail.com>
Date: Mon, 19 Aug 2024 16:34:39 -0400
Subject: [PATCH] [libc++] Refactor the tests for mutex, recursive mutex and
 their timed counterparts

---
 ...mpile.fail.cpp => assign.compile.pass.cpp} |  12 +-
 ...le.fail.cpp => ctor.copy.compile.pass.cpp} |  11 +-
 ...default.pass.cpp => ctor.default.pass.cpp} |  20 ++-
 .../thread.mutex.class/lock.pass.cpp          |  77 ++++++---
 .../thread.mutex.class/try_lock.pass.cpp      |  56 +++----
 ...mpile.fail.cpp => assign.compile.pass.cpp} |  12 +-
 ...le.fail.cpp => ctor.copy.compile.pass.cpp} |  11 +-
 ...default.pass.cpp => ctor.default.pass.cpp} |  14 +-
 .../thread.mutex.recursive/lock.pass.cpp      | 102 +++++++++---
 .../thread.mutex.recursive/try_lock.pass.cpp  |  87 ++++++----
 ...mpile.fail.cpp => assign.compile.pass.cpp} |  12 +-
 ...le.fail.cpp => ctor.copy.compile.pass.cpp} |  11 +-
 ...default.pass.cpp => ctor.default.pass.cpp} |  11 +-
 .../thread.timedmutex.class/lock.pass.cpp     |  75 ++++++---
 .../thread.timedmutex.class/try_lock.pass.cpp |  54 +++----
 .../try_lock_for.pass.cpp                     | 117 +++++++++-----
 .../try_lock_until.pass.cpp                   | 117 +++++++++-----
 ...mpile.fail.cpp => assign.compile.pass.cpp} |  12 +-
 ...le.fail.cpp => ctor.copy.compile.pass.cpp} |  11 +-
 ...default.pass.cpp => ctor.default.pass.cpp} |  11 +-
 .../thread.timedmutex.recursive/lock.pass.cpp | 102 +++++++++---
 .../try_lock.pass.cpp                         |  85 ++++++----
 .../try_lock_for.pass.cpp                     | 148 ++++++++++++------
 .../try_lock_until.pass.cpp                   | 148 ++++++++++++------
 24 files changed, 838 insertions(+), 478 deletions(-)
 rename libcxx/test/std/thread/thread.mutex/thread.mutex.requirements/thread.mutex.requirements.mutex/thread.mutex.class/{assign.compile.fail.cpp => assign.compile.pass.cpp} (80%)
 rename libcxx/test/std/thread/thread.mutex/thread.mutex.requirements/thread.mutex.requirements.mutex/thread.mutex.class/{copy.compile.fail.cpp => ctor.copy.compile.pass.cpp} (79%)
 rename libcxx/test/std/thread/thread.mutex/thread.mutex.requirements/thread.mutex.requirements.mutex/thread.mutex.class/{default.pass.cpp => ctor.default.pass.cpp} (64%)
 rename libcxx/test/std/thread/thread.mutex/thread.mutex.requirements/thread.mutex.requirements.mutex/thread.mutex.recursive/{assign.compile.fail.cpp => assign.compile.pass.cpp} (79%)
 rename libcxx/test/std/thread/thread.mutex/thread.mutex.requirements/thread.mutex.requirements.mutex/thread.mutex.recursive/{copy.compile.fail.cpp => ctor.copy.compile.pass.cpp} (79%)
 rename libcxx/test/std/thread/thread.mutex/thread.mutex.requirements/thread.mutex.requirements.mutex/thread.mutex.recursive/{default.pass.cpp => ctor.default.pass.cpp} (75%)
 rename libcxx/test/std/thread/thread.mutex/thread.mutex.requirements/thread.timedmutex.requirements/thread.timedmutex.class/{assign.compile.fail.cpp => assign.compile.pass.cpp} (80%)
 rename libcxx/test/std/thread/thread.mutex/thread.mutex.requirements/thread.timedmutex.requirements/thread.timedmutex.class/{copy.compile.fail.cpp => ctor.copy.compile.pass.cpp} (79%)
 rename libcxx/test/std/thread/thread.mutex/thread.mutex.requirements/thread.timedmutex.requirements/thread.timedmutex.class/{default.pass.cpp => ctor.default.pass.cpp} (77%)
 rename libcxx/test/std/thread/thread.mutex/thread.mutex.requirements/thread.timedmutex.requirements/thread.timedmutex.recursive/{assign.compile.fail.cpp => assign.compile.pass.cpp} (79%)
 rename libcxx/test/std/thread/thread.mutex/thread.mutex.requirements/thread.timedmutex.requirements/thread.timedmutex.recursive/{copy.compile.fail.cpp => ctor.copy.compile.pass.cpp} (78%)
 rename libcxx/test/std/thread/thread.mutex/thread.mutex.requirements/thread.timedmutex.requirements/thread.timedmutex.recursive/{default.pass.cpp => ctor.default.pass.cpp} (77%)

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 ba09ed1a706ea7..5f5274a6c0027a 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
+
 // <mutex>
 
 // class mutex;
@@ -13,12 +15,6 @@
 // mutex& operator=(const mutex&) = delete;
 
 #include <mutex>
+#include <type_traits>
 
-int main(int, char**)
-{
-    std::mutex m0;
-    std::mutex m1;
-    m1 = m0;
-
-  return 0;
-}
+static_assert(!std::is_copy_assignable<std::mutex>::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 9edfb7267dee6f..74d0dfda41ad1b 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
+
 // <mutex>
 
 // class mutex;
@@ -13,11 +15,6 @@
 // mutex(const mutex&) = delete;
 
 #include <mutex>
+#include <type_traits>
 
-int main(int, char**)
-{
-    std::mutex m0;
-    std::mutex m1(m0);
-
-  return 0;
-}
+static_assert(!std::is_copy_constructible<std::mutex>::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 d8115cd1bf3a08..7bda43087063af 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
 
 // <mutex>
 
 // class mutex;
 
-// mutex();
+// mutex() noexcept;
 
 #include <mutex>
+#include <cassert>
 #include <type_traits>
 
-#include "test_macros.h"
+static_assert(std::is_nothrow_default_constructible<std::mutex>::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<std::mutex>::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 b3e76cf886c4d4..e2bd2de84c33ce 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
 
 // <mutex>
 
@@ -15,40 +15,67 @@
 
 // void lock();
 
-#include <cassert>
-#include <chrono>
-#include <cstdlib>
 #include <mutex>
+#include <atomic>
+#include <cassert>
 #include <thread>
+#include <vector>
 
 #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<bool> ready(false);
+    std::mutex m;
     m.lock();
-    std::thread t = support::make_test_thread(f);
-    std::this_thread::sleep_for(ms(250));
+    std::atomic<bool> 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<int> counter(0);
+    std::mutex mutex;
+
+    std::vector<std::thread> 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 bf3cb6530b3b94..db8b809c08d365 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
 
 // <mutex>
 
@@ -15,44 +15,36 @@
 
 // bool try_lock();
 
-#include <cassert>
-#include <chrono>
-#include <cstdlib>
 #include <mutex>
+#include <cassert>
 #include <thread>
 
 #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 0cf3c5bca1e1b4..fadd9a7cae28cc 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
+
 // <mutex>
 
 // class recursive_mutex;
@@ -13,12 +15,6 @@
 // recursive_mutex& operator=(const recursive_mutex&) = delete;
 
 #include <mutex>
+#include <type_traits>
 
-int main(int, char**)
-{
-    std::recursive_mutex m0;
-    std::recursive_mutex m1;
-    m1 = m0;
-
-  return 0;
-}
+static_assert(!std::is_copy_assignable<std::recursive_mutex>::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 454d7797373cac..bd63224f35d70c 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
+
 // <mutex>
 
 // class recursive_mutex;
@@ -13,11 +15,6 @@
 // recursive_mutex(const recursive_mutex&) = delete;
 
 #include <mutex>
+#include <type_traits>
 
-int main(int, char**)
-{
-    std::recursive_mutex m0;
-    std::recursive_mutex m1(m0);
-
-  return 0;
-}
+static_assert(!std::is_copy_constructible<std::recursive_mutex>::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 43dc38d7cab517..cd2694e8c43c8b 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
 
 // <mutex>
@@ -15,12 +15,16 @@
 // recursive_mutex();
 
 #include <mutex>
+#include <cassert>
+#include <type_traits>
 
-#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 d9bff9b3cbda52..344667fa705d2c 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
 
 // <mutex>
 
@@ -15,42 +15,96 @@
 
 // void lock();
 
-#include <cassert>
-#include <chrono>
-#include <cstdlib>
 #include <mutex>
+#include <atomic>
+#include <cassert>
 #include <thread>
+#include <vector>
 
 #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<bool> 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<bool> 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<int> counter(0);
+    std::recursive_mutex mutex;
+
+    std::vector<std::thread> 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 1247c1ce1ba5fd..96073eb345306d 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
 
 // <mutex>
 
@@ -15,46 +15,67 @@
 
 // bool try_lock();
 
+#include <mutex>
+#include <atomic>
 #include <cassert>
 #include <chrono>
-#include <cstdlib>
-#include <mutex>
 #include <thread>
 
 #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 d0fabc678f26c7..a046a875b3df5c 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
+
 // <mutex>
 
 // class timed_mutex;
@@ -13,12 +15,6 @@
 // timed_mutex& operator=(const timed_mutex&) = delete;
 
 #include <mutex>
+#include <type_traits>
 
-int main(int, char**)
-{
-    std::timed_mutex m0;
-    std::timed_mutex m1;
-    m1 = m0;
-
-  return 0;
-}
+static_assert(!std::is_copy_assignable<std::timed_mutex>::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 a3efb2feeeedae..3e3a01c6b5b197 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
+
 // <mutex>
 
 // class timed_mutex;
@@ -13,11 +15,6 @@
 // timed_mutex(const timed_mutex&) = delete;
 
 #include <mutex>
+#include <type_traits>
 
-int main(int, char**)
-{
-    std::timed_mutex m0;
-    std::timed_mutex m1(m0);
-
-  return 0;
-}
+static_assert(!std::is_copy_constructible<std::timed_mutex>::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 c7f207372ac421..9bb2d93c5b2103 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 <mutex>
-
-#include "test_macros.h"
+#include <cassert>
 
 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 a71bd3d38b2c37..8893d389becef2 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
 
 // <mutex>
 
@@ -15,40 +15,67 @@
 
 // void lock();
 
-#include <cassert>
-#include <chrono>
-#include <cstdlib>
 #include <mutex>
+#include <atomic>
+#include <cassert>
 #include <thread>
+#include <vector>
 
 #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<bool> 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<bool> 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<int> counter(0);
+    std::timed_mutex mutex;
+
+    std::vector<std::thread> 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 f3942ccb9d8603..9a4c68bc120933 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
 
 // <mutex>
 
@@ -15,44 +15,36 @@
 
 // bool try_lock();
 
-#include <cassert>
-#include <chrono>
-#include <cstdlib>
 #include <mutex>
+#include <cassert>
 #include <thread>
 
 #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 acfa5560962ae3..b1882ad61c6036 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
 
 // <mutex>
 
@@ -17,56 +17,89 @@
 //     bool try_lock_for(const chrono::duration<Rep, Period>& rel_time);
 
 #include <mutex>
-#include <thread>
-#include <cstdlib>
+#include <atomic>
 #include <cassert>
+#include <chrono>
+#include <thread>
 
 #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 <class Function>
+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<std::chrono::microseconds>(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<bool> 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 23385c100807d5..72471ed07dcffc 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
 
 // <mutex>
 
@@ -17,56 +17,89 @@
 //     bool try_lock_until(const chrono::time_point<Clock, Duration>& abs_time);
 
 #include <mutex>
-#include <thread>
-#include <cstdlib>
+#include <atomic>
 #include <cassert>
+#include <chrono>
+#include <thread>
 
 #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 <class Function>
+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<std::chrono::microseconds>(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<bool> 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 44be06d6754133..681679e006235e 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
+
 // <mutex>
 
 // class recursive_timed_mutex;
@@ -13,12 +15,6 @@
 // recursive_timed_mutex& operator=(const recursive_timed_mutex&) = delete;
 
 #include <mutex>
+#include <type_traits>
 
-int main(int, char**)
-{
-    std::recursive_timed_mutex m0;
-    std::recursive_timed_mutex m1;
-    m1 = m0;
-
-  return 0;
-}
+static_assert(!std::is_copy_assignable<std::recursive_timed_mutex>::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 154a0192d14db1..1ac287e08d7f99 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
+
 // <mutex>
 
 // class recursive_timed_mutex;
@@ -13,11 +15,6 @@
 // recursive_timed_mutex(const recursive_timed_mutex&) = delete;
 
 #include <mutex>
+#include <type_traits>
 
-int main(int, char**)
-{
-    std::recursive_timed_mutex m0;
-    std::recursive_timed_mutex m1(m0);
-
-  return 0;
-}
+static_assert(!std::is_copy_constructible<std::recursive_timed_mutex>::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 3096e031855a22..dede7f44bc1960 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 <mutex>
-
-#include "test_macros.h"
+#include <cassert>
 
 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 bad5a4457e5160..695ce508cf7c62 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
 
 // <mutex>
 
@@ -15,42 +15,96 @@
 
 // void lock();
 
-#include <cassert>
-#include <chrono>
-#include <cstdlib>
 #include <mutex>
+#include <atomic>
+#include <cassert>
 #include <thread>
+#include <vector>
 
 #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<bool> 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<bool> 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<int> counter(0);
+    std::recursive_timed_mutex mutex;
+
+    std::vector<std::thread> 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 63be0ac713f8ba..848db63a003cb9 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
 
 // <mutex>
 
@@ -15,46 +15,67 @@
 
 // bool try_lock();
 
+#include <mutex>
+#include <atomic>
 #include <cassert>
 #include <chrono>
-#include <cstdlib>
-#include <mutex>
 #include <thread>
 
 #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 b0b27801c8c74d..c9192f6fad78c1 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
 
 // <mutex>
 
@@ -17,58 +17,118 @@
 //     bool try_lock_for(const chrono::duration<Rep, Period>& rel_time);
 
 #include <mutex>
-#include <thread>
-#include <cstdlib>
+#include <atomic>
 #include <cassert>
+#include <chrono>
+#include <thread>
 
 #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 <class Function>
+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<std::chrono::microseconds>(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<bool> 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 5c5807d6736c92..6579b21a38a26b 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
 
 // <mutex>
 
@@ -17,58 +17,118 @@
 //     bool try_lock_until(const chrono::time_point<Clock, Duration>& abs_time);
 
 #include <mutex>
-#include <thread>
-#include <cstdlib>
+#include <atomic>
 #include <cassert>
+#include <chrono>
+#include <thread>
 
 #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 <class Function>
+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<std::chrono::microseconds>(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<bool> 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;
 }



More information about the libcxx-commits mailing list