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