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-threads |
10 | // UNSUPPORTED: c++03, c++11, c++14, c++17 |
11 | // XFAIL: availability-synchronization_library-missing |
12 | |
13 | // template<class C> |
14 | // explicit stop_callback(const stop_token& st, C&& cb) |
15 | // noexcept(is_nothrow_constructible_v<Callback, C>); |
16 | |
17 | #include <atomic> |
18 | #include <cassert> |
19 | #include <chrono> |
20 | #include <stop_token> |
21 | #include <type_traits> |
22 | #include <utility> |
23 | #include <vector> |
24 | |
25 | #include "make_test_thread.h" |
26 | #include "test_macros.h" |
27 | |
28 | struct Cb { |
29 | void operator()() const; |
30 | }; |
31 | |
32 | // Constraints: Callback and C satisfy constructible_from<Callback, C>. |
33 | static_assert(std::is_constructible_v<std::stop_callback<void (*)()>, const std::stop_token&, void (*)()>); |
34 | static_assert(!std::is_constructible_v<std::stop_callback<void (*)()>, const std::stop_token&, void (*)(int)>); |
35 | static_assert(std::is_constructible_v<std::stop_callback<Cb>, const std::stop_token&, Cb&>); |
36 | static_assert(std::is_constructible_v<std::stop_callback<Cb&>, const std::stop_token&, Cb&>); |
37 | static_assert(!std::is_constructible_v<std::stop_callback<Cb>, const std::stop_token&, int>); |
38 | |
39 | // explicit |
40 | template <class T> |
41 | void conversion_test(T); |
42 | |
43 | template <class T, class... Args> |
44 | concept ImplicitlyConstructible = requires(Args&&... args) { conversion_test<T>({std::forward<Args>(args)...}); }; |
45 | static_assert(ImplicitlyConstructible<int, int>); |
46 | static_assert(!ImplicitlyConstructible<std::stop_callback<Cb>, const std::stop_token&, Cb>); |
47 | |
48 | // noexcept |
49 | template <bool NoExceptCtor> |
50 | struct CbNoExcept { |
51 | CbNoExcept(int) noexcept(NoExceptCtor); |
52 | void operator()() const; |
53 | }; |
54 | static_assert(std::is_nothrow_constructible_v<std::stop_callback<CbNoExcept<true>>, const std::stop_token&, int>); |
55 | static_assert(!std::is_nothrow_constructible_v<std::stop_callback<CbNoExcept<false>>, const std::stop_token&, int>); |
56 | |
57 | int main(int, char**) { |
58 | // was requested |
59 | { |
60 | std::stop_source ss; |
61 | const std::stop_token st = ss.get_token(); |
62 | ss.request_stop(); |
63 | |
64 | bool called = false; |
65 | std::stop_callback sc(st, [&] { called = true; }); |
66 | assert(called); |
67 | } |
68 | |
69 | // was not requested |
70 | { |
71 | std::stop_source ss; |
72 | const std::stop_token st = ss.get_token(); |
73 | |
74 | bool called = false; |
75 | std::stop_callback sc(st, [&] { called = true; }); |
76 | assert(!called); |
77 | |
78 | ss.request_stop(); |
79 | assert(called); |
80 | } |
81 | |
82 | // token has no state |
83 | { |
84 | std::stop_token st; |
85 | bool called = false; |
86 | std::stop_callback sc(st, [&] { called = true; }); |
87 | assert(!called); |
88 | } |
89 | |
90 | // should not be called multiple times |
91 | { |
92 | std::stop_source ss; |
93 | const std::stop_token st = ss.get_token(); |
94 | |
95 | int calledTimes = 0; |
96 | std::stop_callback sc(st, [&] { ++calledTimes; }); |
97 | |
98 | std::vector<std::thread> threads; |
99 | for (auto i = 0; i < 10; ++i) { |
100 | threads.emplace_back(support::make_test_thread([&] { ss.request_stop(); })); |
101 | } |
102 | |
103 | for (auto& thread : threads) { |
104 | thread.join(); |
105 | } |
106 | assert(calledTimes == 1); |
107 | } |
108 | |
109 | // adding more callbacks during invoking other callbacks |
110 | { |
111 | std::stop_source ss; |
112 | const std::stop_token st = ss.get_token(); |
113 | |
114 | std::atomic<bool> startedFlag = false; |
115 | std::atomic<bool> finishFlag = false; |
116 | std::stop_callback sc(st, [&] { |
117 | startedFlag = true; |
118 | startedFlag.notify_all(); |
119 | finishFlag.wait(false); |
120 | }); |
121 | |
122 | auto thread = support::make_test_thread([&] { ss.request_stop(); }); |
123 | |
124 | startedFlag.wait(false); |
125 | |
126 | // first callback is still running, adding another one; |
127 | bool secondCallbackCalled = false; |
128 | std::stop_callback sc2(st, [&] { secondCallbackCalled = true; }); |
129 | |
130 | finishFlag = true; |
131 | finishFlag.notify_all(); |
132 | |
133 | thread.join(); |
134 | assert(secondCallbackCalled); |
135 | } |
136 | |
137 | // adding callbacks on different threads |
138 | { |
139 | std::stop_source ss; |
140 | const std::stop_token st = ss.get_token(); |
141 | |
142 | std::vector<std::thread> threads; |
143 | std::atomic<int> callbackCalledTimes = 0; |
144 | std::atomic<bool> done = false; |
145 | for (auto i = 0; i < 10; ++i) { |
146 | threads.emplace_back(support::make_test_thread([&] { |
147 | std::stop_callback sc{st, [&] { callbackCalledTimes.fetch_add(1, std::memory_order_relaxed); }}; |
148 | done.wait(false); |
149 | })); |
150 | } |
151 | using namespace std::chrono_literals; |
152 | std::this_thread::sleep_for(1ms); |
153 | ss.request_stop(); |
154 | done = true; |
155 | done.notify_all(); |
156 | for (auto& thread : threads) { |
157 | thread.join(); |
158 | } |
159 | assert(callbackCalledTimes.load(std::memory_order_relaxed) == 10); |
160 | } |
161 | |
162 | // correct overload |
163 | { |
164 | struct CBWithTracking { |
165 | bool& lvalueCalled; |
166 | bool& lvalueConstCalled; |
167 | bool& rvalueCalled; |
168 | bool& rvalueConstCalled; |
169 | |
170 | void operator()() & { lvalueCalled = true; } |
171 | void operator()() const& { lvalueConstCalled = true; } |
172 | void operator()() && { rvalueCalled = true; } |
173 | void operator()() const&& { rvalueConstCalled = true; } |
174 | }; |
175 | |
176 | // RValue |
177 | { |
178 | bool lvalueCalled = false; |
179 | bool lvalueConstCalled = false; |
180 | bool rvalueCalled = false; |
181 | bool rvalueConstCalled = false; |
182 | std::stop_source ss; |
183 | const std::stop_token st = ss.get_token(); |
184 | ss.request_stop(); |
185 | |
186 | std::stop_callback<CBWithTracking> sc( |
187 | st, CBWithTracking{lvalueCalled, lvalueConstCalled, rvalueCalled, rvalueConstCalled}); |
188 | assert(rvalueCalled); |
189 | } |
190 | |
191 | // RValue |
192 | { |
193 | bool lvalueCalled = false; |
194 | bool lvalueConstCalled = false; |
195 | bool rvalueCalled = false; |
196 | bool rvalueConstCalled = false; |
197 | std::stop_source ss; |
198 | const std::stop_token st = ss.get_token(); |
199 | ss.request_stop(); |
200 | |
201 | std::stop_callback<const CBWithTracking> sc( |
202 | st, CBWithTracking{lvalueCalled, lvalueConstCalled, rvalueCalled, rvalueConstCalled}); |
203 | assert(rvalueConstCalled); |
204 | } |
205 | |
206 | // LValue |
207 | { |
208 | bool lvalueCalled = false; |
209 | bool lvalueConstCalled = false; |
210 | bool rvalueCalled = false; |
211 | bool rvalueConstCalled = false; |
212 | std::stop_source ss; |
213 | const std::stop_token st = ss.get_token(); |
214 | ss.request_stop(); |
215 | CBWithTracking cb{.lvalueCalled: lvalueCalled, .lvalueConstCalled: lvalueConstCalled, .rvalueCalled: rvalueCalled, .rvalueConstCalled: rvalueConstCalled}; |
216 | std::stop_callback<CBWithTracking&> sc(st, cb); |
217 | assert(lvalueCalled); |
218 | } |
219 | |
220 | // const LValue |
221 | { |
222 | bool lvalueCalled = false; |
223 | bool lvalueConstCalled = false; |
224 | bool rvalueCalled = false; |
225 | bool rvalueConstCalled = false; |
226 | std::stop_source ss; |
227 | const std::stop_token st = ss.get_token(); |
228 | ss.request_stop(); |
229 | CBWithTracking cb{.lvalueCalled: lvalueCalled, .lvalueConstCalled: lvalueConstCalled, .rvalueCalled: rvalueCalled, .rvalueConstCalled: rvalueConstCalled}; |
230 | std::stop_callback<const CBWithTracking&> sc(st, cb); |
231 | assert(lvalueConstCalled); |
232 | } |
233 | } |
234 | |
235 | return 0; |
236 | } |
237 | |