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-exception
6//
7//===----------------------------------------------------------------------===//
8
9// UNSUPPORTED: no-exceptions
10// UNSUPPORTED: no-threads
11
12// <condition_variable>
13
14// class condition_variable_any;
15
16// RUN: %{build}
17// RUN: %{run} 1
18// RUN: %{run} 2
19// RUN: %{run} 3
20// RUN: %{run} 4
21// RUN: %{run} 5
22// RUN: %{run} 6
23// RUN: %{run} 7
24// RUN: %{run} 8
25// RUN: %{run} 9
26
27// -----------------------------------------------------------------------------
28// Overview
29// Check that std::terminate is called if wait(...) fails to meet its post
30// conditions. This can happen when reacquiring the mutex throws
31// an exception.
32//
33// The following methods are tested within this file
34// 1. void wait(Lock& lock);
35// 2. void wait(Lock& lock, Pred);
36// 3. void wait_for(Lock& lock, Duration);
37// 4. void wait_for(Lock& lock, Duration, Pred);
38// 5. void wait_until(Lock& lock, TimePoint);
39// 6. void wait_until(Lock& lock, TimePoint, Pred);
40// 7. bool wait(Lock& lock, stop_token stoken, Predicate pred);
41// 8. bool wait_for(Lock& lock, stop_token stoken, Duration, Predicate pred);
42// 9. bool wait_until(Lock& lock, stop_token stoken, TimePoint, Predicate pred);
43//
44// Plan
45// 1 Create a mutex type, 'ThrowingMutex', that throws when the lock is acquired
46// for the *second* time.
47//
48// 2 Replace the terminate handler with one that exits with a '0' exit code.
49//
50// 3 Create a 'condition_variable_any' object 'cv' and a 'ThrowingMutex'
51// object 'm' and lock 'm'.
52//
53// 4 Start a thread 'T2' that will notify 'cv' once 'm' has been unlocked.
54//
55// 5 From the main thread call the specified wait method on 'cv' with 'm'.
56// When 'T2' notifies 'cv' and the wait method attempts to re-lock
57// 'm' an exception will be thrown from 'm.lock()'.
58//
59// 6 Check that control flow does not return from the wait method and that
60// terminate is called (If the program exits with a 0 exit code we know
61// that terminate has been called)
62
63
64#include <atomic>
65#include <cassert>
66#include <chrono>
67#include <condition_variable>
68#include <cstdlib>
69#include <exception>
70#include <string>
71#include <stop_token>
72#include <thread>
73
74#include "make_test_thread.h"
75#include "test_macros.h"
76
77void my_terminate() {
78 std::_Exit(status: 0); // Use _Exit to prevent cleanup from taking place.
79}
80
81// The predicate used in the cv.wait calls.
82bool pred = false;
83bool pred_function() {
84 return pred == true;
85}
86
87class ThrowingMutex
88{
89 std::atomic_bool locked;
90 unsigned state = 0;
91 ThrowingMutex(const ThrowingMutex&) = delete;
92 ThrowingMutex& operator=(const ThrowingMutex&) = delete;
93public:
94 ThrowingMutex() {
95 locked = false;
96 }
97 ~ThrowingMutex() = default;
98
99 void lock() {
100 locked = true;
101 if (++state == 2) {
102 assert(pred); // Check that we actually waited until we were signaled.
103 throw 1; // this throw should end up calling terminate()
104 }
105 }
106
107 void unlock() { locked = false; }
108 bool isLocked() const { return locked == true; }
109};
110
111ThrowingMutex mut;
112std::condition_variable_any cv;
113
114void signal_me() {
115 while (mut.isLocked()) {} // wait until T1 releases mut inside the cv.wait call.
116 pred = true;
117 cv.notify_one();
118}
119
120typedef std::chrono::system_clock Clock;
121typedef std::chrono::milliseconds MS;
122
123int main(int argc, char **argv) {
124 assert(argc == 2);
125 int id = std::stoi(str: argv[1]);
126 assert(id >= 1 && id <= 9);
127 std::set_terminate(my_terminate); // set terminate after std::stoi because it can throw.
128 MS wait(250);
129 try {
130 mut.lock();
131 assert(pred == false);
132 support::make_test_thread(signal_me).detach();
133 switch (id) {
134 case 1: cv.wait(lock&: mut); break;
135 case 2: cv.wait(lock&: mut, p: pred_function); break;
136 case 3: cv.wait_for(lock&: mut, rtime: wait); break;
137 case 4: cv.wait_for(lock&: mut, rtime: wait, p: pred_function); break;
138 case 5: cv.wait_until(lock&: mut, atime: Clock::now() + wait); break;
139 case 6: cv.wait_until(lock&: mut, atime: Clock::now() + wait, p: pred_function); break;
140#if TEST_STD_VER >= 20 && !defined(_LIBCPP_HAS_NO_EXPERIMENTAL_STOP_TOKEN) && _LIBCPP_AVAILABILITY_HAS_SYNC
141 case 7: cv.wait(mut, std::stop_source{}.get_token(), pred_function); break;
142 case 8: cv.wait_for(mut, std::stop_source{}.get_token(), wait, pred_function); break;
143 case 9: cv.wait_until(mut, std::stop_source{}.get_token(), Clock::now() + wait, pred_function); break;
144#else
145 case 7:
146 case 8:
147 case 9:
148 return 0;
149#endif //TEST_STD_VER >=20 && !defined(_LIBCPP_HAS_NO_EXPERIMENTAL_STOP_TOKEN)
150 default: assert(false);
151 }
152 } catch (...) {}
153 assert(false);
154
155 return 0;
156}
157

source code of libcxx/test/std/thread/thread.condition/thread.condition.condvarany/wait_terminates.sh.cpp