1b77e50e6SHui //===----------------------------------------------------------------------===// 2b77e50e6SHui // 3b77e50e6SHui // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. 4b77e50e6SHui // See https://llvm.org/LICENSE.txt for license information. 5b77e50e6SHui // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception 6b77e50e6SHui // 7b77e50e6SHui //===----------------------------------------------------------------------===// 8b77e50e6SHui // 9b77e50e6SHui // UNSUPPORTED: no-threads 10b77e50e6SHui // UNSUPPORTED: c++03, c++11, c++14, c++17 11b77e50e6SHui // XFAIL: availability-synchronization_library-missing 12b77e50e6SHui 13b77e50e6SHui // ~stop_callback(); 14b77e50e6SHui 15b77e50e6SHui #include <atomic> 16b77e50e6SHui #include <cassert> 17b77e50e6SHui #include <chrono> 18b77e50e6SHui #include <functional> 19*09e3a360SLouis Dionne #include <memory> 20b77e50e6SHui #include <optional> 21b77e50e6SHui #include <stop_token> 22b77e50e6SHui #include <type_traits> 23b77e50e6SHui #include <utility> 24b77e50e6SHui #include <vector> 25b77e50e6SHui 26b77e50e6SHui #include "make_test_thread.h" 27b77e50e6SHui #include "test_macros.h" 28b77e50e6SHui 29b77e50e6SHui struct CallbackHolder; 30b77e50e6SHui 31b77e50e6SHui struct DeleteHolder { 32b77e50e6SHui CallbackHolder& holder_; 33b77e50e6SHui void operator()() const; 34b77e50e6SHui }; 35b77e50e6SHui 36b77e50e6SHui struct CallbackHolder { 37b77e50e6SHui std::unique_ptr<std::stop_callback<DeleteHolder>> callback_; 38b77e50e6SHui }; 39b77e50e6SHui 40b77e50e6SHui void DeleteHolder::operator()() const { holder_.callback_.reset(); } 41b77e50e6SHui 42b77e50e6SHui int main(int, char**) { 43b77e50e6SHui // Unregisters the callback from the owned stop state, if any 44b77e50e6SHui { 45b77e50e6SHui std::stop_source ss; 46b77e50e6SHui bool called = false; 47b77e50e6SHui 48b77e50e6SHui { 49b77e50e6SHui std::stop_callback sc(ss.get_token(), [&] { called = true; }); 50b77e50e6SHui } 51b77e50e6SHui ss.request_stop(); 52b77e50e6SHui assert(!called); 53b77e50e6SHui } 54b77e50e6SHui 55b77e50e6SHui // The destructor does not block waiting for the execution of another 56b77e50e6SHui // callback registered by an associated stop_callback. 57b77e50e6SHui { 58b77e50e6SHui std::stop_source ss; 59b77e50e6SHui 60b77e50e6SHui std::atomic<int> startedIndex = 0; 61b77e50e6SHui std::atomic<bool> callbackFinish = false; 62b77e50e6SHui 63b77e50e6SHui std::optional<std::stop_callback<std::function<void()>>> sc1(std::in_place, ss.get_token(), [&] { 64b77e50e6SHui startedIndex = 1; 65b77e50e6SHui startedIndex.notify_all(); 66b77e50e6SHui callbackFinish.wait(false); 67b77e50e6SHui }); 68b77e50e6SHui 69b77e50e6SHui std::optional<std::stop_callback<std::function<void()>>> sc2(std::in_place, ss.get_token(), [&] { 70b77e50e6SHui startedIndex = 2; 71b77e50e6SHui startedIndex.notify_all(); 72b77e50e6SHui callbackFinish.wait(false); 73b77e50e6SHui }); 74b77e50e6SHui 75b77e50e6SHui auto thread = support::make_test_thread([&] { ss.request_stop(); }); 76b77e50e6SHui 77b77e50e6SHui startedIndex.wait(0); 78b77e50e6SHui 79b77e50e6SHui // now one of the callback has started but not finished. 80b77e50e6SHui if (startedIndex == 1) { 81b77e50e6SHui sc2.reset(); // destructor should not block 82b77e50e6SHui } else if (startedIndex == 2) { 83b77e50e6SHui sc1.reset(); // destructor should not block 84b77e50e6SHui } else { 85b77e50e6SHui assert(false); // something is wrong 86b77e50e6SHui } 87b77e50e6SHui 88b77e50e6SHui callbackFinish = true; 89b77e50e6SHui callbackFinish.notify_all(); 90b77e50e6SHui thread.join(); 91b77e50e6SHui } 92b77e50e6SHui 93b77e50e6SHui // If callback is concurrently executing on another thread, then the 94b77e50e6SHui // return from the invocation of callback strongly happens before ([intro.races]) 95b77e50e6SHui // callback is destroyed. 96b77e50e6SHui { 97b77e50e6SHui struct Callback { 98b77e50e6SHui std::atomic<bool>& started_; 99b77e50e6SHui std::atomic<bool>& waitDone_; 100b77e50e6SHui std::atomic<bool>& finished_; 101b77e50e6SHui bool moved = false; 102b77e50e6SHui 103b77e50e6SHui Callback(std::atomic<bool>& started, std::atomic<bool>& waitDone, std::atomic<bool>& finished) 104b77e50e6SHui : started_(started), waitDone_(waitDone), finished_(finished) {} 105b77e50e6SHui Callback(Callback&& other) : started_(other.started_), waitDone_(other.waitDone_), finished_(other.finished_) { 106b77e50e6SHui other.moved = true; 107b77e50e6SHui } 108b77e50e6SHui 109b77e50e6SHui void operator()() const { 110b77e50e6SHui struct ScopedGuard { 111b77e50e6SHui std::atomic<bool>& g_finished_; 112b77e50e6SHui ~ScopedGuard() { g_finished_.store(true, std::memory_order_relaxed); } 113b77e50e6SHui }; 114b77e50e6SHui 115b77e50e6SHui started_ = true; 116b77e50e6SHui started_.notify_all(); 117b77e50e6SHui waitDone_.wait(false); 118b77e50e6SHui ScopedGuard g{finished_}; 119b77e50e6SHui } 120b77e50e6SHui 121b77e50e6SHui ~Callback() { 122b77e50e6SHui if (!moved) { 123b77e50e6SHui // destructor has to be called after operator() returns 124b77e50e6SHui assert(finished_.load(std::memory_order_relaxed)); 125b77e50e6SHui } 126b77e50e6SHui } 127b77e50e6SHui }; 128b77e50e6SHui 129b77e50e6SHui std::stop_source ss; 130b77e50e6SHui 131b77e50e6SHui std::atomic<bool> started = false; 132b77e50e6SHui std::atomic<bool> waitDone = false; 133b77e50e6SHui std::atomic<bool> finished = false; 134b77e50e6SHui 135b77e50e6SHui std::optional<std::stop_callback<Callback>> sc{ 136b77e50e6SHui std::in_place, ss.get_token(), Callback{started, waitDone, finished}}; 137b77e50e6SHui 138b77e50e6SHui auto thread1 = support::make_test_thread([&] { ss.request_stop(); }); 139b77e50e6SHui started.wait(false); 140b77e50e6SHui 141b77e50e6SHui auto thread2 = support::make_test_thread([&] { 142b77e50e6SHui using namespace std::chrono_literals; 143b77e50e6SHui std::this_thread::sleep_for(1ms); 144b77e50e6SHui waitDone = true; 145b77e50e6SHui waitDone.notify_all(); 146b77e50e6SHui }); 147b77e50e6SHui 148b77e50e6SHui sc.reset(); // destructor should block until operator() returns, i.e. waitDone to be true 149b77e50e6SHui 150b77e50e6SHui thread1.join(); 151b77e50e6SHui thread2.join(); 152b77e50e6SHui } 153b77e50e6SHui 154b77e50e6SHui // If callback is executing on the current thread, then the destructor does not block ([defns.block]) 155b77e50e6SHui // waiting for the return from the invocation of callback. 156b77e50e6SHui { 157b77e50e6SHui std::stop_source ss; 158b77e50e6SHui 159b77e50e6SHui CallbackHolder holder; 160b77e50e6SHui holder.callback_ = std::make_unique<std::stop_callback<DeleteHolder>>(ss.get_token(), DeleteHolder{holder}); 161b77e50e6SHui 162b77e50e6SHui assert(holder.callback_ != nullptr); 163b77e50e6SHui 164b77e50e6SHui ss.request_stop(); // the callbacks deletes itself. if the destructor blocks, it would be deadlock 165b77e50e6SHui assert(holder.callback_ == nullptr); 166b77e50e6SHui } 1673350ec9bSLouis Dionne 1683350ec9bSLouis Dionne return 0; 169b77e50e6SHui } 170