| //===----------------------------------------------------------------------===// |
| // |
| // 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 <atomic> |
| #include <cassert> |
| #include <chrono> |
| #include <functional> |
| #include <optional> |
| #include <stop_token> |
| #include <type_traits> |
| #include <utility> |
| #include <vector> |
| |
| #include "make_test_thread.h" |
| #include "test_macros.h" |
| |
| struct CallbackHolder; |
| |
| struct DeleteHolder { |
| CallbackHolder& holder_; |
| void operator()() const; |
| }; |
| |
| struct CallbackHolder { |
| std::unique_ptr<std::stop_callback<DeleteHolder>> 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<int> startedIndex = 0; |
| std::atomic<bool> callbackFinish = false; |
| |
| std::optional<std::stop_callback<std::function<void()>>> sc1(std::in_place, ss.get_token(), [&] { |
| startedIndex = 1; |
| startedIndex.notify_all(); |
| callbackFinish.wait(false); |
| }); |
| |
| std::optional<std::stop_callback<std::function<void()>>> 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<bool>& started_; |
| std::atomic<bool>& waitDone_; |
| std::atomic<bool>& finished_; |
| bool moved = false; |
| |
| Callback(std::atomic<bool>& started, std::atomic<bool>& waitDone, std::atomic<bool>& 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<bool>& 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<bool> started = false; |
| std::atomic<bool> waitDone = false; |
| std::atomic<bool> finished = false; |
| |
| std::optional<std::stop_callback<Callback>> 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<std::stop_callback<DeleteHolder>>(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); |
| } |
| } |