//===----------------------------------------------------------------------===// // // 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 // ~stop_callback(); #include #include #include #include #include #include #include #include #include #include #include "make_test_thread.h" #include "test_macros.h" struct CallbackHolder; struct DeleteHolder { CallbackHolder& holder_; void operator()() const; }; struct CallbackHolder { std::unique_ptr> callback_; }; void DeleteHolder::operator()() const { holder_.callback_.reset(); } int main(int, char**) { // Unregisters the callback from the owned stop state, if any { std::stop_source ss; bool called = false; { std::stop_callback sc(ss.get_token(), [&] { called = true; }); } ss.request_stop(); assert(!called); } // The destructor does not block waiting for the execution of another // callback registered by an associated stop_callback. { std::stop_source ss; std::atomic startedIndex = 0; std::atomic callbackFinish = false; std::optional>> sc1(std::in_place, ss.get_token(), [&] { startedIndex = 1; startedIndex.notify_all(); callbackFinish.wait(false); }); std::optional>> sc2(std::in_place, ss.get_token(), [&] { startedIndex = 2; startedIndex.notify_all(); callbackFinish.wait(false); }); auto thread = support::make_test_thread([&] { ss.request_stop(); }); startedIndex.wait(0); // now one of the callback has started but not finished. if (startedIndex == 1) { sc2.reset(); // destructor should not block } else if (startedIndex == 2) { sc1.reset(); // destructor should not block } else { assert(false); // something is wrong } callbackFinish = true; callbackFinish.notify_all(); thread.join(); } // If callback is concurrently executing on another thread, then the // return from the invocation of callback strongly happens before ([intro.races]) // callback is destroyed. { struct Callback { std::atomic& started_; std::atomic& waitDone_; std::atomic& finished_; bool moved = false; Callback(std::atomic& started, std::atomic& waitDone, std::atomic& finished) : started_(started), waitDone_(waitDone), finished_(finished) {} Callback(Callback&& other) : started_(other.started_), waitDone_(other.waitDone_), finished_(other.finished_) { other.moved = true; } void operator()() const { struct ScopedGuard { std::atomic& g_finished_; ~ScopedGuard() { g_finished_.store(true, std::memory_order_relaxed); } }; started_ = true; started_.notify_all(); waitDone_.wait(false); ScopedGuard g{finished_}; } ~Callback() { if (!moved) { // destructor has to be called after operator() returns assert(finished_.load(std::memory_order_relaxed)); } } }; std::stop_source ss; std::atomic started = false; std::atomic waitDone = false; std::atomic finished = false; std::optional> sc{ std::in_place, ss.get_token(), Callback{started, waitDone, finished}}; auto thread1 = support::make_test_thread([&] { ss.request_stop(); }); started.wait(false); auto thread2 = support::make_test_thread([&] { using namespace std::chrono_literals; std::this_thread::sleep_for(1ms); waitDone = true; waitDone.notify_all(); }); sc.reset(); // destructor should block until operator() returns, i.e. waitDone to be true thread1.join(); thread2.join(); } // If callback is executing on the current thread, then the destructor does not block ([defns.block]) // waiting for the return from the invocation of callback. { std::stop_source ss; CallbackHolder holder; holder.callback_ = std::make_unique>(ss.get_token(), DeleteHolder{holder}); assert(holder.callback_ != nullptr); ss.request_stop(); // the callbacks deletes itself. if the destructor blocks, it would be deadlock assert(holder.callback_ == nullptr); } return 0; }