169 lines · cpp
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-exception6//7//===----------------------------------------------------------------------===//8//9// UNSUPPORTED: no-threads10// UNSUPPORTED: c++03, c++11, c++14, c++1711 12// ~stop_callback();13 14#include <atomic>15#include <cassert>16#include <chrono>17#include <functional>18#include <memory>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 28struct CallbackHolder;29 30struct DeleteHolder {31 CallbackHolder& holder_;32 void operator()() const;33};34 35struct CallbackHolder {36 std::unique_ptr<std::stop_callback<DeleteHolder>> callback_;37};38 39void DeleteHolder::operator()() const { holder_.callback_.reset(); }40 41int main(int, char**) {42 // Unregisters the callback from the owned stop state, if any43 {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 another55 // 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 block81 } else if (startedIndex == 2) {82 sc1.reset(); // destructor should not block83 } else {84 assert(false); // something is wrong85 }86 87 callbackFinish = true;88 callbackFinish.notify_all();89 thread.join();90 }91 92 // If callback is concurrently executing on another thread, then the93 // 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() returns123 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 true148 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 deadlock164 assert(holder.callback_ == nullptr);165 }166 167 return 0;168}169