1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134
|
//===----------------------------------------------------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
// UNSUPPORTED: c++03
// UNSUPPORTED: no-threads
// <mutex>
// class recursive_timed_mutex;
// template <class Clock, class Duration>
// bool try_lock_until(const chrono::time_point<Clock, Duration>& abs_time);
#include <mutex>
#include <atomic>
#include <cassert>
#include <chrono>
#include <thread>
#include "make_test_thread.h"
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;
}
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**) {
// 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;
}
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;
}
|