From 5c8ed491ab6cb4c74b16d2551353429ac2c80a36 Mon Sep 17 00:00:00 2001 From: Philip Rebohle Date: Tue, 25 Apr 2023 11:49:17 +0200 Subject: [PATCH] [dxvk] Rewrite thread wrapper Addresses some issues raised in #3378. --- src/util/thread.cpp | 76 ++++++++++++++++++++++- src/util/thread.h | 145 ++++++++++++++++++-------------------------- 2 files changed, 132 insertions(+), 89 deletions(-) diff --git a/src/util/thread.cpp b/src/util/thread.cpp index 044ae696..5c912412 100644 --- a/src/util/thread.cpp +++ b/src/util/thread.cpp @@ -1,10 +1,82 @@ +#include + #include "thread.h" #include "util_likely.h" -#include - #ifdef _WIN32 +namespace dxvk { + + thread::thread(ThreadProc&& proc) + : m_data(new ThreadData(std::move(proc))) { + m_data->handle = ::CreateThread(nullptr, 0x100000, + thread::threadProc, m_data, STACK_SIZE_PARAM_IS_A_RESERVATION, + &m_data->id); + + if (!m_data->handle) { + delete m_data; + throw std::system_error(std::make_error_code(std::errc::resource_unavailable_try_again), "Failed to create thread"); + } + } + + + thread::~thread() { + if (joinable()) + std::terminate(); + } + + + void thread::join() { + if (!joinable()) + throw std::system_error(std::make_error_code(std::errc::invalid_argument), "Thread not joinable"); + + if (get_id() == this_thread::get_id()) + throw std::system_error(std::make_error_code(std::errc::resource_deadlock_would_occur), "Cannot join current thread"); + + if(::WaitForSingleObjectEx(m_data->handle, INFINITE, FALSE) == WAIT_FAILED) + throw std::system_error(std::make_error_code(std::errc::invalid_argument), "Joining thread failed"); + + detach(); + } + + + void thread::set_priority(ThreadPriority priority) { + int32_t value; + switch (priority) { + default: + case ThreadPriority::Normal: value = THREAD_PRIORITY_NORMAL; break; + case ThreadPriority::Lowest: value = THREAD_PRIORITY_LOWEST; break; + } + + if (m_data) + ::SetThreadPriority(m_data->handle, int32_t(value)); + } + + + uint32_t thread::hardware_concurrency() { + SYSTEM_INFO info = { }; + ::GetSystemInfo(&info); + return info.dwNumberOfProcessors; + } + + + DWORD WINAPI thread::threadProc(void* arg) { + auto data = reinterpret_cast(arg); + DWORD exitCode = 0; + + try { + data->proc(); + } catch (...) { + exitCode = 1; + } + + data->decRef(); + return exitCode; + } + +} + + namespace dxvk::this_thread { bool isInModuleDetachment() { diff --git a/src/util/thread.h b/src/util/thread.h index a0348e68..6e25f407 100644 --- a/src/util/thread.h +++ b/src/util/thread.h @@ -5,6 +5,7 @@ #include #include #include +#include #include "util_error.h" @@ -24,126 +25,96 @@ namespace dxvk { }; #ifdef _WIN32 + + using ThreadProc = std::function; + + /** - * \brief Thread helper class - * - * This is needed mostly for winelib builds. Wine needs to setup each thread that - * calls Windows APIs. It means that in winelib builds, we can't let standard C++ - * library create threads and need to use Wine for that instead. We use a thin wrapper - * around Windows thread functions so that the rest of code just has to use - * dxvk::thread class instead of std::thread. + * \brief Thread object */ - class ThreadFn : public RcObject { - using Proc = std::function; - public: + struct ThreadData { + ThreadData(ThreadProc&& proc_) + : proc(std::move(proc_)) { } - ThreadFn(Proc&& proc) - : m_proc(std::move(proc)) { - // Reference for the thread function - this->incRef(); - - m_handle = ::CreateThread(nullptr, 0x100000, - ThreadFn::threadProc, this, STACK_SIZE_PARAM_IS_A_RESERVATION, - nullptr); - - if (m_handle == nullptr) - throw DxvkError("Failed to create thread"); + ~ThreadData() { + if (handle) + CloseHandle(handle); } - ~ThreadFn() { - if (this->joinable()) - std::terminate(); + HANDLE handle = nullptr; + DWORD id = 0; + std::atomic refs = { 2u }; + ThreadProc proc; + + void decRef() { + if (refs.fetch_sub(1, std::memory_order_release) == 1) + delete this; } - - void detach() { - ::CloseHandle(m_handle); - m_handle = nullptr; - } - - void join() { - if(::WaitForSingleObjectEx(m_handle, INFINITE, FALSE) == WAIT_FAILED) - throw DxvkError("Failed to join thread"); - this->detach(); - } - - bool joinable() const { - return m_handle != nullptr; - } - - void set_priority(ThreadPriority priority) { - int32_t value; - switch (priority) { - default: - case ThreadPriority::Normal: value = THREAD_PRIORITY_NORMAL; break; - case ThreadPriority::Lowest: value = THREAD_PRIORITY_LOWEST; break; - } - ::SetThreadPriority(m_handle, int32_t(value)); - } - - private: - - Proc m_proc; - HANDLE m_handle; - - static DWORD WINAPI threadProc(void *arg) { - auto thread = reinterpret_cast(arg); - thread->m_proc(); - thread->decRef(); - return 0; - } - }; /** - * \brief RAII thread wrapper - * - * Wrapper for \c ThreadFn that can be used - * as a drop-in replacement for \c std::thread. + * \brief Thread wrapper + * + * Drop-in replacement for std::thread + * using plain win32 threads. */ class thread { public: + using id = uint32_t; + using native_handle_type = HANDLE; + thread() { } - explicit thread(std::function&& func) - : m_thread(new ThreadFn(std::move(func))) { } + explicit thread(ThreadProc&& proc); + + ~thread(); thread(thread&& other) - : m_thread(std::move(other.m_thread)) { } + : m_data(std::exchange(other.m_data, nullptr)) { } thread& operator = (thread&& other) { - m_thread = std::move(other.m_thread); + if (m_data) + m_data->decRef(); + + m_data = std::exchange(other.m_data, nullptr); return *this; } void detach() { - m_thread->detach(); - } - - void join() { - m_thread->join(); + m_data->decRef(); + m_data = nullptr; } bool joinable() const { - return m_thread != nullptr - && m_thread->joinable(); + return m_data != nullptr; } - void set_priority(ThreadPriority priority) { - m_thread->set_priority(priority); + id get_id() const { + return joinable() ? m_data->id : id(); } - - static uint32_t hardware_concurrency() { - SYSTEM_INFO info = { }; - ::GetSystemInfo(&info); - return info.dwNumberOfProcessors; + + native_handle_type native_handle() const { + return joinable() ? m_data->handle : native_handle_type(); } + void swap(thread& other) { + std::swap(m_data, other.m_data); + } + + void join(); + + void set_priority(ThreadPriority priority); + + static uint32_t hardware_concurrency(); + private: - Rc m_thread; + ThreadData* m_data = nullptr; + + static DWORD WINAPI threadProc(void* arg); }; @@ -153,8 +124,8 @@ namespace dxvk { SwitchToThread(); } - inline uint32_t get_id() { - return uint32_t(GetCurrentThreadId()); + inline thread::id get_id() { + return thread::id(GetCurrentThreadId()); } bool isInModuleDetachment();