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