From 1e8332f36a60f0e4d80d5932a9170196ae0f83a5 Mon Sep 17 00:00:00 2001 From: Connor McLaughlin Date: Thu, 5 May 2022 23:01:14 +1000 Subject: [PATCH] Misc: Move pxThread and friends to gui --- common/CMakeLists.txt | 1 - common/Darwin/DarwinSemaphore.cpp | 227 ----- common/Darwin/DarwinThreads.cpp | 28 +- common/Exceptions.cpp | 15 - common/General.h | 62 -- common/Linux/LnxThreads.cpp | 27 +- common/Mutex.cpp | 45 - common/Semaphore.cpp | 160 +-- common/ThreadTools.cpp | 765 +------------- common/Threading.h | 236 +---- common/ThreadingInternal.h | 30 - common/Windows/WinThreads.cpp | 26 +- common/common.vcxproj | 2 - common/common.vcxproj.filters | 8 +- pcsx2/CDVD/ThreadedFileReader.cpp | 2 + pcsx2/CDVD/ThreadedFileReader.h | 1 - pcsx2/CMakeLists.txt | 3 + pcsx2/GS/Renderers/SW/GSRasterizer.cpp | 1 - pcsx2/PINE.h | 2 +- pcsx2/gui/AppCommon.h | 2 +- pcsx2/gui/Debugger/CtrlMemSearch.h | 2 +- pcsx2/gui/PersistentThread.cpp | 1175 ++++++++++++++++++++++ {common => pcsx2/gui}/PersistentThread.h | 177 +++- {common => pcsx2/gui}/ScopedPtrMT.h | 2 +- pcsx2/gui/SysCoreThread.cpp | 13 +- pcsx2/gui/SysThreads.h | 2 +- pcsx2/gui/pxEventThread.h | 6 +- pcsx2/gui/pxEvents.h | 1 + pcsx2/gui/wxAppWithHelpers.cpp | 3 +- pcsx2/gui/wxAppWithHelpers.h | 6 +- pcsx2/pcsx2.vcxproj | 5 +- pcsx2/pcsx2.vcxproj.filters | 5 +- 32 files changed, 1393 insertions(+), 1647 deletions(-) delete mode 100644 common/ThreadingInternal.h create mode 100644 pcsx2/gui/PersistentThread.cpp rename {common => pcsx2/gui}/PersistentThread.h (64%) rename {common => pcsx2/gui}/ScopedPtrMT.h (99%) diff --git a/common/CMakeLists.txt b/common/CMakeLists.txt index 03f224a11e..123a425148 100644 --- a/common/CMakeLists.txt +++ b/common/CMakeLists.txt @@ -88,7 +88,6 @@ target_sources(common PRIVATE RwMutex.h SafeArray.h ScopedGuard.h - ScopedPtrMT.h SettingsInterface.h SettingsWrapper.h StringHelpers.h diff --git a/common/Darwin/DarwinSemaphore.cpp b/common/Darwin/DarwinSemaphore.cpp index dd5d5d203f..d3642e69bd 100644 --- a/common/Darwin/DarwinSemaphore.cpp +++ b/common/Darwin/DarwinSemaphore.cpp @@ -26,7 +26,6 @@ #include // mach_absolute_time() #include "common/Threading.h" -#include "common/ThreadingInternal.h" // -------------------------------------------------------------------------------------- // Semaphore Implementation for Darwin/OSX @@ -86,231 +85,5 @@ bool Threading::KernelSemaphore::TryWait() return true; } -/// Wait up to the given time -/// Returns true if successful, false if timed out -static bool WaitUpTo(semaphore_t sema, wxTimeSpan wxtime) -{ - mach_timespec_t time; - u64 ms = wxtime.GetMilliseconds().GetValue(); - time.tv_sec = ms / 1000; - time.tv_nsec = (ms % 1000) * 1000000; - kern_return_t res = semaphore_timedwait(sema, time); - if (res == KERN_OPERATION_TIMED_OUT) - return false; - MACH_CHECK(res); - return true; -} - -Threading::Semaphore::Semaphore() -{ - // other platforms explicitly make a thread-private (unshared) semaphore - // here. But it seems Mach doesn't support that. - MACH_CHECK(semaphore_create(mach_task_self(), (semaphore_t*)&m_sema, SYNC_POLICY_FIFO, 0)); - __atomic_store_n(&m_counter, 0, __ATOMIC_RELEASE); -} - -Threading::Semaphore::~Semaphore() -{ - MACH_CHECK(semaphore_destroy(mach_task_self(), (semaphore_t)m_sema)); -} - -void Threading::Semaphore::Reset() -{ - MACH_CHECK(semaphore_destroy(mach_task_self(), (semaphore_t)m_sema)); - MACH_CHECK(semaphore_create(mach_task_self(), (semaphore_t*)&m_sema, SYNC_POLICY_FIFO, 0)); - __atomic_store_n(&m_counter, 0, __ATOMIC_SEQ_CST); -} - -void Threading::Semaphore::Post() -{ - if (__atomic_fetch_add(&m_counter, 1, __ATOMIC_RELEASE) < 0) - MACH_CHECK(semaphore_signal(m_sema)); -} - -void Threading::Semaphore::Post(int multiple) -{ - for (int i = 0; i < multiple; ++i) - { - Post(); - } -} - -void Threading::Semaphore::WaitWithoutYield() -{ - pxAssertMsg(!wxThread::IsMain(), "Unyielding semaphore wait issued from the main/gui thread. Please use Wait() instead."); - if (__atomic_sub_fetch(&m_counter, 1, __ATOMIC_ACQUIRE) < 0) - MACH_CHECK(semaphore_wait(m_sema)); -} - -bool Threading::Semaphore::WaitWithoutYield(const wxTimeSpan& timeout) -{ - // This method is the reason why there has to be a special Darwin - // implementation of Semaphore. Note that semaphore_timedwait() is prone - // to returning with KERN_ABORTED, which basically signifies that some - // signal has worken it up. The best official "documentation" for - // semaphore_timedwait() is the way it's used in Grand Central Dispatch, - // which is open-source. - - if (__atomic_sub_fetch(&m_counter, 1, __ATOMIC_ACQUIRE) >= 0) - return true; - - // on x86 platforms, mach_absolute_time() returns nanoseconds - // TODO(aktau): on iOS a scale value from mach_timebase_info will be necessary - u64 const kOneThousand = 1000; - u64 const kOneBillion = kOneThousand * kOneThousand * kOneThousand; - u64 const delta = timeout.GetMilliseconds().GetValue() * (kOneThousand * kOneThousand); - mach_timespec_t ts; - kern_return_t kr = KERN_ABORTED; - for (u64 now = mach_absolute_time(), deadline = now + delta; - kr == KERN_ABORTED; now = mach_absolute_time()) - { - if (now > deadline) - { - // timed out by definition - kr = KERN_OPERATION_TIMED_OUT; - break; - } - - u64 timeleft = deadline - now; - ts.tv_sec = timeleft / kOneBillion; - ts.tv_nsec = timeleft % kOneBillion; - - // possible return values of semaphore_timedwait() (from XNU sources): - // internal kernel val -> return value - // THREAD_INTERRUPTED -> KERN_ABORTED - // THREAD_TIMED_OUT -> KERN_OPERATION_TIMED_OUT - // THREAD_AWAKENED -> KERN_SUCCESS - // THREAD_RESTART -> KERN_TERMINATED - // default -> KERN_FAILURE - kr = semaphore_timedwait(m_sema, ts); - } - - if (kr == KERN_OPERATION_TIMED_OUT) - { - int orig = __atomic_load_n(&m_counter, __ATOMIC_RELAXED); - while (orig < 0) - { - if (__atomic_compare_exchange_n(&m_counter, &orig, orig + 1, true, __ATOMIC_ACQUIRE, __ATOMIC_RELAXED)) - return false; - } - // Semaphore was signalled between our wait expiring and now, keep kernel sema in sync - kr = semaphore_wait(m_sema); - } - - // while it's entirely possible to have KERN_FAILURE here, we should - // probably assert so we can study and correct the actual error here - // (the thread dying while someone is wainting for it). - MACH_CHECK(kr); - return true; -} - -// This is a wxApp-safe implementation of Wait, which makes sure and executes the App's -// pending messages *if* the Wait is performed on the Main/GUI thread. This ensures that -// user input continues to be handled and that windows continue to repaint. If the Wait is -// called from another thread, no message pumping is performed. -void Threading::Semaphore::Wait() -{ -#if wxUSE_GUI - if (!wxThread::IsMain() || (wxTheApp == NULL)) - { - WaitWithoutYield(); - } - else if (_WaitGui_RecursionGuard(L"Semaphore::Wait")) - { - WaitWithoutYield(); - } - else - { - if (__atomic_sub_fetch(&m_counter, 1, __ATOMIC_ACQUIRE) >= 0) - return; - while (!WaitUpTo(m_sema, def_yieldgui_interval)) - { - YieldToMain(); - } - } -#else - WaitWithoutYield(); #endif -} -// This is a wxApp-safe implementation of WaitWithoutYield, which makes sure and executes the App's -// pending messages *if* the Wait is performed on the Main/GUI thread. This ensures that -// user input continues to be handled and that windows continue to repaint. If the Wait is -// called from another thread, no message pumping is performed. -// -// Returns: -// false if the wait timed out before the semaphore was signaled, or true if the signal was -// reached prior to timeout. -// -bool Threading::Semaphore::Wait(const wxTimeSpan& timeout) -{ -#if wxUSE_GUI - if (!wxThread::IsMain() || (wxTheApp == NULL)) - { - return WaitWithoutYield(timeout); - } - else if (_WaitGui_RecursionGuard(L"Semaphore::TimedWait")) - { - return WaitWithoutYield(timeout); - } - else - { - wxTimeSpan countdown((timeout)); - - do - { - if (WaitWithoutYield(def_yieldgui_interval)) - break; - YieldToMain(); - countdown -= def_yieldgui_interval; - } while (countdown.GetMilliseconds() > 0); - - return countdown.GetMilliseconds() > 0; - } -#else - return WaitWithoutYield(timeout); -#endif -} - -bool Threading::Semaphore::TryWait() -{ - int counter = __atomic_load_n(&m_counter, __ATOMIC_RELAXED); - while (counter > 0 && !__atomic_compare_exchange_n(&m_counter, &counter, counter - 1, true, __ATOMIC_ACQUIRE, __ATOMIC_RELAXED)) - ; - return counter > 0; -} - -// Performs an uncancellable wait on a semaphore; restoring the thread's previous cancel state -// after the wait has completed. Useful for situations where the semaphore itself is stored on -// the stack and passed to another thread via GUI message or such, avoiding complications where -// the thread might be canceled and the stack value becomes invalid. -// -// Performance note: this function has quite a bit more overhead compared to Semaphore::WaitWithoutYield(), so -// consider manually specifying the thread as uncancellable and using WaitWithoutYield() instead if you need -// to do a lot of no-cancel waits in a tight loop worker thread, for example. -// -// I'm unsure how to do this with pure Mach primitives, the docs in -// osfmk/man seem a bit out of date so perhaps there's a possibility, but -// since as far as I know Mach threads are 1-to-1 on BSD uthreads (and thus -// POSIX threads), this should work. -- aktau -void Threading::Semaphore::WaitNoCancel() -{ - int oldstate; - pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldstate); - Wait(); - pthread_setcancelstate(oldstate, NULL); -} - -void Threading::Semaphore::WaitNoCancel(const wxTimeSpan& timeout) -{ - int oldstate; - pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldstate); - Wait(timeout); - pthread_setcancelstate(oldstate, NULL); -} - -int Threading::Semaphore::Count() -{ - return __atomic_load_n(&m_counter, __ATOMIC_RELAXED); -} -#endif diff --git a/common/Darwin/DarwinThreads.cpp b/common/Darwin/DarwinThreads.cpp index 016d56fb53..d1aaa3347a 100644 --- a/common/Darwin/DarwinThreads.cpp +++ b/common/Darwin/DarwinThreads.cpp @@ -21,7 +21,7 @@ #include #include "common/PrecompiledHeader.h" -#include "common/PersistentThread.h" +#include "common/Threading.h" // Note: assuming multicore is safer because it forces the interlocked routines to use // the LOCK prefix. The prefix works on single core CPUs fine (but is slow), but not @@ -140,32 +140,6 @@ bool Threading::ThreadHandle::SetAffinity(u64 processor_mask) const return false; } -u64 Threading::pxThread::GetCpuTime() const -{ - // Get the cpu time for the thread belonging to this object. Use m_native_id and/or - // m_native_handle to implement it. Return value should be a measure of total time the - // thread has used on the CPU (scaled by the value returned by GetThreadTicksPerSecond(), - // which typically would be an OS-provided scalar or some sort). - if (!m_native_id) - { - return 0; - } - - return getthreadtime((thread_port_t)m_native_id); -} - -void Threading::pxThread::_platform_specific_OnStartInThread() -{ - m_native_id = (uptr)mach_thread_self(); -} - -void Threading::pxThread::_platform_specific_OnCleanupInThread() -{ - // cleanup of handles that were upened in - // _platform_specific_OnStartInThread - mach_port_deallocate(mach_task_self(), (thread_port_t)m_native_id); -} - // name can be up to 16 bytes void Threading::SetNameOfCurrentThread(const char* name) { diff --git a/common/Exceptions.cpp b/common/Exceptions.cpp index dd6f138067..aa7bc58f81 100644 --- a/common/Exceptions.cpp +++ b/common/Exceptions.cpp @@ -38,10 +38,6 @@ Fnptr_OutOfMemory pxDoOutOfMemory = NULL; #define DEVASSERT_INLINE __fi #endif -// Using a threadlocal assertion guard. Separate threads can assert at the same time. -// That's ok. What we don't want is the *same* thread recurse-asserting. -static DeclareTls(int) s_assert_guard(0); - pxDoAssertFnType* pxDoAssert = pxAssertImpl_LogIt; // make life easier for people using VC++ IDE by using this format, which allows double-click @@ -55,8 +51,6 @@ wxString DiagnosticOrigin::ToString(const wxChar* msg) const if (function != NULL) message.Write(" Function: %s\n", function); - message.Write(L" Thread: %s\n", WX_STR(Threading::pxGetCurrentThreadName())); - if (condition != NULL) message.Write(L" Condition: %ls\n", condition); @@ -99,15 +93,6 @@ bool pxAssertImpl_LogIt(const DiagnosticOrigin& origin, const wxChar* msg) DEVASSERT_INLINE void pxOnAssert(const DiagnosticOrigin& origin, const wxString& msg) { - // Recursion guard: Allow at least one recursive call. This is useful because sometimes - // we get meaningless assertions while unwinding stack traces after exceptions have occurred. - - RecursionGuard guard(s_assert_guard); - if (guard.Counter > 2) - { - return pxTrap(); - } - // wxWidgets doesn't come with debug builds on some Linux distros, and other distros make // it difficult to use the debug build (compilation failures). To handle these I've had to // bypass the internal wxWidgets assertion handler entirely, since it may not exist even if diff --git a/common/General.h b/common/General.h index afc9849781..eadd510704 100644 --- a/common/General.h +++ b/common/General.h @@ -36,68 +36,6 @@ }; \ }; - -// ---------------------------------------------------------------------------------------- -// RecursionGuard - Basic protection against function recursion -// ---------------------------------------------------------------------------------------- -// Thread safety note: If used in a threaded environment, you shoud use a handle to a __threadlocal -// storage variable (protects aaginst race conditions and, in *most* cases, is more desirable -// behavior as well. -// -// Rationale: wxWidgets has its own wxRecursionGuard, but it has a sloppy implementation with -// entirely unnecessary assertion checks. -// -class RecursionGuard -{ -public: - int& Counter; - - RecursionGuard(int& counter) - : Counter(counter) - { - ++Counter; - } - - virtual ~RecursionGuard() - { - --Counter; - } - - bool IsReentrant() const { return Counter > 1; } -}; - -// -------------------------------------------------------------------------------------- -// ICloneable / IActionInvocation / IDeletableObject -// -------------------------------------------------------------------------------------- -class IActionInvocation -{ -public: - virtual ~IActionInvocation() = default; - virtual void InvokeAction() = 0; -}; - -class ICloneable -{ -public: - virtual ICloneable* Clone() const = 0; -}; - -class IDeletableObject -{ -public: - virtual ~IDeletableObject() = default; - - virtual void DeleteSelf() = 0; - virtual bool IsBeingDeleted() = 0; - -protected: - // This function is GUI implementation dependent! It's implemented by PCSX2's AppHost, - // but if the SysCore is being linked to another front end, you'll need to implement this - // yourself. Most GUIs have built in message pumps. If a platform lacks one then you'll - // need to implement one yourself (yay?). - virtual void DoDeletion() = 0; -}; - // -------------------------------------------------------------------------------------- // PageProtectionMode // -------------------------------------------------------------------------------------- diff --git a/common/Linux/LnxThreads.cpp b/common/Linux/LnxThreads.cpp index 280f36d320..8368d3fb83 100644 --- a/common/Linux/LnxThreads.cpp +++ b/common/Linux/LnxThreads.cpp @@ -34,7 +34,7 @@ #include #endif -#include "common/PersistentThread.h" +#include "common/Threading.h" // We wont need this until we actually have this more then just stubbed out, so I'm commenting this out // to remove an unneeded dependency. @@ -199,31 +199,6 @@ bool Threading::ThreadHandle::SetAffinity(u64 processor_mask) const #endif } -u64 Threading::pxThread::GetCpuTime() const -{ - // Get the cpu time for the thread belonging to this object. Use m_native_id and/or - // m_native_handle to implement it. Return value should be a measure of total time the - // thread has used on the CPU (scaled by the value returned by GetThreadTicksPerSecond(), - // which typically would be an OS-provided scalar or some sort). - - if (!m_native_id) - return 0; - - return get_thread_time(m_native_id); -} - -void Threading::pxThread::_platform_specific_OnStartInThread() -{ - // Obtain linux-specific thread IDs or Handles here, which can be used to query - // kernel scheduler performance information. - m_native_id = (uptr)pthread_self(); -} - -void Threading::pxThread::_platform_specific_OnCleanupInThread() -{ - // Cleanup handles here, which were opened above. -} - void Threading::SetNameOfCurrentThread(const char* name) { #if defined(__linux__) diff --git a/common/Mutex.cpp b/common/Mutex.cpp index b9dce24f1b..ee078b3b44 100644 --- a/common/Mutex.cpp +++ b/common/Mutex.cpp @@ -14,7 +14,6 @@ */ #include "common/Threading.h" -#include "common/ThreadingInternal.h" namespace Threading { @@ -196,56 +195,12 @@ bool Threading::Mutex::TryAcquire() // void Threading::Mutex::Acquire() { -#if wxUSE_GUI - if (!wxThread::IsMain() || (wxTheApp == NULL)) - { - pthread_mutex_lock(&m_mutex); - } - else if (_WaitGui_RecursionGuard(L"Mutex::Acquire")) - { - pthread_mutex_lock(&m_mutex); - } - else - { - //ScopedBusyCursor hourglass( Cursor_KindaBusy ); - while (!AcquireWithoutYield(def_yieldgui_interval)) - YieldToMain(); - } -#else pthread_mutex_lock(&m_mutex); -#endif } bool Threading::Mutex::Acquire(const wxTimeSpan& timeout) { -#if wxUSE_GUI - if (!wxThread::IsMain() || (wxTheApp == NULL)) - { - return AcquireWithoutYield(timeout); - } - else if (_WaitGui_RecursionGuard(L"Mutex::TimedAcquire")) - { - return AcquireWithoutYield(timeout); - } - else - { - //ScopedBusyCursor hourglass( Cursor_KindaBusy ); - wxTimeSpan countdown((timeout)); - - do - { - if (AcquireWithoutYield(def_yieldgui_interval)) - break; - YieldToMain(); - countdown -= def_yieldgui_interval; - } while (countdown.GetMilliseconds() > 0); - - return countdown.GetMilliseconds() > 0; - } - -#else return AcquireWithoutYield(timeout); -#endif } // Performs a wait on a locked mutex, or returns instantly if the mutex is unlocked. diff --git a/common/Semaphore.cpp b/common/Semaphore.cpp index a7bbfb7e22..89fafe454d 100644 --- a/common/Semaphore.cpp +++ b/common/Semaphore.cpp @@ -14,7 +14,10 @@ */ #include "common/Threading.h" -#include "common/ThreadingInternal.h" + +#ifdef _WIN32 +#include "common/RedtapeWindows.h" +#endif // -------------------------------------------------------------------------------------- // Semaphore Implementations @@ -165,157 +168,4 @@ bool Threading::KernelSemaphore::TryWait() #endif } -Threading::Semaphore::Semaphore() -{ - sem_init(&m_sema, false, 0); -} - -Threading::Semaphore::~Semaphore() -{ - sem_destroy(&m_sema); -} - -void Threading::Semaphore::Reset() -{ - sem_destroy(&m_sema); - sem_init(&m_sema, false, 0); -} - -void Threading::Semaphore::Post() -{ - sem_post(&m_sema); -} - -void Threading::Semaphore::Post(int multiple) -{ -#if defined(_MSC_VER) - sem_post_multiple(&m_sema, multiple); -#else - // Only w32pthreads has the post_multiple, but it's easy enough to fake: - while (multiple > 0) - { - multiple--; - sem_post(&m_sema); - } -#endif -} - -void Threading::Semaphore::WaitWithoutYield() -{ - pxAssertMsg(!wxThread::IsMain(), "Unyielding semaphore wait issued from the main/gui thread. Please use Wait() instead."); - sem_wait(&m_sema); -} - -bool Threading::Semaphore::WaitWithoutYield(const wxTimeSpan& timeout) -{ - wxDateTime megafail(wxDateTime::UNow() + timeout); - const timespec fail = {megafail.GetTicks(), megafail.GetMillisecond() * 1000000}; - return sem_timedwait(&m_sema, &fail) == 0; -} - - -// This is a wxApp-safe implementation of Wait, which makes sure and executes the App's -// pending messages *if* the Wait is performed on the Main/GUI thread. This ensures that -// user input continues to be handled and that windoes continue to repaint. If the Wait is -// called from another thread, no message pumping is performed. -// -void Threading::Semaphore::Wait() -{ -#if wxUSE_GUI - if (!wxThread::IsMain() || (wxTheApp == NULL)) - { - sem_wait(&m_sema); - } - else if (_WaitGui_RecursionGuard(L"Semaphore::Wait")) - { - sem_wait(&m_sema); - } - else - { - //ScopedBusyCursor hourglass( Cursor_KindaBusy ); - while (!WaitWithoutYield(def_yieldgui_interval)) - YieldToMain(); - } -#else - sem_wait(&m_sema); -#endif -} - -// This is a wxApp-safe implementation of WaitWithoutYield, which makes sure and executes the App's -// pending messages *if* the Wait is performed on the Main/GUI thread. This ensures that -// user input continues to be handled and that windows continue to repaint. If the Wait is -// called from another thread, no message pumping is performed. -// -// Returns: -// false if the wait timed out before the semaphore was signaled, or true if the signal was -// reached prior to timeout. -// -bool Threading::Semaphore::Wait(const wxTimeSpan& timeout) -{ -#if wxUSE_GUI - if (!wxThread::IsMain() || (wxTheApp == NULL)) - { - return WaitWithoutYield(timeout); - } - else if (_WaitGui_RecursionGuard(L"Semaphore::TimedWait")) - { - return WaitWithoutYield(timeout); - } - else - { - //ScopedBusyCursor hourglass( Cursor_KindaBusy ); - wxTimeSpan countdown((timeout)); - - do - { - if (WaitWithoutYield(def_yieldgui_interval)) - break; - YieldToMain(); - countdown -= def_yieldgui_interval; - } while (countdown.GetMilliseconds() > 0); - - return countdown.GetMilliseconds() > 0; - } -#else - return WaitWithoutYield(timeout); -#endif -} - -// Performs an uncancellable wait on a semaphore; restoring the thread's previous cancel state -// after the wait has completed. Useful for situations where the semaphore itself is stored on -// the stack and passed to another thread via GUI message or such, avoiding complications where -// the thread might be canceled and the stack value becomes invalid. -// -// Performance note: this function has quite a bit more overhead compared to Semaphore::WaitWithoutYield(), so -// consider manually specifying the thread as uncancellable and using WaitWithoutYield() instead if you need -// to do a lot of no-cancel waits in a tight loop worker thread, for example. -void Threading::Semaphore::WaitNoCancel() -{ - int oldstate; - pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldstate); - //WaitWithoutYield(); - Wait(); - pthread_setcancelstate(oldstate, NULL); -} - -void Threading::Semaphore::WaitNoCancel(const wxTimeSpan& timeout) -{ - int oldstate; - pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldstate); - //WaitWithoutYield( timeout ); - Wait(timeout); - pthread_setcancelstate(oldstate, NULL); -} - -bool Threading::Semaphore::TryWait() -{ - return sem_trywait(&m_sema) == 0; -} - -int Threading::Semaphore::Count() -{ - int retval; - sem_getvalue(&m_sema, &retval); - return retval; -} -#endif +#endif \ No newline at end of file diff --git a/common/ThreadTools.cpp b/common/ThreadTools.cpp index cda6215750..cba098f3cd 100644 --- a/common/ThreadTools.cpp +++ b/common/ThreadTools.cpp @@ -13,777 +13,14 @@ * If not, see . */ -#ifdef __linux__ -#include // for pthread_kill, which is in pthread.h on w32-pthreads -#endif - -#include "common/PersistentThread.h" -#include "common/ThreadingInternal.h" -#include "common/EventSource.inl" -#include "common/General.h" - -using namespace Threading; - -template class EventSource; - -// 100ms interval for waitgui (issued from blocking semaphore waits on the main thread, -// to avoid gui deadlock). -const wxTimeSpan Threading::def_yieldgui_interval(0, 0, 0, 100); - -ConsoleLogSource_Threading::ConsoleLogSource_Threading() -{ - static const TraceLogDescriptor myDesc = - { - L"p&xThread", L"pxThread", - pxLt("Threading activity: start, detach, sync, deletion, etc.")}; - - m_Descriptor = &myDesc; -} - -ConsoleLogSource_Threading pxConLog_Thread; - - -class StaticMutex : public Mutex -{ -protected: - bool& m_DeletedFlag; - -public: - StaticMutex(bool& deletedFlag) - : m_DeletedFlag(deletedFlag) - { - } - - virtual ~StaticMutex() - { - m_DeletedFlag = true; - } -}; - -static pthread_key_t curthread_key = 0; -static s32 total_key_count = 0; - -static bool tkl_destructed = false; -static StaticMutex total_key_lock(tkl_destructed); - -static void make_curthread_key(const pxThread* thr) -{ - pxAssumeDev(!tkl_destructed, "total_key_lock is destroyed; program is shutting down; cannot create new thread key."); - - ScopedLock lock(total_key_lock); - if (total_key_count++ != 0) - return; - - if (0 != pthread_key_create(&curthread_key, NULL)) - { - pxThreadLog.Error(thr->GetName(), L"Thread key creation failed (probably out of memory >_<)"); - curthread_key = 0; - } -} - -static void unmake_curthread_key() -{ - ScopedLock lock; - if (!tkl_destructed) - lock.AssignAndLock(total_key_lock); - - if (--total_key_count > 0) - return; - - if (curthread_key) - pthread_key_delete(curthread_key); - - curthread_key = 0; -} +#include "common/Threading.h" void Threading::pxTestCancel() { pthread_testcancel(); } -// Returns a handle to the current persistent thread. If the current thread does not belong -// to the pxThread table, NULL is returned. Since the main/ui thread is not created -// through pxThread it will also return NULL. Callers can use wxThread::IsMain() to -// test if the NULL thread is the main thread. -pxThread* Threading::pxGetCurrentThread() -{ - return !curthread_key ? NULL : (pxThread*)pthread_getspecific(curthread_key); -} - -// returns the name of the current thread, or "Unknown" if the thread is neither a pxThread -// nor the Main/UI thread. -wxString Threading::pxGetCurrentThreadName() -{ - if (pxThread* thr = pxGetCurrentThread()) - { - return thr->GetName(); - } - else if (wxThread::IsMain()) - { - return L"Main/UI"; - } - - return L"Unknown"; -} - -void Threading::pxYield(int ms) -{ - if (pxThread* thr = pxGetCurrentThread()) - thr->Yield(ms); - else - Sleep(ms); -} - -// (intended for internal use only) -// Returns true if the Wait is recursive, or false if the Wait is safe and should be -// handled via normal yielding methods. -bool Threading::_WaitGui_RecursionGuard(const wxChar* name) -{ - AffinityAssert_AllowFrom_MainUI(); - - // In order to avoid deadlock we need to make sure we cut some time to handle messages. - // But this can result in recursive yield calls, which would crash the app. Protect - // against them here and, if recursion is detected, perform a standard blocking wait. - - static int __Guard = 0; - RecursionGuard guard(__Guard); - - //if( pxAssertDev( !guard.IsReentrant(), "Recursion during UI-bound threading wait object." ) ) return false; - - if (!guard.IsReentrant()) - return false; - pxThreadLog.Write(pxGetCurrentThreadName(), - pxsFmt(L"Yield recursion in %s; opening modal dialog.", name)); - return true; -} - __fi void Threading::Timeslice() { sched_yield(); } - -void Threading::pxThread::_pt_callback_cleanup(void* handle) -{ - ((pxThread*)handle)->_ThreadCleanup(); -} - - -Threading::pxThread::pxThread(const wxString& name) - : m_name(name) - , m_thread() - , m_native_id(0) - , m_native_handle(0) - , m_detached(true) // start out with m_thread in detached/invalid state - , m_running(false) -{ -} - -// This destructor performs basic "last chance" cleanup, which is a blocking join -// against the thread. Extending classes should almost always implement their own -// thread closure process, since any pxThread will, by design, not terminate -// unless it has been properly canceled (resulting in deadlock). -// -// Thread safety: This class must not be deleted from its own thread. That would be -// like marrying your sister, and then cheating on her with your daughter. -Threading::pxThread::~pxThread() -{ - try - { - pxThreadLog.Write(GetName(), L"Executing default destructor!"); - - if (m_running) - { - pxThreadLog.Write(GetName(), L"Waiting for running thread to end..."); - m_mtx_InThread.Wait(); - pxThreadLog.Write(GetName(), L"Thread ended gracefully."); - } - Threading::Sleep(1); - Detach(); - } - DESTRUCTOR_CATCHALL -} - -bool Threading::pxThread::AffinityAssert_AllowFromSelf(const DiagnosticOrigin& origin) const -{ - if (IsSelf()) - return true; - - if (IsDevBuild) - pxOnAssert(origin, pxsFmt(L"Thread affinity violation: Call allowed from '%s' thread only.", WX_STR(GetName()))); - - return false; -} - -bool Threading::pxThread::AffinityAssert_DisallowFromSelf(const DiagnosticOrigin& origin) const -{ - if (!IsSelf()) - return true; - - if (IsDevBuild) - pxOnAssert(origin, pxsFmt(L"Thread affinity violation: Call is *not* allowed from '%s' thread.", WX_STR(GetName()))); - - return false; -} - -void Threading::pxThread::FrankenMutex(Mutex& mutex) -{ - if (mutex.RecreateIfLocked()) - { - // Our lock is bupkis, which means the previous thread probably deadlocked. - // Let's create a new mutex lock to replace it. - - pxThreadLog.Error(GetName(), L"Possible deadlock detected on restarted mutex!"); - } -} - -// Main entry point for starting or e-starting a persistent thread. This function performs necessary -// locks and checks for avoiding race conditions, and then calls OnStart() immediately before -// the actual thread creation. Extending classes should generally not override Start(), and should -// instead override DoPrepStart instead. -// -// This function should not be called from the owner thread. -void Threading::pxThread::Start() -{ - // Prevents sudden parallel startup, and or parallel startup + cancel: - ScopedLock startlock(m_mtx_start); - if (m_running) - { - pxThreadLog.Write(GetName(), L"Start() called on running thread; ignorning..."); - return; - } - - Detach(); // clean up previous thread handle, if one exists. - OnStart(); - - m_except = NULL; - - pxThreadLog.Write(GetName(), L"Calling pthread_create..."); - if (pthread_create(&m_thread, NULL, _internal_callback, this) != 0) - throw Exception::ThreadCreationError(this).SetDiagMsg(L"Thread creation error: " + wxString(std::strerror(errno))); - -#ifdef ASAN_WORKAROUND - // Recent Asan + libc6 do pretty bad stuff on the thread init => https://gcc.gnu.org/bugzilla/show_bug.cgi?id=77982 - // - // In our case, the semaphore was posted (counter is 1) but thread is still - // waiting... So waits 100ms and checks the counter value manually - if (!m_sem_startup.WaitWithoutYield(wxTimeSpan(0, 0, 0, 100))) - { - if (m_sem_startup.Count() == 0) - throw Exception::ThreadCreationError(this).SetDiagMsg(L"Thread creation error: %s thread never posted startup semaphore."); - } -#else - if (!m_sem_startup.WaitWithoutYield(wxTimeSpan(0, 0, 3, 0))) - { - RethrowException(); - - // And if the thread threw nothing of its own: - throw Exception::ThreadCreationError(this).SetDiagMsg(L"Thread creation error: %s thread never posted startup semaphore."); - } -#endif - - // Event Rationale (above): Performing this semaphore wait on the created thread is "slow" in the - // sense that it stalls the calling thread completely until the new thread is created - // (which may not always be desirable). But too bad. In order to safely use 'running' locks - // and detachment management, this *has* to be done. By rule, starting new threads shouldn't - // be done very often anyway, hence the concept of Threadpooling for rapidly rotating tasks. - // (and indeed, this semaphore wait might, in fact, be very swift compared to other kernel - // overhead in starting threads). - - // (this could also be done using operating system specific calls, since any threaded OS has - // functions that allow us to see if a thread is running or not, and to block against it even if - // it's been detached -- removing the need for m_mtx_InThread and the semaphore wait above. But - // pthreads kinda lacks that stuff, since pthread_join() has no timeout option making it im- - // possible to safely block against a running thread) -} - -// Returns: TRUE if the detachment was performed, or FALSE if the thread was -// already detached or isn't running at all. -// This function should not be called from the owner thread. -bool Threading::pxThread::Detach() -{ - AffinityAssert_DisallowFromSelf(pxDiagSpot); - - if (m_detached.exchange(true)) - return false; - pthread_detach(m_thread); - return true; -} - -bool Threading::pxThread::_basecancel() -{ - if (!m_running) - return false; - - if (m_detached) - { - pxThreadLog.Warn(GetName(), L"Ignoring attempted cancellation of detached thread."); - return false; - } - - pthread_cancel(m_thread); - return true; -} - -// Remarks: -// Provision of non-blocking Cancel() is probably academic, since destroying a pxThread -// object performs a blocking Cancel regardless of if you explicitly do a non-blocking Cancel() -// prior, since the ExecuteTaskInThread() method requires a valid object state. If you really need -// fire-and-forget behavior on threads, use pthreads directly for now. -// -// This function should not be called from the owner thread. -// -// Parameters: -// isBlocking - indicates if the Cancel action should block for thread completion or not. -// -// Exceptions raised by the blocking thread will be re-thrown into the main thread. If isBlocking -// is false then no exceptions will occur. -// -void Threading::pxThread::Cancel(bool isBlocking) -{ - AffinityAssert_DisallowFromSelf(pxDiagSpot); - - // Prevent simultaneous startup and cancel, necessary to avoid - ScopedLock startlock(m_mtx_start); - - if (!_basecancel()) - return; - - if (isBlocking) - { - WaitOnSelf(m_mtx_InThread); - Detach(); - } -} - -bool Threading::pxThread::Cancel(const wxTimeSpan& timespan) -{ - AffinityAssert_DisallowFromSelf(pxDiagSpot); - - // Prevent simultaneous startup and cancel: - ScopedLock startlock(m_mtx_start); - - if (!_basecancel()) - return true; - - if (!WaitOnSelf(m_mtx_InThread, timespan)) - return false; - Detach(); - return true; -} - - -// Blocks execution of the calling thread until this thread completes its task. The -// caller should make sure to signal the thread to exit, or else blocking may deadlock the -// calling thread. Classes which extend pxThread should override this method -// and signal any necessary thread exit variables prior to blocking. -// -// Returns the return code of the thread. -// This method is roughly the equivalent of pthread_join(). -// -// Exceptions raised by the blocking thread will be re-thrown into the main thread. -// -void Threading::pxThread::Block() -{ - AffinityAssert_DisallowFromSelf(pxDiagSpot); - WaitOnSelf(m_mtx_InThread); -} - -bool Threading::pxThread::Block(const wxTimeSpan& timeout) -{ - AffinityAssert_DisallowFromSelf(pxDiagSpot); - return WaitOnSelf(m_mtx_InThread, timeout); -} - -bool Threading::pxThread::IsSelf() const -{ - // Detached threads may have their pthread handles recycled as newer threads, causing - // false IsSelf reports. - return !m_detached && (pthread_self() == m_thread); -} - -bool Threading::pxThread::IsRunning() const -{ - return m_running; -} - -void Threading::pxThread::AddListener(EventListener_Thread& evt) -{ - evt.SetThread(this); - m_evtsrc_OnDelete.Add(evt); -} - -// Throws an exception if the thread encountered one. Uses the BaseException's Rethrow() method, -// which ensures the exception type remains consistent. Debuggable stacktraces will be lost, since -// the thread will have allowed itself to terminate properly. -void Threading::pxThread::RethrowException() const -{ - // Thread safety note: always detach the m_except pointer. If we checked it for NULL, the - // pointer might still be invalid after detachment, so might as well just detach and check - // after. - - ScopedExcept ptr(const_cast(this)->m_except.DetachPtr()); - if (ptr) - ptr->Rethrow(); -} - -static bool m_BlockDeletions = false; - -bool Threading::AllowDeletions() -{ - AffinityAssert_AllowFrom_MainUI(); - return !m_BlockDeletions; -} - -void Threading::YieldToMain() -{ - m_BlockDeletions = true; - wxTheApp->Yield(true); - m_BlockDeletions = false; -} - -void Threading::pxThread::_selfRunningTest(const wxChar* name) const -{ - if (HasPendingException()) - { - pxThreadLog.Error(GetName(), pxsFmt(L"An exception was thrown while waiting on a %s.", name)); - RethrowException(); - } - - if (!m_running) - { - throw Exception::CancelEvent(pxsFmt( - L"Blocking thread %s was terminated while another thread was waiting on a %s.", - WX_STR(GetName()), name)); - } - - // Thread is still alive and kicking (for now) -- yield to other messages and hope - // that impending chaos does not ensue. [it shouldn't since we block pxThread - // objects from being deleted until outside the scope of a mutex/semaphore wait). - - if ((wxTheApp != NULL) && wxThread::IsMain() && !_WaitGui_RecursionGuard(L"WaitForSelf")) - Threading::YieldToMain(); -} - -// This helper function is a deadlock-safe method of waiting on a semaphore in a pxThread. If the -// thread is terminated or canceled by another thread or a nested action prior to the semaphore being -// posted, this function will detect that and throw a CancelEvent exception is thrown. -// -// Note: Use of this function only applies to semaphores which are posted by the worker thread. Calling -// this function from the context of the thread itself is an error, and a dev assertion will be generated. -// -// Exceptions: -// This function will rethrow exceptions raised by the persistent thread, if it throws an error -// while the calling thread is blocking (which also means the persistent thread has terminated). -// -void Threading::pxThread::WaitOnSelf(Semaphore& sem) const -{ - if (!AffinityAssert_DisallowFromSelf(pxDiagSpot)) - return; - - while (true) - { - if (sem.WaitWithoutYield(wxTimeSpan(0, 0, 0, 333))) - return; - _selfRunningTest(L"semaphore"); - } -} - -// This helper function is a deadlock-safe method of waiting on a mutex in a pxThread. -// If the thread is terminated or canceled by another thread or a nested action prior to the -// mutex being unlocked, this function will detect that and a CancelEvent exception is thrown. -// -// Note: Use of this function only applies to mutexes which are acquired by a worker thread. -// Calling this function from the context of the thread itself is an error, and a dev assertion -// will be generated. -// -// Exceptions: -// This function will rethrow exceptions raised by the persistent thread, if it throws an -// error while the calling thread is blocking (which also means the persistent thread has -// terminated). -// -void Threading::pxThread::WaitOnSelf(Mutex& mutex) const -{ - if (!AffinityAssert_DisallowFromSelf(pxDiagSpot)) - return; - - while (true) - { - if (mutex.WaitWithoutYield(wxTimeSpan(0, 0, 0, 333))) - return; - _selfRunningTest(L"mutex"); - } -} - -static const wxTimeSpan SelfWaitInterval(0, 0, 0, 333); - -bool Threading::pxThread::WaitOnSelf(Semaphore& sem, const wxTimeSpan& timeout) const -{ - if (!AffinityAssert_DisallowFromSelf(pxDiagSpot)) - return true; - - wxTimeSpan runningout(timeout); - - while (runningout.GetMilliseconds() > 0) - { - const wxTimeSpan interval((SelfWaitInterval < runningout) ? SelfWaitInterval : runningout); - if (sem.WaitWithoutYield(interval)) - return true; - _selfRunningTest(L"semaphore"); - runningout -= interval; - } - return false; -} - -bool Threading::pxThread::WaitOnSelf(Mutex& mutex, const wxTimeSpan& timeout) const -{ - if (!AffinityAssert_DisallowFromSelf(pxDiagSpot)) - return true; - - wxTimeSpan runningout(timeout); - - while (runningout.GetMilliseconds() > 0) - { - const wxTimeSpan interval((SelfWaitInterval < runningout) ? SelfWaitInterval : runningout); - if (mutex.WaitWithoutYield(interval)) - return true; - _selfRunningTest(L"mutex"); - runningout -= interval; - } - return false; -} - -// Inserts a thread cancellation point. If the thread has received a cancel request, this -// function will throw an SEH exception designed to exit the thread (so make sure to use C++ -// object encapsulation for anything that could leak resources, to ensure object unwinding -// and cleanup, or use the DoThreadCleanup() override to perform resource cleanup). -void Threading::pxThread::TestCancel() const -{ - AffinityAssert_AllowFromSelf(pxDiagSpot); - pthread_testcancel(); -} - -// Executes the virtual member method -void Threading::pxThread::_try_virtual_invoke(void (pxThread::*method)()) -{ - try - { - (this->*method)(); - } - - // ---------------------------------------------------------------------------- - // Neat repackaging for STL Runtime errors... - // - catch (std::runtime_error& ex) - { - m_except = new Exception::RuntimeError(ex, WX_STR(GetName())); - } - - // ---------------------------------------------------------------------------- - catch (Exception::RuntimeError& ex) - { - BaseException* woot = ex.Clone(); - woot->DiagMsg() += pxsFmt(L"(thread:%s)", WX_STR(GetName())); - m_except = woot; - } -#ifndef PCSX2_DEVBUILD - // ---------------------------------------------------------------------------- - // Bleh... don't bother with std::exception. runtime_error should catch anything - // useful coming out of the core STL libraries anyway, and these are best handled by - // the MSVC debugger (or by silent random annoying fail on debug-less linux). - /*catch( std::logic_error& ex ) - { - throw BaseException( pxsFmt( L"STL Logic Error (thread:%s): %s", - WX_STR(GetName()), WX_STR(fromUTF8( ex.what() )) ) - ); - } - catch( std::exception& ex ) - { - throw BaseException( pxsFmt( L"STL exception (thread:%s): %s", - WX_STR(GetName()), WX_STR(fromUTF8( ex.what() )) ) - ); - }*/ - // ---------------------------------------------------------------------------- - // BaseException -- same deal as LogicErrors. - // - catch (BaseException& ex) - { - BaseException* woot = ex.Clone(); - woot->DiagMsg() += pxsFmt(L"(thread:%s)", WX_STR(GetName())); - m_except = woot; - } -#endif -} - -// invoked internally when canceling or exiting the thread. Extending classes should implement -// OnCleanupInThread() to extend cleanup functionality. -void Threading::pxThread::_ThreadCleanup() -{ - AffinityAssert_AllowFromSelf(pxDiagSpot); - _try_virtual_invoke(&pxThread::OnCleanupInThread); - m_mtx_InThread.Release(); - - // Must set m_running LAST, as thread destructors depend on this value (it is used - // to avoid destruction of the thread until all internal data use has stopped. - m_running = false; -} - -wxString Threading::pxThread::GetName() const -{ - ScopedLock lock(m_mtx_ThreadName); - return m_name; -} - -void Threading::pxThread::SetName(const wxString& newname) -{ - ScopedLock lock(m_mtx_ThreadName); - m_name = newname; -} - -// This override is called by PeristentThread when the thread is first created, prior to -// calling ExecuteTaskInThread, and after the initial InThread lock has been claimed. -// This code is also executed within a "safe" environment, where the creating thread is -// blocked against m_sem_event. Make sure to do any necessary variable setup here, without -// worry that the calling thread might attempt to test the status of those variables -// before initialization has completed. -// -void Threading::pxThread::OnStartInThread() -{ - m_detached = false; - m_running = true; - - _platform_specific_OnStartInThread(); -} - -void Threading::pxThread::_internal_execute() -{ - m_mtx_InThread.Acquire(); - - _DoSetThreadName(GetName()); - make_curthread_key(this); - if (curthread_key) - pthread_setspecific(curthread_key, this); - - OnStartInThread(); - m_sem_startup.Post(); - - _try_virtual_invoke(&pxThread::ExecuteTaskInThread); -} - -// Called by Start, prior to actual starting of the thread, and after any previous -// running thread has been canceled or detached. -void Threading::pxThread::OnStart() -{ - m_native_handle = 0; - m_native_id = 0; - - FrankenMutex(m_mtx_InThread); - m_sem_event.Reset(); - m_sem_startup.Reset(); -} - -// Extending classes that override this method should always call it last from their -// personal implementations. -void Threading::pxThread::OnCleanupInThread() -{ - if (curthread_key) - pthread_setspecific(curthread_key, NULL); - - unmake_curthread_key(); - - _platform_specific_OnCleanupInThread(); - - m_native_handle = 0; - m_native_id = 0; - - m_evtsrc_OnDelete.Dispatch(0); -} - -// passed into pthread_create, and is used to dispatch the thread's object oriented -// callback function -void* Threading::pxThread::_internal_callback(void* itsme) -{ - if (!pxAssertDev(itsme != NULL, wxNullChar)) - return NULL; - - internal_callback_helper(itsme); - return nullptr; -} - -// __try is used in pthread_cleanup_push when CLEANUP_SEH is used as the cleanup model. -// That can't be used in a function that has objects that require unwinding (compile -// error C2712), so move it into a separate function. -void Threading::pxThread::internal_callback_helper(void* itsme) -{ - pxThread& owner = *static_cast(itsme); - - pthread_cleanup_push(_pt_callback_cleanup, itsme); - owner._internal_execute(); - pthread_cleanup_pop(true); -} - -void Threading::pxThread::_DoSetThreadName(const wxString& name) -{ - _DoSetThreadName(static_cast(name.ToUTF8())); -} - -// -------------------------------------------------------------------------------------- -// pthread Cond is an evil api that is not suited for Pcsx2 needs. -// Let's not use it. (Air) -// -------------------------------------------------------------------------------------- - -#if 0 -Threading::WaitEvent::WaitEvent() -{ - int err = 0; - - err = pthread_cond_init(&cond, NULL); - err = pthread_mutex_init(&mutex, NULL); -} - -Threading::WaitEvent::~WaitEvent() -{ - pthread_cond_destroy( &cond ); - pthread_mutex_destroy( &mutex ); -} - -void Threading::WaitEvent::Set() -{ - pthread_mutex_lock( &mutex ); - pthread_cond_signal( &cond ); - pthread_mutex_unlock( &mutex ); -} - -void Threading::WaitEvent::Wait() -{ - pthread_mutex_lock( &mutex ); - pthread_cond_wait( &cond, &mutex ); - pthread_mutex_unlock( &mutex ); -} -#endif - -// -------------------------------------------------------------------------------------- -// BaseThreadError -// -------------------------------------------------------------------------------------- - -wxString Exception::BaseThreadError::FormatDiagnosticMessage() const -{ - wxString null_str(L"Null Thread Object"); - return pxsFmt(m_message_diag, (m_thread == NULL) ? WX_STR(null_str) : WX_STR(m_thread->GetName())); -} - -wxString Exception::BaseThreadError::FormatDisplayMessage() const -{ - wxString null_str(L"Null Thread Object"); - return pxsFmt(m_message_user, (m_thread == NULL) ? WX_STR(null_str) : WX_STR(m_thread->GetName())); -} - -pxThread& Exception::BaseThreadError::Thread() -{ - pxAssertDev(m_thread != NULL, "NULL thread object on ThreadError exception."); - return *m_thread; -} -const pxThread& Exception::BaseThreadError::Thread() const -{ - pxAssertDev(m_thread != NULL, "NULL thread object on ThreadError exception."); - return *m_thread; -} diff --git a/common/Threading.h b/common/Threading.h index 3df9b4db21..7921647ace 100644 --- a/common/Threading.h +++ b/common/Threading.h @@ -15,6 +15,7 @@ #pragma once +#include #ifdef _WIN32 // thanks I hate it. #include @@ -31,43 +32,6 @@ #include "common/General.h" #include -#undef Yield // release the burden of windows.h global namespace spam. - -#define AffinityAssert_AllowFrom_MainUI() \ - pxAssertMsg(wxThread::IsMain(), "Thread affinity violation: Call allowed from main thread only.") - -// -------------------------------------------------------------------------------------- -// pxThreadLog / ConsoleLogSource_Threading -// -------------------------------------------------------------------------------------- - -class ConsoleLogSource_Threading : ConsoleLogSource -{ - typedef ConsoleLogSource _parent; - -public: - using _parent::IsActive; - - ConsoleLogSource_Threading(); - - bool Write(const wxString& thrname, const wxChar* msg) - { - return _parent::Write(wxsFormat(L"(thread:%s) ", WX_STR(thrname)) + msg); - } - bool Warn(const wxString& thrname, const wxChar* msg) - { - return _parent::Warn(wxsFormat(L"(thread:%s) ", WX_STR(thrname)) + msg); - } - bool Error(const wxString& thrname, const wxChar* msg) - { - return _parent::Error(wxsFormat(L"(thread:%s) ", WX_STR(thrname)) + msg); - } -}; - -extern ConsoleLogSource_Threading pxConLog_Thread; - -#define pxThreadLog pxConLog_Thread.IsActive() && pxConLog_Thread - - // -------------------------------------------------------------------------------------- // PCSX2_THREAD_LOCAL - Defines platform/operating system support for Thread Local Storage // -------------------------------------------------------------------------------------- @@ -86,8 +50,6 @@ extern ConsoleLogSource_Threading pxConLog_Thread; #define DeclareTls(x) x #endif -class wxTimeSpan; - namespace Threading { class ThreadHandle; @@ -95,75 +57,17 @@ namespace Threading class RwMutex; extern void pxTestCancel(); - extern pxThread* pxGetCurrentThread(); - extern wxString pxGetCurrentThreadName(); + extern void YieldToMain(); + extern u64 GetThreadCpuTime(); extern u64 GetThreadTicksPerSecond(); /// Set the name of the current thread extern void SetNameOfCurrentThread(const char* name); - // Yields the current thread and provides cancellation points if the thread is managed by - // pxThread. Unmanaged threads use standard Sleep. - extern void pxYield(int ms); + extern const wxTimeSpan def_yieldgui_interval; } // namespace Threading -namespace Exception -{ - class BaseThreadError : public RuntimeError - { - DEFINE_EXCEPTION_COPYTORS(BaseThreadError, RuntimeError) - DEFINE_EXCEPTION_MESSAGES(BaseThreadError) - - public: - Threading::pxThread* m_thread; - - protected: - BaseThreadError() - { - m_thread = NULL; - } - - public: - explicit BaseThreadError(Threading::pxThread* _thread) - { - m_thread = _thread; - m_message_diag = L"An unspecified thread-related error occurred (thread=%s)"; - } - - explicit BaseThreadError(Threading::pxThread& _thread) - { - m_thread = &_thread; - m_message_diag = L"An unspecified thread-related error occurred (thread=%s)"; - } - - virtual wxString FormatDiagnosticMessage() const; - virtual wxString FormatDisplayMessage() const; - - Threading::pxThread& Thread(); - const Threading::pxThread& Thread() const; - }; - - class ThreadCreationError : public BaseThreadError - { - DEFINE_EXCEPTION_COPYTORS(ThreadCreationError, BaseThreadError) - - public: - explicit ThreadCreationError(Threading::pxThread* _thread) - { - m_thread = _thread; - SetBothMsgs(L"Thread creation failure. An unspecified error occurred while trying to create the %s thread."); - } - - explicit ThreadCreationError(Threading::pxThread& _thread) - { - m_thread = &_thread; - SetBothMsgs(L"Thread creation failure. An unspecified error occurred while trying to create the %s thread."); - } - }; -} // namespace Exception - - namespace Threading { // -------------------------------------------------------------------------------------- @@ -186,22 +90,6 @@ namespace Threading // sleeps the current thread for the given number of milliseconds. extern void Sleep(int ms); -// pthread Cond is an evil api that is not suited for Pcsx2 needs. -// Let's not use it. Use mutexes and semaphores instead to create waits. (Air) -#if 0 - struct WaitEvent - { - pthread_cond_t cond; - pthread_mutex_t mutex; - - WaitEvent(); - ~WaitEvent(); - - void Set(); - void Wait(); - }; -#endif - // -------------------------------------------------------------------------------------- // ThreadHandle // -------------------------------------------------------------------------------------- @@ -374,48 +262,6 @@ namespace Threading void Reset(); }; - class Semaphore - { - protected: -#ifdef __APPLE__ - semaphore_t m_sema; - int m_counter; -#else - sem_t m_sema; -#endif - - public: - Semaphore(); - virtual ~Semaphore(); - - void Reset(); - void Post(); - void Post(int multiple); - - void WaitWithoutYield(); - bool WaitWithoutYield(const wxTimeSpan& timeout); - void WaitNoCancel(); - void WaitNoCancel(const wxTimeSpan& timeout); - void WaitWithoutYieldWithSpin() - { - u32 waited = 0; - while (true) - { - if (TryWait()) - return; - if (waited >= SPIN_TIME_NS) - break; - waited += ShortSpin(); - } - WaitWithoutYield(); - } - bool TryWait(); - int Count(); - - void Wait(); - bool Wait(const wxTimeSpan& timeout); - }; - class Mutex { protected: @@ -503,78 +349,4 @@ namespace Threading // Special constructor used by ScopedTryLock ScopedLock(const Mutex& locker, bool isTryLock); }; - - class ScopedTryLock : public ScopedLock - { - public: - ScopedTryLock(const Mutex& locker) - : ScopedLock(locker, true) - { - } - virtual ~ScopedTryLock() = default; - bool Failed() const { return !m_IsLocked; } - }; - - // -------------------------------------------------------------------------------------- - // ScopedNonblockingLock - // -------------------------------------------------------------------------------------- - // A ScopedTryLock branded for use with Nonblocking mutexes. See ScopedTryLock for details. - // - class ScopedNonblockingLock - { - DeclareNoncopyableObject(ScopedNonblockingLock); - - protected: - NonblockingMutex& m_lock; - bool m_IsLocked; - - public: - ScopedNonblockingLock(NonblockingMutex& locker) - : m_lock(locker) - , m_IsLocked(m_lock.TryAcquire()) - { - } - - virtual ~ScopedNonblockingLock() - { - if (m_IsLocked) - m_lock.Release(); - } - - bool Failed() const { return !m_IsLocked; } - }; - - // -------------------------------------------------------------------------------------- - // ScopedLockBool - // -------------------------------------------------------------------------------------- - // A ScopedLock in which you specify an external bool to get updated on locks/unlocks. - // Note that the isLockedBool should only be used as an indicator for the locked status, - // and not actually depended on for thread synchronization... - - struct ScopedLockBool - { - ScopedLock m_lock; - std::atomic& m_bool; - - ScopedLockBool(Mutex& mutexToLock, std::atomic& isLockedBool) - : m_lock(mutexToLock) - , m_bool(isLockedBool) - { - m_bool.store(m_lock.IsLocked(), std::memory_order_relaxed); - } - virtual ~ScopedLockBool() - { - m_bool.store(false, std::memory_order_relaxed); - } - void Acquire() - { - m_lock.Acquire(); - m_bool.store(m_lock.IsLocked(), std::memory_order_relaxed); - } - void Release() - { - m_bool.store(false, std::memory_order_relaxed); - m_lock.Release(); - } - }; } // namespace Threading diff --git a/common/ThreadingInternal.h b/common/ThreadingInternal.h deleted file mode 100644 index fa1ebaccd2..0000000000 --- a/common/ThreadingInternal.h +++ /dev/null @@ -1,30 +0,0 @@ -/* PCSX2 - PS2 Emulator for PCs - * Copyright (C) 2002-2010 PCSX2 Dev Team - * - * PCSX2 is free software: you can redistribute it and/or modify it under the terms - * of the GNU Lesser General Public License as published by the Free Software Found- - * ation, either version 3 of the License, or (at your option) any later version. - * - * PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; - * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR - * PURPOSE. See the GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along with PCSX2. - * If not, see . - */ - -#pragma once - -#include -#include -#include - -namespace Threading -{ - extern const wxTimeSpan def_yieldgui_interval; - - extern bool _WaitGui_RecursionGuard(const wxChar* name); - - extern void YieldToMain(); - extern bool AllowDeletions(); -} // namespace Threading diff --git a/common/Windows/WinThreads.cpp b/common/Windows/WinThreads.cpp index 938af62db3..02718314cf 100644 --- a/common/Windows/WinThreads.cpp +++ b/common/Windows/WinThreads.cpp @@ -16,7 +16,7 @@ #if defined(_WIN32) #include "common/RedtapeWindows.h" -#include "common/PersistentThread.h" +#include "common/Threading.h" #include "common/emitter/tools.h" __fi void Threading::Sleep(int ms) @@ -142,30 +142,6 @@ u64 Threading::GetThreadTicksPerSecond() return frequency; } -u64 Threading::pxThread::GetCpuTime() const -{ - u64 ret = 0; - if (m_native_handle) - QueryThreadCycleTime((HANDLE)m_native_handle, &ret); - return ret; -} - -void Threading::pxThread::_platform_specific_OnStartInThread() -{ - // OpenThread Note: Vista and Win7 need only THREAD_QUERY_LIMITED_INFORMATION (XP and 2k need more), - // however we own our process threads, so shouldn't matter in any case... - - m_native_id = (uptr)GetCurrentThreadId(); - m_native_handle = (uptr)OpenThread(THREAD_QUERY_INFORMATION, false, (DWORD)m_native_id); - - pxAssertDev(m_native_handle, wxNullChar); -} - -void Threading::pxThread::_platform_specific_OnCleanupInThread() -{ - CloseHandle((HANDLE)m_native_handle); -} - void Threading::SetNameOfCurrentThread(const char* name) { // This feature needs Windows headers and MSVC's SEH support: diff --git a/common/common.vcxproj b/common/common.vcxproj index e8392cffa8..da60a48a7e 100644 --- a/common/common.vcxproj +++ b/common/common.vcxproj @@ -151,7 +151,6 @@ - @@ -178,7 +177,6 @@ - diff --git a/common/common.vcxproj.filters b/common/common.vcxproj.filters index 09bd9e6830..7d77cee78a 100644 --- a/common/common.vcxproj.filters +++ b/common/common.vcxproj.filters @@ -285,9 +285,6 @@ Header Files - - Header Files - Header Files @@ -324,9 +321,6 @@ Header Files - - Header Files - Header Files @@ -514,4 +508,4 @@ Source Files - + \ No newline at end of file diff --git a/pcsx2/CDVD/ThreadedFileReader.cpp b/pcsx2/CDVD/ThreadedFileReader.cpp index c92b75659d..27a588c219 100644 --- a/pcsx2/CDVD/ThreadedFileReader.cpp +++ b/pcsx2/CDVD/ThreadedFileReader.cpp @@ -16,6 +16,8 @@ #include "PrecompiledHeader.h" #include "ThreadedFileReader.h" +#include "common/Threading.h" + // Make sure buffer size is bigger than the cutoff where PCSX2 emulates a seek // If buffers are smaller than that, we can't keep up with linear reads static constexpr u32 MINIMUM_SIZE = 128 * 1024; diff --git a/pcsx2/CDVD/ThreadedFileReader.h b/pcsx2/CDVD/ThreadedFileReader.h index d0dc356e8d..b45771beac 100644 --- a/pcsx2/CDVD/ThreadedFileReader.h +++ b/pcsx2/CDVD/ThreadedFileReader.h @@ -16,7 +16,6 @@ #pragma once #include "AsyncFileReader.h" -#include "common/PersistentThread.h" #include #include diff --git a/pcsx2/CMakeLists.txt b/pcsx2/CMakeLists.txt index 73a963620d..e7d858e0f6 100644 --- a/pcsx2/CMakeLists.txt +++ b/pcsx2/CMakeLists.txt @@ -1133,6 +1133,7 @@ set(pcsx2GuiSources gui/Panels/PathsPanel.cpp gui/Panels/SpeedhacksPanel.cpp gui/Panels/VideoPanel.cpp + gui/PersistentThread.cpp gui/pxCheckBox.cpp gui/pxRadioPanel.cpp gui/pxStaticText.cpp @@ -1186,6 +1187,7 @@ set(pcsx2GuiHeaders gui/Panels/ConfigurationPanels.h gui/Panels/LogOptionsPanels.h gui/Panels/MemoryCardPanels.h + gui/PersistentThread.h gui/pxCheckBox.h gui/pxEvents.h gui/pxEventThread.h @@ -1193,6 +1195,7 @@ set(pcsx2GuiHeaders gui/pxStaticText.h gui/RecentIsoList.h gui/Saveslots.h + gui/ScopedPtrMT.h gui/SysThreads.h gui/ThreadingDialogs.h gui/ThreadingDialogs.cpp diff --git a/pcsx2/GS/Renderers/SW/GSRasterizer.cpp b/pcsx2/GS/Renderers/SW/GSRasterizer.cpp index 2ccc59b2a9..cbc016278c 100644 --- a/pcsx2/GS/Renderers/SW/GSRasterizer.cpp +++ b/pcsx2/GS/Renderers/SW/GSRasterizer.cpp @@ -20,7 +20,6 @@ #include "GS/GSExtra.h" #include "PerformanceMetrics.h" #include "common/StringUtil.h" -#include "common/PersistentThread.h" #define ENABLE_DRAW_STATS 0 diff --git a/pcsx2/PINE.h b/pcsx2/PINE.h index f23cdf78bf..2cf61dc401 100644 --- a/pcsx2/PINE.h +++ b/pcsx2/PINE.h @@ -27,7 +27,7 @@ #define PINE_DEFAULT_SLOT 28011 #define PINE_EMULATOR_NAME "pcsx2" -#include "common/PersistentThread.h" +#include "gui/PersistentThread.h" #include "gui/SysThreads.h" #include #ifdef _WIN32 diff --git a/pcsx2/gui/AppCommon.h b/pcsx2/gui/AppCommon.h index fc9e74b4ac..cac45bbe09 100644 --- a/pcsx2/gui/AppCommon.h +++ b/pcsx2/gui/AppCommon.h @@ -17,9 +17,9 @@ #include "common/SafeArray.h" #include "common/EventSource.h" -#include "common/PersistentThread.h" #include "gui/wxGuiTools.h" +#include "gui/PersistentThread.h" #include "gui/pxRadioPanel.h" #include "gui/pxCheckBox.h" #include "gui/pxStaticText.h" diff --git a/pcsx2/gui/Debugger/CtrlMemSearch.h b/pcsx2/gui/Debugger/CtrlMemSearch.h index 909dd2fb22..95292f94a4 100644 --- a/pcsx2/gui/Debugger/CtrlMemSearch.h +++ b/pcsx2/gui/Debugger/CtrlMemSearch.h @@ -15,9 +15,9 @@ #pragma once #include -#include "common/PersistentThread.h" #include "DebugTools/DebugInterface.h" #include "DebugTools/DisassemblyManager.h" +#include "gui/PersistentThread.h" enum class SEARCHTYPE { diff --git a/pcsx2/gui/PersistentThread.cpp b/pcsx2/gui/PersistentThread.cpp new file mode 100644 index 0000000000..f24ee9dd16 --- /dev/null +++ b/pcsx2/gui/PersistentThread.cpp @@ -0,0 +1,1175 @@ +/* PCSX2 - PS2 Emulator for PCs + * Copyright (C) 2002-2010 PCSX2 Dev Team + * + * PCSX2 is free software: you can redistribute it and/or modify it under the terms + * of the GNU Lesser General Public License as published by the Free Software Found- + * ation, either version 3 of the License, or (at your option) any later version. + * + * PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with PCSX2. + * If not, see . + */ + +#ifdef __linux__ +#include // for pthread_kill, which is in pthread.h on w32-pthreads +#endif + +#if defined(__APPLE__) +#include +#include // semaphore_create() and semaphore_destroy() +#include // semaphore_*() +#include // mach_error_string() +#include // mach_absolute_time() +#include +#endif + +#include "PersistentThread.h" +#include "common/EventSource.inl" +#include "common/General.h" +#include + +using namespace Threading; + +template class EventSource; + +// 100ms interval for waitgui (issued from blocking semaphore waits on the main thread, +// to avoid gui deadlock). +const wxTimeSpan Threading::def_yieldgui_interval(0, 0, 0, 100); + +ConsoleLogSource_Threading::ConsoleLogSource_Threading() +{ + static const TraceLogDescriptor myDesc = + { + L"p&xThread", L"pxThread", + pxLt("Threading activity: start, detach, sync, deletion, etc.")}; + + m_Descriptor = &myDesc; +} + +ConsoleLogSource_Threading pxConLog_Thread; + + +class StaticMutex : public Mutex +{ +protected: + bool& m_DeletedFlag; + +public: + StaticMutex(bool& deletedFlag) + : m_DeletedFlag(deletedFlag) + { + } + + virtual ~StaticMutex() + { + m_DeletedFlag = true; + } +}; + +static pthread_key_t curthread_key = 0; +static s32 total_key_count = 0; + +static bool tkl_destructed = false; +static StaticMutex total_key_lock(tkl_destructed); + +static void make_curthread_key(const pxThread* thr) +{ + pxAssumeDev(!tkl_destructed, "total_key_lock is destroyed; program is shutting down; cannot create new thread key."); + + ScopedLock lock(total_key_lock); + if (total_key_count++ != 0) + return; + + if (0 != pthread_key_create(&curthread_key, NULL)) + { + pxThreadLog.Error(thr->GetName(), L"Thread key creation failed (probably out of memory >_<)"); + curthread_key = 0; + } +} + +static void unmake_curthread_key() +{ + ScopedLock lock; + if (!tkl_destructed) + lock.AssignAndLock(total_key_lock); + + if (--total_key_count > 0) + return; + + if (curthread_key) + pthread_key_delete(curthread_key); + + curthread_key = 0; +} + +void Threading::pxTestCancel() +{ + pthread_testcancel(); +} + +// Returns a handle to the current persistent thread. If the current thread does not belong +// to the pxThread table, NULL is returned. Since the main/ui thread is not created +// through pxThread it will also return NULL. Callers can use wxThread::IsMain() to +// test if the NULL thread is the main thread. +pxThread* Threading::pxGetCurrentThread() +{ + return !curthread_key ? NULL : (pxThread*)pthread_getspecific(curthread_key); +} + +// returns the name of the current thread, or "Unknown" if the thread is neither a pxThread +// nor the Main/UI thread. +wxString Threading::pxGetCurrentThreadName() +{ + if (pxThread* thr = pxGetCurrentThread()) + { + return thr->GetName(); + } + else if (wxThread::IsMain()) + { + return L"Main/UI"; + } + + return L"Unknown"; +} + +// (intended for internal use only) +// Returns true if the Wait is recursive, or false if the Wait is safe and should be +// handled via normal yielding methods. +bool Threading::_WaitGui_RecursionGuard(const wxChar* name) +{ + AffinityAssert_AllowFrom_MainUI(); + + // In order to avoid deadlock we need to make sure we cut some time to handle messages. + // But this can result in recursive yield calls, which would crash the app. Protect + // against them here and, if recursion is detected, perform a standard blocking wait. + + static int __Guard = 0; + RecursionGuard guard(__Guard); + + //if( pxAssertDev( !guard.IsReentrant(), "Recursion during UI-bound threading wait object." ) ) return false; + + if (!guard.IsReentrant()) + return false; + pxThreadLog.Write(pxGetCurrentThreadName(), + pxsFmt(L"Yield recursion in %s; opening modal dialog.", name)); + return true; +} + +void Threading::pxThread::_pt_callback_cleanup(void* handle) +{ + ((pxThread*)handle)->_ThreadCleanup(); +} + +Threading::pxThread::pxThread(const wxString& name) + : m_name(name) + , m_thread() + , m_detached(true) // start out with m_thread in detached/invalid state + , m_running(false) +{ +} + +// This destructor performs basic "last chance" cleanup, which is a blocking join +// against the thread. Extending classes should almost always implement their own +// thread closure process, since any pxThread will, by design, not terminate +// unless it has been properly canceled (resulting in deadlock). +// +// Thread safety: This class must not be deleted from its own thread. That would be +// like marrying your sister, and then cheating on her with your daughter. +Threading::pxThread::~pxThread() +{ + try + { + pxThreadLog.Write(GetName(), L"Executing default destructor!"); + + if (m_running) + { + pxThreadLog.Write(GetName(), L"Waiting for running thread to end..."); + m_mtx_InThread.Wait(); + pxThreadLog.Write(GetName(), L"Thread ended gracefully."); + } + Threading::Sleep(1); + Detach(); + } + DESTRUCTOR_CATCHALL +} + +bool Threading::pxThread::AffinityAssert_AllowFromSelf(const DiagnosticOrigin& origin) const +{ + if (IsSelf()) + return true; + + if (IsDevBuild) + pxOnAssert(origin, pxsFmt(L"Thread affinity violation: Call allowed from '%s' thread only.", WX_STR(GetName()))); + + return false; +} + +bool Threading::pxThread::AffinityAssert_DisallowFromSelf(const DiagnosticOrigin& origin) const +{ + if (!IsSelf()) + return true; + + if (IsDevBuild) + pxOnAssert(origin, pxsFmt(L"Thread affinity violation: Call is *not* allowed from '%s' thread.", WX_STR(GetName()))); + + return false; +} + +void Threading::pxThread::FrankenMutex(Mutex& mutex) +{ + if (mutex.RecreateIfLocked()) + { + // Our lock is bupkis, which means the previous thread probably deadlocked. + // Let's create a new mutex lock to replace it. + + pxThreadLog.Error(GetName(), L"Possible deadlock detected on restarted mutex!"); + } +} + +// Main entry point for starting or e-starting a persistent thread. This function performs necessary +// locks and checks for avoiding race conditions, and then calls OnStart() immediately before +// the actual thread creation. Extending classes should generally not override Start(), and should +// instead override DoPrepStart instead. +// +// This function should not be called from the owner thread. +void Threading::pxThread::Start() +{ + // Prevents sudden parallel startup, and or parallel startup + cancel: + ScopedLock startlock(m_mtx_start); + if (m_running) + { + pxThreadLog.Write(GetName(), L"Start() called on running thread; ignorning..."); + return; + } + + Detach(); // clean up previous thread handle, if one exists. + OnStart(); + + m_except = NULL; + + pxThreadLog.Write(GetName(), L"Calling pthread_create..."); + if (pthread_create(&m_thread, NULL, _internal_callback, this) != 0) + throw Exception::ThreadCreationError(this).SetDiagMsg(L"Thread creation error: " + wxString(std::strerror(errno))); + +#ifdef ASAN_WORKAROUND + // Recent Asan + libc6 do pretty bad stuff on the thread init => https://gcc.gnu.org/bugzilla/show_bug.cgi?id=77982 + // + // In our case, the semaphore was posted (counter is 1) but thread is still + // waiting... So waits 100ms and checks the counter value manually + if (!m_sem_startup.WaitWithoutYield(wxTimeSpan(0, 0, 0, 100))) + { + if (m_sem_startup.Count() == 0) + throw Exception::ThreadCreationError(this).SetDiagMsg(L"Thread creation error: %s thread never posted startup semaphore."); + } +#else + if (!m_sem_startup.WaitWithoutYield(wxTimeSpan(0, 0, 3, 0))) + { + RethrowException(); + + // And if the thread threw nothing of its own: + throw Exception::ThreadCreationError(this).SetDiagMsg(L"Thread creation error: %s thread never posted startup semaphore."); + } +#endif + + // Event Rationale (above): Performing this semaphore wait on the created thread is "slow" in the + // sense that it stalls the calling thread completely until the new thread is created + // (which may not always be desirable). But too bad. In order to safely use 'running' locks + // and detachment management, this *has* to be done. By rule, starting new threads shouldn't + // be done very often anyway, hence the concept of Threadpooling for rapidly rotating tasks. + // (and indeed, this semaphore wait might, in fact, be very swift compared to other kernel + // overhead in starting threads). + + // (this could also be done using operating system specific calls, since any threaded OS has + // functions that allow us to see if a thread is running or not, and to block against it even if + // it's been detached -- removing the need for m_mtx_InThread and the semaphore wait above. But + // pthreads kinda lacks that stuff, since pthread_join() has no timeout option making it im- + // possible to safely block against a running thread) +} + +// Returns: TRUE if the detachment was performed, or FALSE if the thread was +// already detached or isn't running at all. +// This function should not be called from the owner thread. +bool Threading::pxThread::Detach() +{ + AffinityAssert_DisallowFromSelf(pxDiagSpot); + + if (m_detached.exchange(true)) + return false; + pthread_detach(m_thread); + return true; +} + +bool Threading::pxThread::_basecancel() +{ + if (!m_running) + return false; + + if (m_detached) + { + pxThreadLog.Warn(GetName(), L"Ignoring attempted cancellation of detached thread."); + return false; + } + + pthread_cancel(m_thread); + return true; +} + +// Remarks: +// Provision of non-blocking Cancel() is probably academic, since destroying a pxThread +// object performs a blocking Cancel regardless of if you explicitly do a non-blocking Cancel() +// prior, since the ExecuteTaskInThread() method requires a valid object state. If you really need +// fire-and-forget behavior on threads, use pthreads directly for now. +// +// This function should not be called from the owner thread. +// +// Parameters: +// isBlocking - indicates if the Cancel action should block for thread completion or not. +// +// Exceptions raised by the blocking thread will be re-thrown into the main thread. If isBlocking +// is false then no exceptions will occur. +// +void Threading::pxThread::Cancel(bool isBlocking) +{ + AffinityAssert_DisallowFromSelf(pxDiagSpot); + + // Prevent simultaneous startup and cancel, necessary to avoid + ScopedLock startlock(m_mtx_start); + + if (!_basecancel()) + return; + + if (isBlocking) + { + WaitOnSelf(m_mtx_InThread); + Detach(); + } +} + +bool Threading::pxThread::Cancel(const wxTimeSpan& timespan) +{ + AffinityAssert_DisallowFromSelf(pxDiagSpot); + + // Prevent simultaneous startup and cancel: + ScopedLock startlock(m_mtx_start); + + if (!_basecancel()) + return true; + + if (!WaitOnSelf(m_mtx_InThread, timespan)) + return false; + Detach(); + return true; +} + + +// Blocks execution of the calling thread until this thread completes its task. The +// caller should make sure to signal the thread to exit, or else blocking may deadlock the +// calling thread. Classes which extend pxThread should override this method +// and signal any necessary thread exit variables prior to blocking. +// +// Returns the return code of the thread. +// This method is roughly the equivalent of pthread_join(). +// +// Exceptions raised by the blocking thread will be re-thrown into the main thread. +// +void Threading::pxThread::Block() +{ + AffinityAssert_DisallowFromSelf(pxDiagSpot); + WaitOnSelf(m_mtx_InThread); +} + +bool Threading::pxThread::Block(const wxTimeSpan& timeout) +{ + AffinityAssert_DisallowFromSelf(pxDiagSpot); + return WaitOnSelf(m_mtx_InThread, timeout); +} + +bool Threading::pxThread::IsSelf() const +{ + // Detached threads may have their pthread handles recycled as newer threads, causing + // false IsSelf reports. + return !m_detached && (pthread_self() == m_thread); +} + +bool Threading::pxThread::IsRunning() const +{ + return m_running; +} + +void Threading::pxThread::AddListener(EventListener_Thread& evt) +{ + evt.SetThread(this); + m_evtsrc_OnDelete.Add(evt); +} + +// Throws an exception if the thread encountered one. Uses the BaseException's Rethrow() method, +// which ensures the exception type remains consistent. Debuggable stacktraces will be lost, since +// the thread will have allowed itself to terminate properly. +void Threading::pxThread::RethrowException() const +{ + // Thread safety note: always detach the m_except pointer. If we checked it for NULL, the + // pointer might still be invalid after detachment, so might as well just detach and check + // after. + + ScopedExcept ptr(const_cast(this)->m_except.DetachPtr()); + if (ptr) + ptr->Rethrow(); +} + +static bool m_BlockDeletions = false; + +bool Threading::AllowDeletions() +{ + AffinityAssert_AllowFrom_MainUI(); + return !m_BlockDeletions; +} + +void Threading::YieldToMain() +{ + m_BlockDeletions = true; + wxTheApp->Yield(true); + m_BlockDeletions = false; +} + +void Threading::pxThread::_selfRunningTest(const wxChar* name) const +{ + if (HasPendingException()) + { + pxThreadLog.Error(GetName(), pxsFmt(L"An exception was thrown while waiting on a %s.", name)); + RethrowException(); + } + + if (!m_running) + { + throw Exception::CancelEvent(pxsFmt( + L"Blocking thread %s was terminated while another thread was waiting on a %s.", + WX_STR(GetName()), name)); + } + + // Thread is still alive and kicking (for now) -- yield to other messages and hope + // that impending chaos does not ensue. [it shouldn't since we block pxThread + // objects from being deleted until outside the scope of a mutex/semaphore wait). + + if ((wxTheApp != NULL) && wxThread::IsMain() && !_WaitGui_RecursionGuard(L"WaitForSelf")) + Threading::YieldToMain(); +} + +// This helper function is a deadlock-safe method of waiting on a semaphore in a pxThread. If the +// thread is terminated or canceled by another thread or a nested action prior to the semaphore being +// posted, this function will detect that and throw a CancelEvent exception is thrown. +// +// Note: Use of this function only applies to semaphores which are posted by the worker thread. Calling +// this function from the context of the thread itself is an error, and a dev assertion will be generated. +// +// Exceptions: +// This function will rethrow exceptions raised by the persistent thread, if it throws an error +// while the calling thread is blocking (which also means the persistent thread has terminated). +// +void Threading::pxThread::WaitOnSelf(Semaphore& sem) const +{ + if (!AffinityAssert_DisallowFromSelf(pxDiagSpot)) + return; + + while (true) + { + if (sem.WaitWithoutYield(wxTimeSpan(0, 0, 0, 333))) + return; + _selfRunningTest(L"semaphore"); + } +} + +// This helper function is a deadlock-safe method of waiting on a mutex in a pxThread. +// If the thread is terminated or canceled by another thread or a nested action prior to the +// mutex being unlocked, this function will detect that and a CancelEvent exception is thrown. +// +// Note: Use of this function only applies to mutexes which are acquired by a worker thread. +// Calling this function from the context of the thread itself is an error, and a dev assertion +// will be generated. +// +// Exceptions: +// This function will rethrow exceptions raised by the persistent thread, if it throws an +// error while the calling thread is blocking (which also means the persistent thread has +// terminated). +// +void Threading::pxThread::WaitOnSelf(Mutex& mutex) const +{ + if (!AffinityAssert_DisallowFromSelf(pxDiagSpot)) + return; + + while (true) + { + if (mutex.WaitWithoutYield(wxTimeSpan(0, 0, 0, 333))) + return; + _selfRunningTest(L"mutex"); + } +} + +static const wxTimeSpan SelfWaitInterval(0, 0, 0, 333); + +bool Threading::pxThread::WaitOnSelf(Semaphore& sem, const wxTimeSpan& timeout) const +{ + if (!AffinityAssert_DisallowFromSelf(pxDiagSpot)) + return true; + + wxTimeSpan runningout(timeout); + + while (runningout.GetMilliseconds() > 0) + { + const wxTimeSpan interval((SelfWaitInterval < runningout) ? SelfWaitInterval : runningout); + if (sem.WaitWithoutYield(interval)) + return true; + _selfRunningTest(L"semaphore"); + runningout -= interval; + } + return false; +} + +bool Threading::pxThread::WaitOnSelf(Mutex& mutex, const wxTimeSpan& timeout) const +{ + if (!AffinityAssert_DisallowFromSelf(pxDiagSpot)) + return true; + + wxTimeSpan runningout(timeout); + + while (runningout.GetMilliseconds() > 0) + { + const wxTimeSpan interval((SelfWaitInterval < runningout) ? SelfWaitInterval : runningout); + if (mutex.WaitWithoutYield(interval)) + return true; + _selfRunningTest(L"mutex"); + runningout -= interval; + } + return false; +} + +// Inserts a thread cancellation point. If the thread has received a cancel request, this +// function will throw an SEH exception designed to exit the thread (so make sure to use C++ +// object encapsulation for anything that could leak resources, to ensure object unwinding +// and cleanup, or use the DoThreadCleanup() override to perform resource cleanup). +void Threading::pxThread::TestCancel() const +{ + AffinityAssert_AllowFromSelf(pxDiagSpot); + pthread_testcancel(); +} + +// Executes the virtual member method +void Threading::pxThread::_try_virtual_invoke(void (pxThread::*method)()) +{ + try + { + (this->*method)(); + } + + // ---------------------------------------------------------------------------- + // Neat repackaging for STL Runtime errors... + // + catch (std::runtime_error& ex) + { + m_except = new Exception::RuntimeError(ex, WX_STR(GetName())); + } + + // ---------------------------------------------------------------------------- + catch (Exception::RuntimeError& ex) + { + BaseException* woot = ex.Clone(); + woot->DiagMsg() += pxsFmt(L"(thread:%s)", WX_STR(GetName())); + m_except = woot; + } +#ifndef PCSX2_DEVBUILD + // ---------------------------------------------------------------------------- + // Bleh... don't bother with std::exception. runtime_error should catch anything + // useful coming out of the core STL libraries anyway, and these are best handled by + // the MSVC debugger (or by silent random annoying fail on debug-less linux). + /*catch( std::logic_error& ex ) + { + throw BaseException( pxsFmt( L"STL Logic Error (thread:%s): %s", + WX_STR(GetName()), WX_STR(fromUTF8( ex.what() )) ) + ); + } + catch( std::exception& ex ) + { + throw BaseException( pxsFmt( L"STL exception (thread:%s): %s", + WX_STR(GetName()), WX_STR(fromUTF8( ex.what() )) ) + ); + }*/ + // ---------------------------------------------------------------------------- + // BaseException -- same deal as LogicErrors. + // + catch (BaseException& ex) + { + BaseException* woot = ex.Clone(); + woot->DiagMsg() += pxsFmt(L"(thread:%s)", WX_STR(GetName())); + m_except = woot; + } +#endif +} + +// invoked internally when canceling or exiting the thread. Extending classes should implement +// OnCleanupInThread() to extend cleanup functionality. +void Threading::pxThread::_ThreadCleanup() +{ + AffinityAssert_AllowFromSelf(pxDiagSpot); + _try_virtual_invoke(&pxThread::OnCleanupInThread); + m_mtx_InThread.Release(); + + // Must set m_running LAST, as thread destructors depend on this value (it is used + // to avoid destruction of the thread until all internal data use has stopped. + m_running = false; +} + +wxString Threading::pxThread::GetName() const +{ + ScopedLock lock(m_mtx_ThreadName); + return m_name; +} + +void Threading::pxThread::SetName(const wxString& newname) +{ + ScopedLock lock(m_mtx_ThreadName); + m_name = newname; +} + +// This override is called by PeristentThread when the thread is first created, prior to +// calling ExecuteTaskInThread, and after the initial InThread lock has been claimed. +// This code is also executed within a "safe" environment, where the creating thread is +// blocked against m_sem_event. Make sure to do any necessary variable setup here, without +// worry that the calling thread might attempt to test the status of those variables +// before initialization has completed. +// +void Threading::pxThread::OnStartInThread() +{ + m_detached = false; + m_running = true; +} + +void Threading::pxThread::_internal_execute() +{ + m_mtx_InThread.Acquire(); + + _DoSetThreadName(GetName()); + make_curthread_key(this); + if (curthread_key) + pthread_setspecific(curthread_key, this); + + OnStartInThread(); + m_sem_startup.Post(); + + _try_virtual_invoke(&pxThread::ExecuteTaskInThread); +} + +// Called by Start, prior to actual starting of the thread, and after any previous +// running thread has been canceled or detached. +void Threading::pxThread::OnStart() +{ + FrankenMutex(m_mtx_InThread); + m_sem_event.Reset(); + m_sem_startup.Reset(); +} + +// Extending classes that override this method should always call it last from their +// personal implementations. +void Threading::pxThread::OnCleanupInThread() +{ + if (curthread_key) + pthread_setspecific(curthread_key, NULL); + + unmake_curthread_key(); + + m_evtsrc_OnDelete.Dispatch(0); +} + +// passed into pthread_create, and is used to dispatch the thread's object oriented +// callback function +void* Threading::pxThread::_internal_callback(void* itsme) +{ + if (!pxAssertDev(itsme != NULL, wxNullChar)) + return NULL; + + internal_callback_helper(itsme); + return nullptr; +} + +// __try is used in pthread_cleanup_push when CLEANUP_SEH is used as the cleanup model. +// That can't be used in a function that has objects that require unwinding (compile +// error C2712), so move it into a separate function. +void Threading::pxThread::internal_callback_helper(void* itsme) +{ + pxThread& owner = *static_cast(itsme); + + pthread_cleanup_push(_pt_callback_cleanup, itsme); + owner._internal_execute(); + pthread_cleanup_pop(true); +} + +void Threading::pxThread::_DoSetThreadName(const wxString& name) +{ + _DoSetThreadName(static_cast(name.ToUTF8())); +} + +// -------------------------------------------------------------------------------------- +// pthread Cond is an evil api that is not suited for Pcsx2 needs. +// Let's not use it. (Air) +// -------------------------------------------------------------------------------------- + +#if 0 +Threading::WaitEvent::WaitEvent() +{ + int err = 0; + + err = pthread_cond_init(&cond, NULL); + err = pthread_mutex_init(&mutex, NULL); +} + +Threading::WaitEvent::~WaitEvent() +{ + pthread_cond_destroy( &cond ); + pthread_mutex_destroy( &mutex ); +} + +void Threading::WaitEvent::Set() +{ + pthread_mutex_lock( &mutex ); + pthread_cond_signal( &cond ); + pthread_mutex_unlock( &mutex ); +} + +void Threading::WaitEvent::Wait() +{ + pthread_mutex_lock( &mutex ); + pthread_cond_wait( &cond, &mutex ); + pthread_mutex_unlock( &mutex ); +} +#endif + +// -------------------------------------------------------------------------------------- +// BaseThreadError +// -------------------------------------------------------------------------------------- + +wxString Exception::BaseThreadError::FormatDiagnosticMessage() const +{ + wxString null_str(L"Null Thread Object"); + return pxsFmt(m_message_diag, (m_thread == NULL) ? WX_STR(null_str) : WX_STR(m_thread->GetName())); +} + +wxString Exception::BaseThreadError::FormatDisplayMessage() const +{ + wxString null_str(L"Null Thread Object"); + return pxsFmt(m_message_user, (m_thread == NULL) ? WX_STR(null_str) : WX_STR(m_thread->GetName())); +} + +pxThread& Exception::BaseThreadError::Thread() +{ + pxAssertDev(m_thread != NULL, "NULL thread object on ThreadError exception."); + return *m_thread; +} +const pxThread& Exception::BaseThreadError::Thread() const +{ + pxAssertDev(m_thread != NULL, "NULL thread object on ThreadError exception."); + return *m_thread; +} + +#ifndef __APPLE__ + +Threading::Semaphore::Semaphore() +{ + sem_init(&m_sema, false, 0); +} + +Threading::Semaphore::~Semaphore() +{ + sem_destroy(&m_sema); +} + +void Threading::Semaphore::Reset() +{ + sem_destroy(&m_sema); + sem_init(&m_sema, false, 0); +} + +void Threading::Semaphore::Post() +{ + sem_post(&m_sema); +} + +void Threading::Semaphore::Post(int multiple) +{ +#if defined(_MSC_VER) + sem_post_multiple(&m_sema, multiple); +#else + // Only w32pthreads has the post_multiple, but it's easy enough to fake: + while (multiple > 0) + { + multiple--; + sem_post(&m_sema); + } +#endif +} + +void Threading::Semaphore::WaitWithoutYield() +{ + pxAssertMsg(!wxThread::IsMain(), "Unyielding semaphore wait issued from the main/gui thread. Please use Wait() instead."); + sem_wait(&m_sema); +} + +bool Threading::Semaphore::WaitWithoutYield(const wxTimeSpan& timeout) +{ + wxDateTime megafail(wxDateTime::UNow() + timeout); + const timespec fail = {megafail.GetTicks(), megafail.GetMillisecond() * 1000000}; + return sem_timedwait(&m_sema, &fail) == 0; +} + + +// This is a wxApp-safe implementation of Wait, which makes sure and executes the App's +// pending messages *if* the Wait is performed on the Main/GUI thread. This ensures that +// user input continues to be handled and that windoes continue to repaint. If the Wait is +// called from another thread, no message pumping is performed. +// +void Threading::Semaphore::Wait() +{ +#if wxUSE_GUI + if (!wxThread::IsMain() || (wxTheApp == NULL)) + { + sem_wait(&m_sema); + } + else if (_WaitGui_RecursionGuard(L"Semaphore::Wait")) + { + sem_wait(&m_sema); + } + else + { + //ScopedBusyCursor hourglass( Cursor_KindaBusy ); + while (!WaitWithoutYield(def_yieldgui_interval)) + YieldToMain(); + } +#else + sem_wait(&m_sema); +#endif +} + +// This is a wxApp-safe implementation of WaitWithoutYield, which makes sure and executes the App's +// pending messages *if* the Wait is performed on the Main/GUI thread. This ensures that +// user input continues to be handled and that windows continue to repaint. If the Wait is +// called from another thread, no message pumping is performed. +// +// Returns: +// false if the wait timed out before the semaphore was signaled, or true if the signal was +// reached prior to timeout. +// +bool Threading::Semaphore::Wait(const wxTimeSpan& timeout) +{ +#if wxUSE_GUI + if (!wxThread::IsMain() || (wxTheApp == NULL)) + { + return WaitWithoutYield(timeout); + } + else if (_WaitGui_RecursionGuard(L"Semaphore::TimedWait")) + { + return WaitWithoutYield(timeout); + } + else + { + //ScopedBusyCursor hourglass( Cursor_KindaBusy ); + wxTimeSpan countdown((timeout)); + + do + { + if (WaitWithoutYield(def_yieldgui_interval)) + break; + YieldToMain(); + countdown -= def_yieldgui_interval; + } while (countdown.GetMilliseconds() > 0); + + return countdown.GetMilliseconds() > 0; + } +#else + return WaitWithoutYield(timeout); +#endif +} + +// Performs an uncancellable wait on a semaphore; restoring the thread's previous cancel state +// after the wait has completed. Useful for situations where the semaphore itself is stored on +// the stack and passed to another thread via GUI message or such, avoiding complications where +// the thread might be canceled and the stack value becomes invalid. +// +// Performance note: this function has quite a bit more overhead compared to Semaphore::WaitWithoutYield(), so +// consider manually specifying the thread as uncancellable and using WaitWithoutYield() instead if you need +// to do a lot of no-cancel waits in a tight loop worker thread, for example. +void Threading::Semaphore::WaitNoCancel() +{ + int oldstate; + pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldstate); + //WaitWithoutYield(); + Wait(); + pthread_setcancelstate(oldstate, NULL); +} + +void Threading::Semaphore::WaitNoCancel(const wxTimeSpan& timeout) +{ + int oldstate; + pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldstate); + //WaitWithoutYield( timeout ); + Wait(timeout); + pthread_setcancelstate(oldstate, NULL); +} + +bool Threading::Semaphore::TryWait() +{ + return sem_trywait(&m_sema) == 0; +} + +int Threading::Semaphore::Count() +{ + int retval; + sem_getvalue(&m_sema, &retval); + return retval; +} + +#else + +static void MACH_CHECK(kern_return_t mach_retval) +{ + switch (mach_retval) + { + case KERN_SUCCESS: + break; + case KERN_ABORTED: // Awoken due reason unrelated to semaphore (e.g. pthread_cancel) + pthread_testcancel(); // Unlike sem_wait, mach semaphore ops aren't cancellation points + // fallthrough + default: + fprintf(stderr, "mach error: %s", mach_error_string(mach_retval)); + assert(mach_retval == KERN_SUCCESS); + } +} + +/// Wait up to the given time +/// Returns true if successful, false if timed out +static bool WaitUpTo(semaphore_t sema, wxTimeSpan wxtime) +{ + mach_timespec_t time; + u64 ms = wxtime.GetMilliseconds().GetValue(); + time.tv_sec = ms / 1000; + time.tv_nsec = (ms % 1000) * 1000000; + kern_return_t res = semaphore_timedwait(sema, time); + if (res == KERN_OPERATION_TIMED_OUT) + return false; + MACH_CHECK(res); + return true; +} + +Threading::Semaphore::Semaphore() +{ + // other platforms explicitly make a thread-private (unshared) semaphore + // here. But it seems Mach doesn't support that. + MACH_CHECK(semaphore_create(mach_task_self(), (semaphore_t*)&m_sema, SYNC_POLICY_FIFO, 0)); + __atomic_store_n(&m_counter, 0, __ATOMIC_RELEASE); +} + +Threading::Semaphore::~Semaphore() +{ + MACH_CHECK(semaphore_destroy(mach_task_self(), (semaphore_t)m_sema)); +} + +void Threading::Semaphore::Reset() +{ + MACH_CHECK(semaphore_destroy(mach_task_self(), (semaphore_t)m_sema)); + MACH_CHECK(semaphore_create(mach_task_self(), (semaphore_t*)&m_sema, SYNC_POLICY_FIFO, 0)); + __atomic_store_n(&m_counter, 0, __ATOMIC_SEQ_CST); +} + +void Threading::Semaphore::Post() +{ + if (__atomic_fetch_add(&m_counter, 1, __ATOMIC_RELEASE) < 0) + MACH_CHECK(semaphore_signal(m_sema)); +} + +void Threading::Semaphore::Post(int multiple) +{ + for (int i = 0; i < multiple; ++i) + { + Post(); + } +} + +void Threading::Semaphore::WaitWithoutYield() +{ + pxAssertMsg(!wxThread::IsMain(), "Unyielding semaphore wait issued from the main/gui thread. Please use Wait() instead."); + if (__atomic_sub_fetch(&m_counter, 1, __ATOMIC_ACQUIRE) < 0) + MACH_CHECK(semaphore_wait(m_sema)); +} + +bool Threading::Semaphore::WaitWithoutYield(const wxTimeSpan& timeout) +{ + // This method is the reason why there has to be a special Darwin + // implementation of Semaphore. Note that semaphore_timedwait() is prone + // to returning with KERN_ABORTED, which basically signifies that some + // signal has worken it up. The best official "documentation" for + // semaphore_timedwait() is the way it's used in Grand Central Dispatch, + // which is open-source. + + if (__atomic_sub_fetch(&m_counter, 1, __ATOMIC_ACQUIRE) >= 0) + return true; + + // on x86 platforms, mach_absolute_time() returns nanoseconds + // TODO(aktau): on iOS a scale value from mach_timebase_info will be necessary + u64 const kOneThousand = 1000; + u64 const kOneBillion = kOneThousand * kOneThousand * kOneThousand; + u64 const delta = timeout.GetMilliseconds().GetValue() * (kOneThousand * kOneThousand); + mach_timespec_t ts; + kern_return_t kr = KERN_ABORTED; + for (u64 now = mach_absolute_time(), deadline = now + delta; + kr == KERN_ABORTED; now = mach_absolute_time()) + { + if (now > deadline) + { + // timed out by definition + kr = KERN_OPERATION_TIMED_OUT; + break; + } + + u64 timeleft = deadline - now; + ts.tv_sec = timeleft / kOneBillion; + ts.tv_nsec = timeleft % kOneBillion; + + // possible return values of semaphore_timedwait() (from XNU sources): + // internal kernel val -> return value + // THREAD_INTERRUPTED -> KERN_ABORTED + // THREAD_TIMED_OUT -> KERN_OPERATION_TIMED_OUT + // THREAD_AWAKENED -> KERN_SUCCESS + // THREAD_RESTART -> KERN_TERMINATED + // default -> KERN_FAILURE + kr = semaphore_timedwait(m_sema, ts); + } + + if (kr == KERN_OPERATION_TIMED_OUT) + { + int orig = __atomic_load_n(&m_counter, __ATOMIC_RELAXED); + while (orig < 0) + { + if (__atomic_compare_exchange_n(&m_counter, &orig, orig + 1, true, __ATOMIC_ACQUIRE, __ATOMIC_RELAXED)) + return false; + } + // Semaphore was signalled between our wait expiring and now, keep kernel sema in sync + kr = semaphore_wait(m_sema); + } + + // while it's entirely possible to have KERN_FAILURE here, we should + // probably assert so we can study and correct the actual error here + // (the thread dying while someone is wainting for it). + MACH_CHECK(kr); + return true; +} + +// This is a wxApp-safe implementation of Wait, which makes sure and executes the App's +// pending messages *if* the Wait is performed on the Main/GUI thread. This ensures that +// user input continues to be handled and that windows continue to repaint. If the Wait is +// called from another thread, no message pumping is performed. +void Threading::Semaphore::Wait() +{ +#if wxUSE_GUI + if (!wxThread::IsMain() || (wxTheApp == NULL)) + { + WaitWithoutYield(); + } + else if (_WaitGui_RecursionGuard(L"Semaphore::Wait")) + { + WaitWithoutYield(); + } + else + { + if (__atomic_sub_fetch(&m_counter, 1, __ATOMIC_ACQUIRE) >= 0) + return; + while (!WaitUpTo(m_sema, def_yieldgui_interval)) + { + YieldToMain(); + } + } +#else + WaitWithoutYield(); +#endif +} + +// This is a wxApp-safe implementation of WaitWithoutYield, which makes sure and executes the App's +// pending messages *if* the Wait is performed on the Main/GUI thread. This ensures that +// user input continues to be handled and that windows continue to repaint. If the Wait is +// called from another thread, no message pumping is performed. +// +// Returns: +// false if the wait timed out before the semaphore was signaled, or true if the signal was +// reached prior to timeout. +// +bool Threading::Semaphore::Wait(const wxTimeSpan& timeout) +{ +#if wxUSE_GUI + if (!wxThread::IsMain() || (wxTheApp == NULL)) + { + return WaitWithoutYield(timeout); + } + else if (_WaitGui_RecursionGuard(L"Semaphore::TimedWait")) + { + return WaitWithoutYield(timeout); + } + else + { + wxTimeSpan countdown((timeout)); + + do + { + if (WaitWithoutYield(def_yieldgui_interval)) + break; + YieldToMain(); + countdown -= def_yieldgui_interval; + } while (countdown.GetMilliseconds() > 0); + + return countdown.GetMilliseconds() > 0; + } +#else + return WaitWithoutYield(timeout); +#endif +} + +bool Threading::Semaphore::TryWait() +{ + int counter = __atomic_load_n(&m_counter, __ATOMIC_RELAXED); + while (counter > 0 && !__atomic_compare_exchange_n(&m_counter, &counter, counter - 1, true, __ATOMIC_ACQUIRE, __ATOMIC_RELAXED)) + ; + return counter > 0; +} + +// Performs an uncancellable wait on a semaphore; restoring the thread's previous cancel state +// after the wait has completed. Useful for situations where the semaphore itself is stored on +// the stack and passed to another thread via GUI message or such, avoiding complications where +// the thread might be canceled and the stack value becomes invalid. +// +// Performance note: this function has quite a bit more overhead compared to Semaphore::WaitWithoutYield(), so +// consider manually specifying the thread as uncancellable and using WaitWithoutYield() instead if you need +// to do a lot of no-cancel waits in a tight loop worker thread, for example. +// +// I'm unsure how to do this with pure Mach primitives, the docs in +// osfmk/man seem a bit out of date so perhaps there's a possibility, but +// since as far as I know Mach threads are 1-to-1 on BSD uthreads (and thus +// POSIX threads), this should work. -- aktau +void Threading::Semaphore::WaitNoCancel() +{ + int oldstate; + pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldstate); + Wait(); + pthread_setcancelstate(oldstate, NULL); +} + +void Threading::Semaphore::WaitNoCancel(const wxTimeSpan& timeout) +{ + int oldstate; + pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldstate); + Wait(timeout); + pthread_setcancelstate(oldstate, NULL); +} + +int Threading::Semaphore::Count() +{ + return __atomic_load_n(&m_counter, __ATOMIC_RELAXED); +} + +#endif diff --git a/common/PersistentThread.h b/pcsx2/gui/PersistentThread.h similarity index 64% rename from common/PersistentThread.h rename to pcsx2/gui/PersistentThread.h index ffad6882b6..7e02a13900 100644 --- a/common/PersistentThread.h +++ b/pcsx2/gui/PersistentThread.h @@ -16,11 +16,137 @@ #pragma once #include "common/Threading.h" -#include "common/ScopedPtrMT.h" #include "common/EventSource.h" +#include "ScopedPtrMT.h" + +#undef Yield // release the burden of windows.h global namespace spam. + +#define AffinityAssert_AllowFrom_MainUI() \ + pxAssertMsg(wxThread::IsMain(), "Thread affinity violation: Call allowed from main thread only.") + +// -------------------------------------------------------------------------------------- +// pxThreadLog / ConsoleLogSource_Threading +// -------------------------------------------------------------------------------------- + +class ConsoleLogSource_Threading : ConsoleLogSource +{ + typedef ConsoleLogSource _parent; + +public: + using _parent::IsActive; + + ConsoleLogSource_Threading(); + + bool Write(const wxString& thrname, const wxChar* msg) + { + return _parent::Write(wxsFormat(L"(thread:%s) ", WX_STR(thrname)) + msg); + } + bool Warn(const wxString& thrname, const wxChar* msg) + { + return _parent::Warn(wxsFormat(L"(thread:%s) ", WX_STR(thrname)) + msg); + } + bool Error(const wxString& thrname, const wxChar* msg) + { + return _parent::Error(wxsFormat(L"(thread:%s) ", WX_STR(thrname)) + msg); + } +}; + +extern ConsoleLogSource_Threading pxConLog_Thread; + +#define pxThreadLog pxConLog_Thread.IsActive() && pxConLog_Thread + + +namespace Exception +{ + class BaseThreadError : public RuntimeError + { + DEFINE_EXCEPTION_COPYTORS(BaseThreadError, RuntimeError) + DEFINE_EXCEPTION_MESSAGES(BaseThreadError) + + public: + Threading::pxThread* m_thread; + + protected: + BaseThreadError() + { + m_thread = NULL; + } + + public: + explicit BaseThreadError(Threading::pxThread* _thread) + { + m_thread = _thread; + m_message_diag = L"An unspecified thread-related error occurred (thread=%s)"; + } + + explicit BaseThreadError(Threading::pxThread& _thread) + { + m_thread = &_thread; + m_message_diag = L"An unspecified thread-related error occurred (thread=%s)"; + } + + virtual wxString FormatDiagnosticMessage() const; + virtual wxString FormatDisplayMessage() const; + + Threading::pxThread& Thread(); + const Threading::pxThread& Thread() const; + }; + + class ThreadCreationError : public BaseThreadError + { + DEFINE_EXCEPTION_COPYTORS(ThreadCreationError, BaseThreadError) + + public: + explicit ThreadCreationError(Threading::pxThread* _thread) + { + m_thread = _thread; + SetBothMsgs(L"Thread creation failure. An unspecified error occurred while trying to create the %s thread."); + } + + explicit ThreadCreationError(Threading::pxThread& _thread) + { + m_thread = &_thread; + SetBothMsgs(L"Thread creation failure. An unspecified error occurred while trying to create the %s thread."); + } + }; +} // namespace Exception namespace Threading { + extern pxThread* pxGetCurrentThread(); + extern wxString pxGetCurrentThreadName(); + extern bool _WaitGui_RecursionGuard(const wxChar* name); + + extern bool AllowDeletions(); + + // ---------------------------------------------------------------------------------------- + // RecursionGuard - Basic protection against function recursion + // ---------------------------------------------------------------------------------------- + // Thread safety note: If used in a threaded environment, you shoud use a handle to a __threadlocal + // storage variable (protects aaginst race conditions and, in *most* cases, is more desirable + // behavior as well. + // + // Rationale: wxWidgets has its own wxRecursionGuard, but it has a sloppy implementation with + // entirely unnecessary assertion checks. + // + class RecursionGuard + { + public: + int& Counter; + + RecursionGuard(int& counter) + : Counter(counter) + { + ++Counter; + } + + virtual ~RecursionGuard() + { + --Counter; + } + + bool IsReentrant() const { return Counter > 1; } + }; // -------------------------------------------------------------------------------------- // ThreadDeleteEvent @@ -58,6 +184,48 @@ namespace Threading virtual void OnThreadCleanup() = 0; }; + class Semaphore + { + protected: +#ifdef __APPLE__ + semaphore_t m_sema; + int m_counter; +#else + sem_t m_sema; +#endif + + public: + Semaphore(); + virtual ~Semaphore(); + + void Reset(); + void Post(); + void Post(int multiple); + + void WaitWithoutYield(); + bool WaitWithoutYield(const wxTimeSpan& timeout); + void WaitNoCancel(); + void WaitNoCancel(const wxTimeSpan& timeout); + void WaitWithoutYieldWithSpin() + { + u32 waited = 0; + while (true) + { + if (TryWait()) + return; + if (waited >= SPIN_TIME_NS) + break; + waited += ShortSpin(); + } + WaitWithoutYield(); + } + bool TryWait(); + int Count(); + + void Wait(); + bool Wait(const wxTimeSpan& timeout); + }; + // -------------------------------------------------------------------------------------- // pxThread - Helper class for the basics of starting/managing persistent threads. // -------------------------------------------------------------------------------------- @@ -89,13 +257,9 @@ namespace Threading { DeclareNoncopyableObject(pxThread); - friend void pxYield(int ms); - protected: wxString m_name; // diagnostic name for our thread. pthread_t m_thread; - uptr m_native_id; // typically an id, but implementing platforms can do whatever. - uptr m_native_handle; // typically a pointer/handle, but implementing platforms can do whatever. WorkSema m_sem_event; // general wait event that's needed by most threads Semaphore m_sem_startup; // startup sync tool @@ -118,7 +282,6 @@ namespace Threading pxThread(const wxString& name = L"pxThread"); pthread_t GetId() const { return m_thread; } - u64 GetCpuTime() const; virtual void Start(); virtual void Cancel(bool isBlocking = true); @@ -189,8 +352,6 @@ namespace Threading // ---------------------------------------------------------------------------- // Section of methods for internal use only. - void _platform_specific_OnStartInThread(); - void _platform_specific_OnCleanupInThread(); bool _basecancel(); void _selfRunningTest(const wxChar* name) const; void _DoSetThreadName(const wxString& name); diff --git a/common/ScopedPtrMT.h b/pcsx2/gui/ScopedPtrMT.h similarity index 99% rename from common/ScopedPtrMT.h rename to pcsx2/gui/ScopedPtrMT.h index e8ed6f4e82..3402743eb6 100644 --- a/common/ScopedPtrMT.h +++ b/pcsx2/gui/ScopedPtrMT.h @@ -15,7 +15,7 @@ #pragma once -#include "Threading.h" +#include "common/Threading.h" using Threading::ScopedLock; // -------------------------------------------------------------------------------------- diff --git a/pcsx2/gui/SysCoreThread.cpp b/pcsx2/gui/SysCoreThread.cpp index c42d4d8ba3..7a9d4fbbec 100644 --- a/pcsx2/gui/SysCoreThread.cpp +++ b/pcsx2/gui/SysCoreThread.cpp @@ -197,12 +197,9 @@ void SysCoreThread::ApplySettings(const Pcsx2Config& src) // handle GS setting changes if (GetMTGS().IsOpen() && gs_settings_changed) { - // if by change we reopen the GS, the window handles will invalidate. - // so, we should block here until GS has finished reinitializing, if needed. Console.WriteLn("Applying GS settings..."); GetMTGS().ApplySettings(); GetMTGS().SetVSync(EmuConfig.GetEffectiveVsyncMode()); - GetMTGS().WaitGS(); } } @@ -340,6 +337,9 @@ void SysCoreThread::TearDownSystems(SystemsMask systemsToTearDown) if (systemsToTearDown & System_SPU2) SPU2close(); if (systemsToTearDown & System_MCD) FileMcd_EmuClose(); + if (GetMTGS().IsOpen()) + GetMTGS().WaitGS(); + PerformanceMetrics::SetCPUThread(Threading::ThreadHandle()); } @@ -348,7 +348,12 @@ void SysCoreThread::OnResumeInThread(SystemsMask systemsToReinstate) PerformanceMetrics::SetCPUThread(Threading::ThreadHandle::GetForCallingThread()); PerformanceMetrics::Reset(); - GetMTGS().WaitForOpen(); + // if GS is open, wait for it to finish (could be applying settings) + if (GetMTGS().IsOpen()) + GetMTGS().WaitGS(); + else + GetMTGS().WaitForOpen(); + if (systemsToReinstate & System_DEV9) DEV9open(); if (systemsToReinstate & System_USB) USBopen(g_gs_window_info); if (systemsToReinstate & System_FW) FWopen(); diff --git a/pcsx2/gui/SysThreads.h b/pcsx2/gui/SysThreads.h index ba2251c9d2..15922ca3a7 100644 --- a/pcsx2/gui/SysThreads.h +++ b/pcsx2/gui/SysThreads.h @@ -17,8 +17,8 @@ #include "System.h" -#include "common/PersistentThread.h" #include "common/emitter/tools.h" +#include "PersistentThread.h" #include "PINE.h" diff --git a/pcsx2/gui/pxEventThread.h b/pcsx2/gui/pxEventThread.h index 2a36f97604..c88b3e8503 100644 --- a/pcsx2/gui/pxEventThread.h +++ b/pcsx2/gui/pxEventThread.h @@ -15,7 +15,7 @@ #pragma once -#include "common/PersistentThread.h" +#include "gui/PersistentThread.h" #include "gui/pxEvents.h" #include @@ -75,14 +75,14 @@ extern ConsoleLogSource_Event pxConLog_Event; // the other class you want to turn into an event within it. It might feel like more work // but it *will* be less work in the long run. // -class SysExecEvent : public ICloneable +class SysExecEvent { protected: SynchronousActionState* m_sync; public: virtual ~SysExecEvent() = default; - SysExecEvent* Clone() const { return new SysExecEvent( *this ); } + virtual SysExecEvent* Clone() const { return new SysExecEvent( *this ); } SysExecEvent( SynchronousActionState* sync=NULL ) { diff --git a/pcsx2/gui/pxEvents.h b/pcsx2/gui/pxEvents.h index e3b2b3e69e..a145cdb20a 100644 --- a/pcsx2/gui/pxEvents.h +++ b/pcsx2/gui/pxEvents.h @@ -19,6 +19,7 @@ #include "common/General.h" #include "common/Dependencies.h" #include "common/Threading.h" +#include "gui/PersistentThread.h" wxDECLARE_EVENT(pxEvt_StartIdleEventTimer, wxCommandEvent); wxDECLARE_EVENT(pxEvt_DeleteObject, wxCommandEvent); diff --git a/pcsx2/gui/wxAppWithHelpers.cpp b/pcsx2/gui/wxAppWithHelpers.cpp index 682bddd659..6cb970f65c 100644 --- a/pcsx2/gui/wxAppWithHelpers.cpp +++ b/pcsx2/gui/wxAppWithHelpers.cpp @@ -15,8 +15,7 @@ #include "PrecompiledHeader.h" #include "gui/wxAppWithHelpers.h" -#include "common/ThreadingInternal.h" -#include "common/PersistentThread.h" +#include "gui/PersistentThread.h" wxDEFINE_EVENT(pxEvt_StartIdleEventTimer, wxCommandEvent); wxDEFINE_EVENT(pxEvt_DeleteObject, wxCommandEvent); diff --git a/pcsx2/gui/wxAppWithHelpers.h b/pcsx2/gui/wxAppWithHelpers.h index afc71dee41..7e98dc3966 100644 --- a/pcsx2/gui/wxAppWithHelpers.h +++ b/pcsx2/gui/wxAppWithHelpers.h @@ -49,7 +49,7 @@ class pxSynchronousCommandEvent; // (sigh). And, finally, it requires quite a bit of red tape to implement wxObjects because // of the wx-custom runtime type information. So I made my own. // -class BaseDeletableObject : public virtual IDeletableObject +class BaseDeletableObject { protected: std::atomic m_IsBeingDeleted; @@ -58,8 +58,8 @@ public: BaseDeletableObject(); virtual ~BaseDeletableObject(); - void DeleteSelf(); - bool IsBeingDeleted() { return !!m_IsBeingDeleted; } + virtual void DeleteSelf(); + virtual bool IsBeingDeleted() { return !!m_IsBeingDeleted; } // Returns FALSE if the object is already marked for deletion, or TRUE if the app // should schedule the object for deletion. Only schedule if TRUE is returned, otherwise diff --git a/pcsx2/pcsx2.vcxproj b/pcsx2/pcsx2.vcxproj index 80f42d6bf5..10c9916455 100644 --- a/pcsx2/pcsx2.vcxproj +++ b/pcsx2/pcsx2.vcxproj @@ -355,6 +355,7 @@ + @@ -802,6 +803,7 @@ + @@ -816,6 +818,7 @@ + @@ -1224,4 +1227,4 @@ - + \ No newline at end of file diff --git a/pcsx2/pcsx2.vcxproj.filters b/pcsx2/pcsx2.vcxproj.filters index 5d119d02ba..84059ffc80 100644 --- a/pcsx2/pcsx2.vcxproj.filters +++ b/pcsx2/pcsx2.vcxproj.filters @@ -1769,6 +1769,7 @@ System\Ps2\GS\Renderers\Direct3D12 + @@ -2945,6 +2946,8 @@ System\Ps2\GS\Renderers\Direct3D12 + + @@ -3107,4 +3110,4 @@ AppHost\Resources - + \ No newline at end of file