polyfill_thread.h
1 // Copyright 2022 yuzu Emulator Project 2 // Licensed under GPLv2 or any later version 3 // Refer to the license.txt file included. 4 5 // 6 // TODO: remove this file when jthread is supported by all compilation targets 7 // 8 9 #pragma once 10 11 #include <version> 12 13 #ifdef __cpp_lib_jthread 14 15 #include <stop_token> 16 #include <thread> 17 18 namespace Common { 19 20 template <typename Condvar, typename Lock, typename Pred> 21 void CondvarWait(Condvar& cv, Lock& lock, std::stop_token token, Pred&& pred) { 22 cv.wait(lock, token, std::move(pred)); 23 } 24 25 } // namespace Common 26 27 #else 28 29 #include <atomic> 30 #include <functional> 31 #include <map> 32 #include <memory> 33 #include <mutex> 34 #include <optional> 35 #include <thread> 36 #include <type_traits> 37 #include <utility> 38 39 namespace std { 40 namespace polyfill { 41 42 using stop_state_callback = std::size_t; 43 44 class stop_state { 45 public: 46 stop_state() = default; 47 ~stop_state() = default; 48 49 bool request_stop() { 50 unique_lock lk{m_lock}; 51 52 if (m_stop_requested) { 53 // Already set, nothing to do. 54 return false; 55 } 56 57 // Mark stop requested. 58 m_stop_requested = true; 59 60 while (!m_callbacks.empty()) { 61 // Get an iterator to the first element. 62 const auto it = m_callbacks.begin(); 63 64 // Move the callback function out of the map. 65 function<void()> f; 66 swap(it->second, f); 67 68 // Erase the now-empty map element. 69 m_callbacks.erase(it); 70 71 // Run the callback. 72 if (f) { 73 f(); 74 } 75 } 76 77 return true; 78 } 79 80 bool stop_requested() const { 81 unique_lock lk{m_lock}; 82 return m_stop_requested; 83 } 84 85 stop_state_callback insert_callback(function<void()> f) { 86 unique_lock lk{m_lock}; 87 88 if (m_stop_requested) { 89 // Stop already requested. Don't insert anything, 90 // just run the callback synchronously. 91 if (f) { 92 f(); 93 } 94 return 0; 95 } 96 97 // Insert the callback. 98 stop_state_callback ret = ++m_next_callback; 99 m_callbacks.emplace(ret, std::move(f)); 100 return ret; 101 } 102 103 void remove_callback(stop_state_callback cb) { 104 unique_lock lk{m_lock}; 105 m_callbacks.erase(cb); 106 } 107 108 private: 109 mutable recursive_mutex m_lock; 110 map<stop_state_callback, function<void()>> m_callbacks; 111 stop_state_callback m_next_callback{0}; 112 bool m_stop_requested{false}; 113 }; 114 115 } // namespace polyfill 116 117 #ifndef __cpp_lib_concepts 118 template <class T, class... Args> 119 concept constructible_from = is_nothrow_destructible_v<T> && is_constructible_v<T, Args...>; 120 #endif 121 122 class stop_token; 123 class stop_source; 124 struct nostopstate_t { 125 explicit nostopstate_t() = default; 126 }; 127 inline constexpr nostopstate_t nostopstate{}; 128 129 template <class Callback> 130 class stop_callback; 131 132 class stop_token { 133 public: 134 stop_token() noexcept = default; 135 136 stop_token(const stop_token&) noexcept = default; 137 stop_token(stop_token&&) noexcept = default; 138 stop_token& operator=(const stop_token&) noexcept = default; 139 stop_token& operator=(stop_token&&) noexcept = default; 140 ~stop_token() = default; 141 142 void swap(stop_token& other) noexcept { 143 m_stop_state.swap(other.m_stop_state); 144 } 145 146 [[nodiscard]] bool stop_requested() const noexcept { 147 return m_stop_state && m_stop_state->stop_requested(); 148 } 149 [[nodiscard]] bool stop_possible() const noexcept { 150 return m_stop_state != nullptr; 151 } 152 153 private: 154 friend class stop_source; 155 template <typename Callback> 156 friend class stop_callback; 157 stop_token(shared_ptr<polyfill::stop_state> stop_state) : m_stop_state(std::move(stop_state)) {} 158 159 private: 160 shared_ptr<polyfill::stop_state> m_stop_state; 161 }; 162 163 class stop_source { 164 public: 165 stop_source() : m_stop_state(make_shared<polyfill::stop_state>()) {} 166 explicit stop_source(nostopstate_t) noexcept {} 167 168 stop_source(const stop_source&) noexcept = default; 169 stop_source(stop_source&&) noexcept = default; 170 stop_source& operator=(const stop_source&) noexcept = default; 171 stop_source& operator=(stop_source&&) noexcept = default; 172 ~stop_source() = default; 173 void swap(stop_source& other) noexcept { 174 m_stop_state.swap(other.m_stop_state); 175 } 176 177 [[nodiscard]] stop_token get_token() const noexcept { 178 return stop_token(m_stop_state); 179 } 180 [[nodiscard]] bool stop_possible() const noexcept { 181 return m_stop_state != nullptr; 182 } 183 [[nodiscard]] bool stop_requested() const noexcept { 184 return m_stop_state && m_stop_state->stop_requested(); 185 } 186 bool request_stop() noexcept { 187 return m_stop_state && m_stop_state->request_stop(); 188 } 189 190 private: 191 friend class jthread; 192 explicit stop_source(shared_ptr<polyfill::stop_state> stop_state) 193 : m_stop_state(std::move(stop_state)) {} 194 195 private: 196 shared_ptr<polyfill::stop_state> m_stop_state; 197 }; 198 199 template <typename Callback> 200 class stop_callback { 201 static_assert(is_nothrow_destructible_v<Callback>); 202 static_assert(is_invocable_v<Callback>); 203 204 public: 205 using callback_type = Callback; 206 207 template <typename C> 208 requires constructible_from<Callback, C> 209 explicit stop_callback(const stop_token& st, 210 C&& cb) noexcept(is_nothrow_constructible_v<Callback, C>) 211 : m_stop_state(st.m_stop_state) { 212 if (m_stop_state) { 213 m_callback = m_stop_state->insert_callback(std::move(cb)); 214 } 215 } 216 template <typename C> 217 requires constructible_from<Callback, C> 218 explicit stop_callback(stop_token&& st, 219 C&& cb) noexcept(is_nothrow_constructible_v<Callback, C>) 220 : m_stop_state(std::move(st.m_stop_state)) { 221 if (m_stop_state) { 222 m_callback = m_stop_state->insert_callback(std::move(cb)); 223 } 224 } 225 ~stop_callback() { 226 if (m_stop_state && m_callback) { 227 m_stop_state->remove_callback(m_callback); 228 } 229 } 230 231 stop_callback(const stop_callback&) = delete; 232 stop_callback(stop_callback&&) = delete; 233 stop_callback& operator=(const stop_callback&) = delete; 234 stop_callback& operator=(stop_callback&&) = delete; 235 236 private: 237 shared_ptr<polyfill::stop_state> m_stop_state; 238 polyfill::stop_state_callback m_callback; 239 }; 240 241 template <typename Callback> 242 stop_callback(stop_token, Callback) -> stop_callback<Callback>; 243 244 class jthread { 245 public: 246 using id = thread::id; 247 using native_handle_type = thread::native_handle_type; 248 249 jthread() noexcept = default; 250 251 template <typename F, typename... Args, 252 typename = enable_if_t<!is_same_v<remove_cvref_t<F>, jthread>>> 253 explicit jthread(F&& f, Args&&... args) 254 : m_stop_state(make_shared<polyfill::stop_state>()), 255 m_thread(make_thread(std::move(f), std::move(args)...)) {} 256 257 ~jthread() { 258 if (joinable()) { 259 request_stop(); 260 join(); 261 } 262 } 263 264 jthread(const jthread&) = delete; 265 jthread(jthread&&) noexcept = default; 266 jthread& operator=(const jthread&) = delete; 267 268 jthread& operator=(jthread&& other) noexcept { 269 m_thread.swap(other.m_thread); 270 m_stop_state.swap(other.m_stop_state); 271 return *this; 272 } 273 274 void swap(jthread& other) noexcept { 275 m_thread.swap(other.m_thread); 276 m_stop_state.swap(other.m_stop_state); 277 } 278 [[nodiscard]] bool joinable() const noexcept { 279 return m_thread.joinable(); 280 } 281 void join() { 282 m_thread.join(); 283 } 284 void detach() { 285 m_thread.detach(); 286 m_stop_state.reset(); 287 } 288 289 [[nodiscard]] id get_id() const noexcept { 290 return m_thread.get_id(); 291 } 292 [[nodiscard]] native_handle_type native_handle() { 293 return m_thread.native_handle(); 294 } 295 [[nodiscard]] stop_source get_stop_source() noexcept { 296 return stop_source(m_stop_state); 297 } 298 [[nodiscard]] stop_token get_stop_token() const noexcept { 299 return stop_source(m_stop_state).get_token(); 300 } 301 bool request_stop() noexcept { 302 return get_stop_source().request_stop(); 303 } 304 [[nodiscard]] static unsigned int hardware_concurrency() noexcept { 305 return thread::hardware_concurrency(); 306 } 307 308 private: 309 template <typename F, typename... Args> 310 thread make_thread(F&& f, Args&&... args) { 311 if constexpr (is_invocable_v<decay_t<F>, stop_token, decay_t<Args>...>) { 312 return thread(std::move(f), get_stop_token(), std::move(args)...); 313 } else { 314 return thread(std::move(f), std::move(args)...); 315 } 316 } 317 318 shared_ptr<polyfill::stop_state> m_stop_state; 319 thread m_thread; 320 }; 321 322 } // namespace std 323 324 namespace Common { 325 326 template <typename Condvar, typename Lock, typename Pred> 327 void CondvarWait(Condvar& cv, Lock& lock, std::stop_token token, Pred pred) { 328 if (token.stop_requested()) { 329 return; 330 } 331 332 std::stop_callback callback(token, [&] { cv.notify_all(); }); 333 cv.wait(lock, [&] { return pred() || token.stop_requested(); }); 334 } 335 336 } // namespace Common 337 338 #endif // __cpp_lib_jthread