| //===----------------------------------------------------------------------===// | 
 | // | 
 | // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. | 
 | // See https://llvm.org/LICENSE.txt for license information. | 
 | // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception | 
 | // | 
 | //===----------------------------------------------------------------------===// | 
 | // | 
 | // UNSUPPORTED: no-threads | 
 | // UNSUPPORTED: c++03, c++11, c++14, c++17 | 
 | // XFAIL: availability-synchronization_library-missing | 
 |  | 
 | // template<class C> | 
 | // explicit stop_callback(stop_token&& st, C&& cb) | 
 | //   noexcept(is_nothrow_constructible_v<Callback, C>); | 
 |  | 
 | #include <atomic> | 
 | #include <cassert> | 
 | #include <chrono> | 
 | #include <stop_token> | 
 | #include <type_traits> | 
 | #include <utility> | 
 | #include <vector> | 
 |  | 
 | #include "make_test_thread.h" | 
 | #include "test_macros.h" | 
 |  | 
 | struct Cb { | 
 |   void operator()() const; | 
 | }; | 
 |  | 
 | // Constraints: Callback and C satisfy constructible_from<Callback, C>. | 
 | static_assert(std::is_constructible_v<std::stop_callback<void (*)()>, std::stop_token&&, void (*)()>); | 
 | static_assert(!std::is_constructible_v<std::stop_callback<void (*)()>, std::stop_token&&, void (*)(int)>); | 
 | static_assert(std::is_constructible_v<std::stop_callback<Cb>, std::stop_token&&, Cb&>); | 
 | static_assert(std::is_constructible_v<std::stop_callback<Cb&>, std::stop_token&&, Cb&>); | 
 | static_assert(!std::is_constructible_v<std::stop_callback<Cb>, std::stop_token&&, int>); | 
 |  | 
 | // explicit | 
 | template <class T> | 
 | void conversion_test(T); | 
 |  | 
 | template <class T, class... Args> | 
 | concept ImplicitlyConstructible = requires(Args&&... args) { conversion_test<T>({std::forward<Args>(args)...}); }; | 
 | static_assert(ImplicitlyConstructible<int, int>); | 
 | static_assert(!ImplicitlyConstructible<std::stop_callback<Cb>, std::stop_token&&, Cb>); | 
 |  | 
 | // noexcept | 
 | template <bool NoExceptCtor> | 
 | struct CbNoExcept { | 
 |   CbNoExcept(int) noexcept(NoExceptCtor); | 
 |   void operator()() const; | 
 | }; | 
 | static_assert(std::is_nothrow_constructible_v<std::stop_callback<CbNoExcept<true>>, std::stop_token&&, int>); | 
 | static_assert(!std::is_nothrow_constructible_v<std::stop_callback<CbNoExcept<false>>, std::stop_token&&, int>); | 
 |  | 
 | int main(int, char**) { | 
 |   // was requested | 
 |   { | 
 |     std::stop_source ss; | 
 |     ss.request_stop(); | 
 |  | 
 |     bool called = false; | 
 |     std::stop_callback sc(ss.get_token(), [&] { called = true; }); | 
 |     assert(called); | 
 |   } | 
 |  | 
 |   // was not requested | 
 |   { | 
 |     std::stop_source ss; | 
 |  | 
 |     bool called = false; | 
 |     std::stop_callback sc(ss.get_token(), [&] { called = true; }); | 
 |     assert(!called); | 
 |  | 
 |     ss.request_stop(); | 
 |     assert(called); | 
 |   } | 
 |  | 
 |   // token has no state | 
 |   { | 
 |     std::stop_token st; | 
 |     bool called = false; | 
 |     std::stop_callback sc(std::move(st), [&] { called = true; }); | 
 |     assert(!called); | 
 |   } | 
 |  | 
 |   // should not be called multiple times | 
 |   { | 
 |     std::stop_source ss; | 
 |  | 
 |     int calledTimes = 0; | 
 |     std::stop_callback sc(ss.get_token(), [&] { ++calledTimes; }); | 
 |  | 
 |     std::vector<std::thread> threads; | 
 |     for (auto i = 0; i < 10; ++i) { | 
 |       threads.emplace_back(support::make_test_thread([&] { ss.request_stop(); })); | 
 |     } | 
 |  | 
 |     for (auto& thread : threads) { | 
 |       thread.join(); | 
 |     } | 
 |     assert(calledTimes == 1); | 
 |   } | 
 |  | 
 |   // adding more callbacks during invoking other callbacks | 
 |   { | 
 |     std::stop_source ss; | 
 |  | 
 |     std::atomic<bool> startedFlag = false; | 
 |     std::atomic<bool> finishFlag  = false; | 
 |     std::stop_callback sc(ss.get_token(), [&] { | 
 |       startedFlag = true; | 
 |       startedFlag.notify_all(); | 
 |       finishFlag.wait(false); | 
 |     }); | 
 |  | 
 |     auto thread = support::make_test_thread([&] { ss.request_stop(); }); | 
 |  | 
 |     startedFlag.wait(false); | 
 |  | 
 |     // first callback is still running, adding another one; | 
 |     bool secondCallbackCalled = false; | 
 |     std::stop_callback sc2(ss.get_token(), [&] { secondCallbackCalled = true; }); | 
 |  | 
 |     finishFlag = true; | 
 |     finishFlag.notify_all(); | 
 |  | 
 |     thread.join(); | 
 |     assert(secondCallbackCalled); | 
 |   } | 
 |  | 
 |   // adding callbacks on different threads | 
 |   { | 
 |     std::stop_source ss; | 
 |  | 
 |     std::vector<std::thread> threads; | 
 |     std::atomic<int> callbackCalledTimes = 0; | 
 |     std::atomic<bool> done               = false; | 
 |     for (auto i = 0; i < 10; ++i) { | 
 |       threads.emplace_back(support::make_test_thread([&] { | 
 |         std::stop_callback sc{ss.get_token(), [&] { callbackCalledTimes.fetch_add(1, std::memory_order_relaxed); }}; | 
 |         done.wait(false); | 
 |       })); | 
 |     } | 
 |     using namespace std::chrono_literals; | 
 |     std::this_thread::sleep_for(1ms); | 
 |     ss.request_stop(); | 
 |     done = true; | 
 |     done.notify_all(); | 
 |     for (auto& thread : threads) { | 
 |       thread.join(); | 
 |     } | 
 |     assert(callbackCalledTimes.load(std::memory_order_relaxed) == 10); | 
 |   } | 
 |  | 
 |   // correct overload | 
 |   { | 
 |     struct CBWithTracking { | 
 |       bool& lvalueCalled; | 
 |       bool& lvalueConstCalled; | 
 |       bool& rvalueCalled; | 
 |       bool& rvalueConstCalled; | 
 |  | 
 |       void operator()() & { lvalueCalled = true; } | 
 |       void operator()() const& { lvalueConstCalled = true; } | 
 |       void operator()() && { rvalueCalled = true; } | 
 |       void operator()() const&& { rvalueConstCalled = true; } | 
 |     }; | 
 |  | 
 |     // RValue | 
 |     { | 
 |       bool lvalueCalled      = false; | 
 |       bool lvalueConstCalled = false; | 
 |       bool rvalueCalled      = false; | 
 |       bool rvalueConstCalled = false; | 
 |       std::stop_source ss; | 
 |       ss.request_stop(); | 
 |  | 
 |       std::stop_callback<CBWithTracking> sc( | 
 |           ss.get_token(), CBWithTracking{lvalueCalled, lvalueConstCalled, rvalueCalled, rvalueConstCalled}); | 
 |       assert(rvalueCalled); | 
 |     } | 
 |  | 
 |     // RValue | 
 |     { | 
 |       bool lvalueCalled      = false; | 
 |       bool lvalueConstCalled = false; | 
 |       bool rvalueCalled      = false; | 
 |       bool rvalueConstCalled = false; | 
 |       std::stop_source ss; | 
 |       ss.request_stop(); | 
 |  | 
 |       std::stop_callback<const CBWithTracking> sc( | 
 |           ss.get_token(), CBWithTracking{lvalueCalled, lvalueConstCalled, rvalueCalled, rvalueConstCalled}); | 
 |       assert(rvalueConstCalled); | 
 |     } | 
 |  | 
 |     // LValue | 
 |     { | 
 |       bool lvalueCalled      = false; | 
 |       bool lvalueConstCalled = false; | 
 |       bool rvalueCalled      = false; | 
 |       bool rvalueConstCalled = false; | 
 |       std::stop_source ss; | 
 |       ss.request_stop(); | 
 |       CBWithTracking cb{lvalueCalled, lvalueConstCalled, rvalueCalled, rvalueConstCalled}; | 
 |       std::stop_callback<CBWithTracking&> sc(ss.get_token(), cb); | 
 |       assert(lvalueCalled); | 
 |     } | 
 |  | 
 |     // const LValue | 
 |     { | 
 |       bool lvalueCalled      = false; | 
 |       bool lvalueConstCalled = false; | 
 |       bool rvalueCalled      = false; | 
 |       bool rvalueConstCalled = false; | 
 |       std::stop_source ss; | 
 |       ss.request_stop(); | 
 |       CBWithTracking cb{lvalueCalled, lvalueConstCalled, rvalueCalled, rvalueConstCalled}; | 
 |       std::stop_callback<const CBWithTracking&> sc(ss.get_token(), cb); | 
 |       assert(lvalueConstCalled); | 
 |     } | 
 |   } | 
 |  | 
 |   return 0; | 
 | } |