xref: /llvm-project/libcxx/test/std/thread/thread.stoptoken/stopcallback/dtor.pass.cpp (revision 121ed5c1985356436d0040dbe81bca26992b1fae)
1b77e50e6SHui //===----------------------------------------------------------------------===//
2b77e50e6SHui //
3b77e50e6SHui // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4b77e50e6SHui // See https://llvm.org/LICENSE.txt for license information.
5b77e50e6SHui // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6b77e50e6SHui //
7b77e50e6SHui //===----------------------------------------------------------------------===//
8b77e50e6SHui //
9b77e50e6SHui // UNSUPPORTED: no-threads
10b77e50e6SHui // UNSUPPORTED: c++03, c++11, c++14, c++17
11b77e50e6SHui // XFAIL: availability-synchronization_library-missing
12b77e50e6SHui 
13b77e50e6SHui // ~stop_callback();
14b77e50e6SHui 
15b77e50e6SHui #include <atomic>
16b77e50e6SHui #include <cassert>
17b77e50e6SHui #include <chrono>
18b77e50e6SHui #include <functional>
19*09e3a360SLouis Dionne #include <memory>
20b77e50e6SHui #include <optional>
21b77e50e6SHui #include <stop_token>
22b77e50e6SHui #include <type_traits>
23b77e50e6SHui #include <utility>
24b77e50e6SHui #include <vector>
25b77e50e6SHui 
26b77e50e6SHui #include "make_test_thread.h"
27b77e50e6SHui #include "test_macros.h"
28b77e50e6SHui 
29b77e50e6SHui struct CallbackHolder;
30b77e50e6SHui 
31b77e50e6SHui struct DeleteHolder {
32b77e50e6SHui   CallbackHolder& holder_;
33b77e50e6SHui   void operator()() const;
34b77e50e6SHui };
35b77e50e6SHui 
36b77e50e6SHui struct CallbackHolder {
37b77e50e6SHui   std::unique_ptr<std::stop_callback<DeleteHolder>> callback_;
38b77e50e6SHui };
39b77e50e6SHui 
40b77e50e6SHui void DeleteHolder::operator()() const { holder_.callback_.reset(); }
41b77e50e6SHui 
42b77e50e6SHui int main(int, char**) {
43b77e50e6SHui   // Unregisters the callback from the owned stop state, if any
44b77e50e6SHui   {
45b77e50e6SHui     std::stop_source ss;
46b77e50e6SHui     bool called = false;
47b77e50e6SHui 
48b77e50e6SHui     {
49b77e50e6SHui       std::stop_callback sc(ss.get_token(), [&] { called = true; });
50b77e50e6SHui     }
51b77e50e6SHui     ss.request_stop();
52b77e50e6SHui     assert(!called);
53b77e50e6SHui   }
54b77e50e6SHui 
55b77e50e6SHui   // The destructor does not block waiting for the execution of another
56b77e50e6SHui   // callback registered by an associated stop_callback.
57b77e50e6SHui   {
58b77e50e6SHui     std::stop_source ss;
59b77e50e6SHui 
60b77e50e6SHui     std::atomic<int> startedIndex    = 0;
61b77e50e6SHui     std::atomic<bool> callbackFinish = false;
62b77e50e6SHui 
63b77e50e6SHui     std::optional<std::stop_callback<std::function<void()>>> sc1(std::in_place, ss.get_token(), [&] {
64b77e50e6SHui       startedIndex = 1;
65b77e50e6SHui       startedIndex.notify_all();
66b77e50e6SHui       callbackFinish.wait(false);
67b77e50e6SHui     });
68b77e50e6SHui 
69b77e50e6SHui     std::optional<std::stop_callback<std::function<void()>>> sc2(std::in_place, ss.get_token(), [&] {
70b77e50e6SHui       startedIndex = 2;
71b77e50e6SHui       startedIndex.notify_all();
72b77e50e6SHui       callbackFinish.wait(false);
73b77e50e6SHui     });
74b77e50e6SHui 
75b77e50e6SHui     auto thread = support::make_test_thread([&] { ss.request_stop(); });
76b77e50e6SHui 
77b77e50e6SHui     startedIndex.wait(0);
78b77e50e6SHui 
79b77e50e6SHui     // now one of the callback has started but not finished.
80b77e50e6SHui     if (startedIndex == 1) {
81b77e50e6SHui       sc2.reset();   // destructor should not block
82b77e50e6SHui     } else if (startedIndex == 2) {
83b77e50e6SHui       sc1.reset();   // destructor should not block
84b77e50e6SHui     } else {
85b77e50e6SHui       assert(false); // something is wrong
86b77e50e6SHui     }
87b77e50e6SHui 
88b77e50e6SHui     callbackFinish = true;
89b77e50e6SHui     callbackFinish.notify_all();
90b77e50e6SHui     thread.join();
91b77e50e6SHui   }
92b77e50e6SHui 
93b77e50e6SHui   // If callback is concurrently executing on another thread, then the
94b77e50e6SHui   // return from the invocation of callback strongly happens before ([intro.races])
95b77e50e6SHui   // callback is destroyed.
96b77e50e6SHui   {
97b77e50e6SHui     struct Callback {
98b77e50e6SHui       std::atomic<bool>& started_;
99b77e50e6SHui       std::atomic<bool>& waitDone_;
100b77e50e6SHui       std::atomic<bool>& finished_;
101b77e50e6SHui       bool moved = false;
102b77e50e6SHui 
103b77e50e6SHui       Callback(std::atomic<bool>& started, std::atomic<bool>& waitDone, std::atomic<bool>& finished)
104b77e50e6SHui           : started_(started), waitDone_(waitDone), finished_(finished) {}
105b77e50e6SHui       Callback(Callback&& other) : started_(other.started_), waitDone_(other.waitDone_), finished_(other.finished_) {
106b77e50e6SHui         other.moved = true;
107b77e50e6SHui       }
108b77e50e6SHui 
109b77e50e6SHui       void operator()() const {
110b77e50e6SHui         struct ScopedGuard {
111b77e50e6SHui           std::atomic<bool>& g_finished_;
112b77e50e6SHui           ~ScopedGuard() { g_finished_.store(true, std::memory_order_relaxed); }
113b77e50e6SHui         };
114b77e50e6SHui 
115b77e50e6SHui         started_ = true;
116b77e50e6SHui         started_.notify_all();
117b77e50e6SHui         waitDone_.wait(false);
118b77e50e6SHui         ScopedGuard g{finished_};
119b77e50e6SHui       }
120b77e50e6SHui 
121b77e50e6SHui       ~Callback() {
122b77e50e6SHui         if (!moved) {
123b77e50e6SHui           // destructor has to be called after operator() returns
124b77e50e6SHui           assert(finished_.load(std::memory_order_relaxed));
125b77e50e6SHui         }
126b77e50e6SHui       }
127b77e50e6SHui     };
128b77e50e6SHui 
129b77e50e6SHui     std::stop_source ss;
130b77e50e6SHui 
131b77e50e6SHui     std::atomic<bool> started  = false;
132b77e50e6SHui     std::atomic<bool> waitDone = false;
133b77e50e6SHui     std::atomic<bool> finished = false;
134b77e50e6SHui 
135b77e50e6SHui     std::optional<std::stop_callback<Callback>> sc{
136b77e50e6SHui         std::in_place, ss.get_token(), Callback{started, waitDone, finished}};
137b77e50e6SHui 
138b77e50e6SHui     auto thread1 = support::make_test_thread([&] { ss.request_stop(); });
139b77e50e6SHui     started.wait(false);
140b77e50e6SHui 
141b77e50e6SHui     auto thread2 = support::make_test_thread([&] {
142b77e50e6SHui       using namespace std::chrono_literals;
143b77e50e6SHui       std::this_thread::sleep_for(1ms);
144b77e50e6SHui       waitDone = true;
145b77e50e6SHui       waitDone.notify_all();
146b77e50e6SHui     });
147b77e50e6SHui 
148b77e50e6SHui     sc.reset(); // destructor should block until operator() returns, i.e. waitDone to be true
149b77e50e6SHui 
150b77e50e6SHui     thread1.join();
151b77e50e6SHui     thread2.join();
152b77e50e6SHui   }
153b77e50e6SHui 
154b77e50e6SHui   // If callback is executing on the current thread, then the destructor does not block ([defns.block])
155b77e50e6SHui   // waiting for the return from the invocation of callback.
156b77e50e6SHui   {
157b77e50e6SHui     std::stop_source ss;
158b77e50e6SHui 
159b77e50e6SHui     CallbackHolder holder;
160b77e50e6SHui     holder.callback_ = std::make_unique<std::stop_callback<DeleteHolder>>(ss.get_token(), DeleteHolder{holder});
161b77e50e6SHui 
162b77e50e6SHui     assert(holder.callback_ != nullptr);
163b77e50e6SHui 
164b77e50e6SHui     ss.request_stop(); // the callbacks deletes itself. if the destructor blocks, it would be deadlock
165b77e50e6SHui     assert(holder.callback_ == nullptr);
166b77e50e6SHui   }
1673350ec9bSLouis Dionne 
1683350ec9bSLouis Dionne   return 0;
169b77e50e6SHui }
170