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