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, lvalueConstCalled, rvalueCalled, 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, lvalueConstCalled, rvalueCalled, rvalueConstCalled}; 230 std::stop_callback<const CBWithTracking&> sc(st, cb); 231 assert(lvalueConstCalled); 232 } 233 } 234 235 return 0; 236 } 237