1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169
|
//===----------------------------------------------------------------------===//
//
// 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: libcpp-has-no-experimental-stop_token
// 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);
}
return 0;
}
|