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