| 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 | |