190 lines · cpp
1//===----------------------------------------------------------------------===//2//3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.4// See https://llvm.org/LICENSE.txt for license information.5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception6//7//===----------------------------------------------------------------------===//8//9// UNSUPPORTED: no-threads, c++0310 11// <condition_variable>12 13// class condition_variable_any;14 15// template <class Lock, class Duration, class Predicate>16// bool17// wait_until(Lock& lock,18// const chrono::time_point<Clock, Duration>& abs_time,19// Predicate pred);20 21#include <condition_variable>22#include <atomic>23#include <cassert>24#include <chrono>25#include <mutex>26#include <thread>27 28#include "make_test_thread.h"29#include "test_macros.h"30 31struct TestClock {32 typedef std::chrono::milliseconds duration;33 typedef duration::rep rep;34 typedef duration::period period;35 typedef std::chrono::time_point<TestClock> time_point;36 static const bool is_steady = true;37 38 static time_point now() {39 using namespace std::chrono;40 return time_point(duration_cast<duration>(steady_clock::now().time_since_epoch()));41 }42};43 44template <class Mutex>45struct MyLock : std::unique_lock<Mutex> {46 using std::unique_lock<Mutex>::unique_lock;47};48 49template <class Lock, class Clock>50void test() {51 using Mutex = typename Lock::mutex_type;52 // Test unblocking via a call to notify_one() in another thread.53 //54 // To test this, we set a very long timeout in wait_until() and we try to minimize55 // the likelihood that we got awoken by a spurious wakeup by updating the56 // likely_spurious flag only immediately before we perform the notification.57 {58 std::atomic<bool> ready(false);59 std::atomic<bool> likely_spurious(true);60 auto timeout = Clock::now() + std::chrono::seconds(3600);61 std::condition_variable_any cv;62 Mutex mutex;63 64 std::thread t1 = support::make_test_thread([&] {65 Lock lock(mutex);66 ready = true;67 bool result = cv.wait_until(lock, timeout, [&] { return !likely_spurious; });68 assert(result); // return value should be true since we didn't time out69 assert(Clock::now() < timeout);70 });71 72 std::thread t2 = support::make_test_thread([&] {73 while (!ready) {74 // spin75 }76 77 // Acquire the same mutex as t1. This ensures that the condition variable has started78 // waiting (and hence released that mutex).79 Lock lock(mutex);80 81 likely_spurious = false;82 lock.unlock();83 cv.notify_one();84 });85 86 t2.join();87 t1.join();88 }89 90 // Test unblocking via a timeout.91 //92 // To test this, we create a thread that waits on a condition variable with a certain93 // timeout, and we never awaken it. The "stop waiting" predicate always returns false,94 // which means that we can't get out of the wait via a spurious wakeup.95 {96 auto timeout = Clock::now() + std::chrono::milliseconds(250);97 std::condition_variable_any cv;98 Mutex mutex;99 100 std::thread t1 = support::make_test_thread([&] {101 Lock lock(mutex);102 bool result = cv.wait_until(lock, timeout, [] { return false; }); // never stop waiting (until timeout)103 assert(!result); // return value should be false since the predicate returns false after the timeout104 assert(Clock::now() >= timeout);105 });106 107 t1.join();108 }109 110 // Test unblocking via a spurious wakeup.111 //112 // To test this, we set a fairly long timeout in wait_until() and we basically never113 // wake up the condition variable. This way, we are hoping to get out of the wait114 // via a spurious wakeup.115 //116 // However, since spurious wakeups are not required to even happen, this test is117 // only trying to trigger that code path, but not actually asserting that it is118 // taken. In particular, we do need to eventually ensure we get out of the wait119 // by standard means, so we actually wake up the thread at the end.120 {121 std::atomic<bool> ready(false);122 std::atomic<bool> awoken(false);123 auto timeout = Clock::now() + std::chrono::seconds(3600);124 std::condition_variable_any cv;125 Mutex mutex;126 127 std::thread t1 = support::make_test_thread([&] {128 Lock lock(mutex);129 ready = true;130 bool result = cv.wait_until(lock, timeout, [&] { return true; });131 awoken = true;132 assert(result); // return value should be true since we didn't time out133 assert(Clock::now() < timeout); // can technically fail if t2 never executes and we timeout, but very unlikely134 });135 136 std::thread t2 = support::make_test_thread([&] {137 while (!ready) {138 // spin139 }140 141 // Acquire the same mutex as t1. This ensures that the condition variable has started142 // waiting (and hence released that mutex).143 Lock lock(mutex);144 lock.unlock();145 146 // Give some time for t1 to be awoken spuriously so that code path is used.147 std::this_thread::sleep_for(std::chrono::seconds(1));148 149 // We would want to assert that the thread has been awoken after this time,150 // however nothing guarantees us that it ever gets spuriously awoken, so151 // we can't really check anything. This is still left here as documentation.152 bool woke = awoken.load();153 assert(woke || !woke);154 155 // Whatever happened, actually awaken the condition variable to ensure the test156 // doesn't keep running until the timeout.157 cv.notify_one();158 });159 160 t2.join();161 t1.join();162 }163}164 165int main(int, char**) {166 // Run on multiple threads to speed up the test, and because it ought to work anyways.167 std::thread tests[] = {168 support::make_test_thread([] {169 test<std::unique_lock<std::mutex>, TestClock>();170 test<std::unique_lock<std::mutex>, std::chrono::steady_clock>();171 }),172 support::make_test_thread([] {173 test<std::unique_lock<std::timed_mutex>, TestClock>();174 test<std::unique_lock<std::timed_mutex>, std::chrono::steady_clock>();175 }),176 support::make_test_thread([] {177 test<MyLock<std::mutex>, TestClock>();178 test<MyLock<std::mutex>, std::chrono::steady_clock>();179 }),180 support::make_test_thread([] {181 test<MyLock<std::timed_mutex>, TestClock>();182 test<MyLock<std::timed_mutex>, std::chrono::steady_clock>();183 })};184 185 for (std::thread& t : tests)186 t.join();187 188 return 0;189}190