xref: /llvm-project/libcxx/test/std/thread/thread.stoptoken/stopcallback/dtor.pass.cpp (revision b77e50e6aef5650d1ce0ce7ad1a41049395b9426)
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 <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 
28 struct CallbackHolder;
29 
30 struct DeleteHolder {
31   CallbackHolder& holder_;
32   void operator()() const;
33 };
34 
35 struct CallbackHolder {
36   std::unique_ptr<std::stop_callback<DeleteHolder>> callback_;
37 };
38 
39 void DeleteHolder::operator()() const { holder_.callback_.reset(); }
40 
41 int main(int, char**) {
42   // Unregisters the callback from the owned stop state, if any
43   {
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 another
55   // 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 block
81     } else if (startedIndex == 2) {
82       sc1.reset();   // destructor should not block
83     } else {
84       assert(false); // something is wrong
85     }
86 
87     callbackFinish = true;
88     callbackFinish.notify_all();
89     thread.join();
90   }
91 
92   // If callback is concurrently executing on another thread, then the
93   // 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() returns
123           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 true
148 
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 deadlock
164     assert(holder.callback_ == nullptr);
165   }
166 }
167