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(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 (*)()>, std::stop_token&&, void (*)()>); |
34 | static_assert(!std::is_constructible_v<std::stop_callback<void (*)()>, std::stop_token&&, void (*)(int)>); |
35 | static_assert(std::is_constructible_v<std::stop_callback<Cb>, std::stop_token&&, Cb&>); |
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&&, 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>, 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>>, std::stop_token&&, int>); |
55 | static_assert(!std::is_nothrow_constructible_v<std::stop_callback<CbNoExcept<false>>, std::stop_token&&, int>); |
56 | |
57 | int main(int, char**) { |
58 | // was requested |
59 | { |
60 | std::stop_source ss; |
61 | ss.request_stop(); |
62 | |
63 | bool called = false; |
64 | std::stop_callback sc(ss.get_token(), [&] { called = true; }); |
65 | assert(called); |
66 | } |
67 | |
68 | // was not requested |
69 | { |
70 | std::stop_source ss; |
71 | |
72 | bool called = false; |
73 | std::stop_callback sc(ss.get_token(), [&] { called = true; }); |
74 | assert(!called); |
75 | |
76 | ss.request_stop(); |
77 | assert(called); |
78 | } |
79 | |
80 | // token has no state |
81 | { |
82 | std::stop_token st; |
83 | bool called = false; |
84 | std::stop_callback sc(std::move(st), [&] { called = true; }); |
85 | assert(!called); |
86 | } |
87 | |
88 | // should not be called multiple times |
89 | { |
90 | std::stop_source ss; |
91 | |
92 | int calledTimes = 0; |
93 | std::stop_callback sc(ss.get_token(), [&] { ++calledTimes; }); |
94 | |
95 | std::vector<std::thread> threads; |
96 | for (auto i = 0; i < 10; ++i) { |
97 | threads.emplace_back(support::make_test_thread([&] { ss.request_stop(); })); |
98 | } |
99 | |
100 | for (auto& thread : threads) { |
101 | thread.join(); |
102 | } |
103 | assert(calledTimes == 1); |
104 | } |
105 | |
106 | // adding more callbacks during invoking other callbacks |
107 | { |
108 | std::stop_source ss; |
109 | |
110 | std::atomic<bool> startedFlag = false; |
111 | std::atomic<bool> finishFlag = false; |
112 | std::stop_callback sc(ss.get_token(), [&] { |
113 | startedFlag = true; |
114 | startedFlag.notify_all(); |
115 | finishFlag.wait(false); |
116 | }); |
117 | |
118 | auto thread = support::make_test_thread([&] { ss.request_stop(); }); |
119 | |
120 | startedFlag.wait(false); |
121 | |
122 | // first callback is still running, adding another one; |
123 | bool secondCallbackCalled = false; |
124 | std::stop_callback sc2(ss.get_token(), [&] { secondCallbackCalled = true; }); |
125 | |
126 | finishFlag = true; |
127 | finishFlag.notify_all(); |
128 | |
129 | thread.join(); |
130 | assert(secondCallbackCalled); |
131 | } |
132 | |
133 | // adding callbacks on different threads |
134 | { |
135 | std::stop_source ss; |
136 | |
137 | std::vector<std::thread> threads; |
138 | std::atomic<int> callbackCalledTimes = 0; |
139 | std::atomic<bool> done = false; |
140 | for (auto i = 0; i < 10; ++i) { |
141 | threads.emplace_back(support::make_test_thread([&] { |
142 | std::stop_callback sc{ss.get_token(), [&] { callbackCalledTimes.fetch_add(1, std::memory_order_relaxed); }}; |
143 | done.wait(false); |
144 | })); |
145 | } |
146 | using namespace std::chrono_literals; |
147 | std::this_thread::sleep_for(1ms); |
148 | ss.request_stop(); |
149 | done = true; |
150 | done.notify_all(); |
151 | for (auto& thread : threads) { |
152 | thread.join(); |
153 | } |
154 | assert(callbackCalledTimes.load(std::memory_order_relaxed) == 10); |
155 | } |
156 | |
157 | // correct overload |
158 | { |
159 | struct CBWithTracking { |
160 | bool& lvalueCalled; |
161 | bool& lvalueConstCalled; |
162 | bool& rvalueCalled; |
163 | bool& rvalueConstCalled; |
164 | |
165 | void operator()() & { lvalueCalled = true; } |
166 | void operator()() const& { lvalueConstCalled = true; } |
167 | void operator()() && { rvalueCalled = true; } |
168 | void operator()() const&& { rvalueConstCalled = true; } |
169 | }; |
170 | |
171 | // RValue |
172 | { |
173 | bool lvalueCalled = false; |
174 | bool lvalueConstCalled = false; |
175 | bool rvalueCalled = false; |
176 | bool rvalueConstCalled = false; |
177 | std::stop_source ss; |
178 | ss.request_stop(); |
179 | |
180 | std::stop_callback<CBWithTracking> sc( |
181 | ss.get_token(), CBWithTracking{lvalueCalled, lvalueConstCalled, rvalueCalled, rvalueConstCalled}); |
182 | assert(rvalueCalled); |
183 | } |
184 | |
185 | // RValue |
186 | { |
187 | bool lvalueCalled = false; |
188 | bool lvalueConstCalled = false; |
189 | bool rvalueCalled = false; |
190 | bool rvalueConstCalled = false; |
191 | std::stop_source ss; |
192 | ss.request_stop(); |
193 | |
194 | std::stop_callback<const CBWithTracking> sc( |
195 | ss.get_token(), CBWithTracking{lvalueCalled, lvalueConstCalled, rvalueCalled, rvalueConstCalled}); |
196 | assert(rvalueConstCalled); |
197 | } |
198 | |
199 | // LValue |
200 | { |
201 | bool lvalueCalled = false; |
202 | bool lvalueConstCalled = false; |
203 | bool rvalueCalled = false; |
204 | bool rvalueConstCalled = false; |
205 | std::stop_source ss; |
206 | ss.request_stop(); |
207 | CBWithTracking cb{.lvalueCalled: lvalueCalled, .lvalueConstCalled: lvalueConstCalled, .rvalueCalled: rvalueCalled, .rvalueConstCalled: rvalueConstCalled}; |
208 | std::stop_callback<CBWithTracking&> sc(ss.get_token(), cb); |
209 | assert(lvalueCalled); |
210 | } |
211 | |
212 | // const LValue |
213 | { |
214 | bool lvalueCalled = false; |
215 | bool lvalueConstCalled = false; |
216 | bool rvalueCalled = false; |
217 | bool rvalueConstCalled = false; |
218 | std::stop_source ss; |
219 | ss.request_stop(); |
220 | CBWithTracking cb{.lvalueCalled: lvalueCalled, .lvalueConstCalled: lvalueConstCalled, .rvalueCalled: rvalueCalled, .rvalueConstCalled: rvalueConstCalled}; |
221 | std::stop_callback<const CBWithTracking&> sc(ss.get_token(), cb); |
222 | assert(lvalueConstCalled); |
223 | } |
224 | } |
225 | |
226 | return 0; |
227 | } |
228 | |