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