Skip to content

Commit

Permalink
[libc++] Refactor tests for shared_mutex and shared_timed_mutex (#100783
Browse files Browse the repository at this point in the history
)

This makes the tests less flaky and also makes a few other refactorings
like using traits instead of .compile.fail.cpp tests.
  • Loading branch information
ldionne authored Jul 31, 2024
1 parent d8b985c commit 29ef92b
Show file tree
Hide file tree
Showing 18 changed files with 900 additions and 624 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
//

// UNSUPPORTED: no-threads
// UNSUPPORTED: c++03, c++11, c++14

Expand All @@ -16,12 +16,6 @@
// shared_mutex& operator=(const shared_mutex&) = delete;

#include <shared_mutex>
#include <type_traits>

int main(int, char**)
{
std::shared_mutex m0;
std::shared_mutex m1;
m1 = m0; // expected-error {{overload resolution selected deleted operator '='}}

return 0;
}
static_assert(!std::is_copy_assignable<std::shared_mutex>::value, "");
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
//

// UNSUPPORTED: no-threads
// UNSUPPORTED: c++03, c++11, c++14

Expand All @@ -16,11 +16,6 @@
// shared_mutex(const shared_mutex&) = delete;

#include <shared_mutex>
#include <type_traits>

int main(int, char**)
{
std::shared_mutex m0;
std::shared_mutex m1(m0); // expected-error {{call to deleted constructor of 'std::shared_mutex'}}

return 0;
}
static_assert(!std::is_copy_constructible<std::shared_mutex>::value, "");
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
//

// UNSUPPORTED: no-threads
// UNSUPPORTED: c++03, c++11, c++14

Expand All @@ -19,10 +19,9 @@

#include "test_macros.h"

int main(int, char**)
{
std::shared_mutex m;
(void)m;
int main(int, char**) {
std::shared_mutex m;
(void)m;

return 0;
return 0;
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,63 +5,105 @@
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
//

// UNSUPPORTED: no-threads
// UNSUPPORTED: c++03, c++11, c++14

// ALLOW_RETRIES: 2

// <shared_mutex>

// class shared_mutex;

// void lock();

#include <shared_mutex>
#include <atomic>
#include <cassert>
#include <chrono>
#include <cstdlib>
#include <shared_mutex>
#include <thread>
#include <vector>

#include "make_test_thread.h"
#include "test_macros.h"

std::shared_mutex m;
int main(int, char**) {
// Exclusive-lock a mutex that is not locked yet. This should succeed.
{
std::shared_mutex m;
m.lock();
m.unlock();
}

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;
// Exclusive-lock a mutex that is already locked exclusively. This should block until it is unlocked.
{
std::atomic<bool> ready(false);
std::shared_mutex m;
m.lock();
std::atomic<bool> is_locked_from_main(true);

ms WaitTime = ms(250);
std::thread t = support::make_test_thread([&] {
ready = true;
m.lock();
assert(!is_locked_from_main);
m.unlock();
});

// Thread sanitizer causes more overhead and will sometimes cause this test
// to fail. To prevent this we give Thread sanitizer more time to complete the
// test.
#if !defined(TEST_IS_EXECUTED_IN_A_SLOW_ENVIRONMENT)
ms Tolerance = ms(50);
#else
ms Tolerance = ms(50 * 5);
#endif
while (!ready)
/* spin */;

void f()
{
time_point t0 = Clock::now();
m.lock();
time_point t1 = Clock::now();
// 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 we're still holding it and for the test to still pass.
is_locked_from_main = false;
m.unlock();
ns d = t1 - t0 - WaitTime;
assert(d < Tolerance); // within tolerance
}

int main(int, char**)
{
m.lock();
std::thread t = support::make_test_thread(f);
std::this_thread::sleep_for(WaitTime);
m.unlock();
t.join();
}

// Exclusive-lock a mutex that is already share-locked. This should block until it is unlocked.
{
std::atomic<bool> ready(false);
std::shared_mutex m;
m.lock_shared();
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 we're still holding it and for the test to still pass.
is_locked_from_main = false;
m.unlock_shared();

t.join();
}

// Make sure that at most one thread can acquire the mutex concurrently.
{
std::atomic<int> counter = 0;
std::shared_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;
}
Loading

0 comments on commit 29ef92b

Please sign in to comment.