/ src / common / polyfill_thread.h
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