xref: /llvm-project/libcxx/test/std/thread/thread.stoptoken/stopcallback/dtor.pass.cpp (revision 121ed5c1985356436d0040dbe81bca26992b1fae)
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