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 <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
29struct CallbackHolder;
30
31struct DeleteHolder {
32 CallbackHolder& holder_;
33 void operator()() const;
34};
35
36struct CallbackHolder {
37 std::unique_ptr<std::stop_callback<DeleteHolder>> callback_;
38};
39
40void DeleteHolder::operator()() const { holder_.callback_.reset(); }
41
42int 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(i: true, m: std::memory_order_relaxed); }
113 };
114
115 started_ = true;
116 started_.notify_all();
117 waitDone_.wait(false);
118 ScopedGuard g{.g_finished_: 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

source code of libcxx/test/std/thread/thread.stoptoken/stopcallback/dtor.pass.cpp