Skip to content

[libc++] Refactor flaky tests for std::shared_lock #91779

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
May 21, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,9 @@
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
//

// UNSUPPORTED: no-threads
// UNSUPPORTED: c++03, c++11
// ALLOW_RETRIES: 2

// <shared_mutex>

Expand All @@ -19,87 +18,86 @@
// template<class _Mutex> shared_lock(shared_lock<_Mutex>)
// -> shared_lock<_Mutex>; // C++17

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

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

typedef std::chrono::system_clock Clock;
typedef Clock::time_point time_point;
typedef Clock::duration duration;
typedef std::chrono::milliseconds ms;
typedef std::chrono::nanoseconds ns;

ms WaitTime = ms(250);

// Thread sanitizer causes more overhead and will sometimes cause this test
// to fail. To prevent this we give Thread sanitizer more time to complete the
// test.
#if !defined(TEST_IS_EXECUTED_IN_A_SLOW_ENVIRONMENT)
ms Tolerance = ms(50);
#else
ms Tolerance = ms(50 * 5);
#endif
struct Monitor {
bool lock_shared_called = false;
bool unlock_shared_called = false;
};

std::shared_timed_mutex m;
struct TrackedMutex {
Monitor* monitor = nullptr;

void f()
{
time_point t0 = Clock::now();
time_point t1;
{
std::shared_lock<std::shared_timed_mutex> ul(m);
t1 = Clock::now();
}
ns d = t1 - t0 - WaitTime;
assert(d < Tolerance); // within tolerance
}
void lock_shared() {
if (monitor != nullptr)
monitor->lock_shared_called = true;
}
void unlock_shared() {
if (monitor != nullptr)
monitor->unlock_shared_called = true;
}
};

void g()
{
time_point t0 = Clock::now();
time_point t1;
{
std::shared_lock<std::shared_timed_mutex> ul(m);
t1 = Clock::now();
}
ns d = t1 - t0;
assert(d < Tolerance); // within tolerance
}
template <class Mutex>
void test() {
// Basic sanity test
{
Mutex mutex;
std::vector<std::thread> threads;
std::atomic<bool> ready(false);
for (int i = 0; i != 5; ++i) {
threads.push_back(support::make_test_thread([&] {
while (!ready) {
// spin
}

int main(int, char**)
{
std::vector<std::thread> v;
{
m.lock();
for (int i = 0; i < 5; ++i)
v.push_back(support::make_test_thread(f));
std::this_thread::sleep_for(WaitTime);
m.unlock();
for (auto& t : v)
t.join();
}
{
m.lock_shared();
for (auto& t : v)
t = support::make_test_thread(g);
std::thread q = support::make_test_thread(f);
std::this_thread::sleep_for(WaitTime);
m.unlock_shared();
for (auto& t : v)
t.join();
q.join();
std::shared_lock<Mutex> lock(mutex);
assert(lock.owns_lock());
}));
}

ready = true;
for (auto& t : threads)
t.join();
}

// Test CTAD
{
#if TEST_STD_VER >= 17
Mutex mutex;
std::shared_lock lock(mutex);
static_assert(std::is_same<decltype(lock), std::shared_lock<Mutex>>::value);
#endif
}
}

int main(int, char**) {
#if TEST_STD_VER >= 17
std::shared_lock sl(m);
static_assert((std::is_same<decltype(sl), std::shared_lock<decltype(m)>>::value), "" );
test<std::shared_mutex>();
#endif
test<std::shared_timed_mutex>();
test<TrackedMutex>();

// Use shared_lock with a dummy mutex class that tracks whether each
// operation has been called or not.
{
Monitor monitor;
TrackedMutex mutex{&monitor};

std::shared_lock<TrackedMutex> lock(mutex);
assert(monitor.lock_shared_called);
assert(lock.owns_lock());

lock.unlock();
assert(monitor.unlock_shared_called);
}

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

// UNSUPPORTED: no-threads
// UNSUPPORTED: c++03, c++11
// ALLOW_RETRIES: 2

// <shared_mutex>

// template <class Mutex> class shared_lock;

// void lock();

#include <atomic>
#include <cassert>
#include <chrono>
#include <cstdlib>
#include <mutex>
#include <mutex> // std::defer_lock
#include <shared_mutex>
#include <system_error>
#include <thread>
Expand All @@ -28,71 +26,99 @@
#include "make_test_thread.h"
#include "test_macros.h"

std::shared_timed_mutex m;
struct Monitor {
bool lock_shared_called = false;
bool unlock_shared_called = false;
};

typedef std::chrono::system_clock Clock;
typedef Clock::time_point time_point;
typedef Clock::duration duration;
typedef std::chrono::milliseconds ms;
typedef std::chrono::nanoseconds ns;
struct TrackedMutex {
Monitor* monitor = nullptr;

ms WaitTime = ms(250);
void lock_shared() {
if (monitor != nullptr)
monitor->lock_shared_called = true;
}
void unlock_shared() {
if (monitor != nullptr)
monitor->unlock_shared_called = true;
}
};

// Thread sanitizer causes more overhead and will sometimes cause this test
// to fail. To prevent this we give Thread sanitizer more time to complete the
// test.
#if !defined(TEST_IS_EXECUTED_IN_A_SLOW_ENVIRONMENT)
ms Tolerance = ms(25);
#else
ms Tolerance = ms(25 * 5);
#endif
template <class Mutex>
void test() {
// Basic sanity test
{
Mutex mutex;
std::vector<std::thread> threads;
std::atomic<bool> ready(false);
for (int i = 0; i != 5; ++i) {
threads.push_back(support::make_test_thread([&] {
while (!ready) {
// spin
}

std::shared_lock<Mutex> lock(mutex, std::defer_lock);
lock.lock();
assert(lock.owns_lock());
}));
}

ready = true;
for (auto& t : threads)
t.join();
}

void f()
{
std::shared_lock<std::shared_timed_mutex> lk(m, std::defer_lock);
time_point t0 = Clock::now();
lk.lock();
time_point t1 = Clock::now();
assert(lk.owns_lock() == true);
ns d = t1 - t0 - WaitTime;
assert(d < Tolerance); // within tolerance
// Try locking the same shared_lock again in the same thread. This should throw an exception.
{
Mutex mutex;
std::shared_lock<Mutex> lock(mutex, std::defer_lock);
lock.lock();
assert(lock.owns_lock());
#ifndef TEST_HAS_NO_EXCEPTIONS
try
{
lk.lock();
assert(false);
}
catch (std::system_error& e)
{
assert(e.code().value() == EDEADLK);
try {
lock.lock();
assert(false);
} catch (std::system_error const& e) {
assert(e.code() == std::errc::resource_deadlock_would_occur);
}
#endif
lk.unlock();
lk.release();
}

// Try locking a shared_lock that isn't associated to any mutex. This should throw an exception.
{
std::shared_lock<Mutex> lock; // no associated mutex
#ifndef TEST_HAS_NO_EXCEPTIONS
try
{
lk.lock();
assert(false);
}
catch (std::system_error& e)
{
assert(e.code().value() == EPERM);
try {
lock.lock();
assert(false);
} catch (std::system_error const& e) {
assert(e.code() == std::errc::operation_not_permitted);
}
#endif
}
}

int main(int, char**)
{
m.lock();
std::vector<std::thread> v;
for (int i = 0; i < 5; ++i)
v.push_back(support::make_test_thread(f));
std::this_thread::sleep_for(WaitTime);
m.unlock();
for (auto& t : v)
t.join();
int main(int, char**) {
#if TEST_STD_VER >= 17
test<std::shared_mutex>();
#endif
test<std::shared_timed_mutex>();
test<TrackedMutex>();

// Use shared_lock with a dummy mutex class that tracks whether each
// operation has been called or not.
{
Monitor monitor;
TrackedMutex mutex{&monitor};

std::shared_lock<TrackedMutex> lock(mutex, std::defer_lock);
lock.lock();
assert(monitor.lock_shared_called);
assert(lock.owns_lock());

lock.unlock();
assert(monitor.unlock_shared_called);
}

return 0;
}
Loading
Loading