Misc: Move pxThread and friends to gui

This commit is contained in:
Connor McLaughlin 2022-05-05 23:01:14 +10:00 committed by refractionpcsx2
parent 096696bed7
commit 1e8332f36a
32 changed files with 1393 additions and 1647 deletions

View File

@ -88,7 +88,6 @@ target_sources(common PRIVATE
RwMutex.h
SafeArray.h
ScopedGuard.h
ScopedPtrMT.h
SettingsInterface.h
SettingsWrapper.h
StringHelpers.h

View File

@ -26,7 +26,6 @@
#include <mach/mach_time.h> // 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

View File

@ -21,7 +21,7 @@
#include <mach/mach_port.h>
#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)
{

View File

@ -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

View File

@ -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
// --------------------------------------------------------------------------------------

View File

@ -34,7 +34,7 @@
#include <pthread_np.h>
#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__)

View File

@ -14,7 +14,6 @@
*/
#include "common/Threading.h"
#include "common/ThreadingInternal.h"
namespace Threading
{
@ -195,58 +194,14 @@ bool Threading::Mutex::TryAcquire()
// and messages *if* the lock is performed from the main GUI thread.
//
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.
// Typically this action is used to determine if a thread is currently performing some

View File

@ -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

View File

@ -13,777 +13,14 @@
* If not, see <http://www.gnu.org/licenses/>.
*/
#ifdef __linux__
#include <signal.h> // 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<EventListener_Thread>;
// 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<pxThread*>(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<pxThread*>(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<const char*>(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;
}

View File

@ -15,6 +15,7 @@
#pragma once
#include <wx/datetime.h>
#ifdef _WIN32
// thanks I hate it.
#include <wx/filefn.h>
@ -31,43 +32,6 @@
#include "common/General.h"
#include <atomic>
#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<bool>& m_bool;
ScopedLockBool(Mutex& mutexToLock, std::atomic<bool>& 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

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <wx/app.h>
#include <wx/datetime.h>
#include <wx/thread.h>
namespace Threading
{
extern const wxTimeSpan def_yieldgui_interval;
extern bool _WaitGui_RecursionGuard(const wxChar* name);
extern void YieldToMain();
extern bool AllowDeletions();
} // namespace Threading

View File

@ -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:

View File

@ -151,7 +151,6 @@
<ClInclude Include="StringUtil.h" />
<ClInclude Include="SettingsInterface.h" />
<ClInclude Include="SettingsWrapper.h" />
<ClInclude Include="ThreadingInternal.h" />
<ClInclude Include="Assertions.h" />
<ClInclude Include="Console.h" />
<ClInclude Include="Dependencies.h" />
@ -178,7 +177,6 @@
<ClInclude Include="Vulkan\Util.h" />
<ClInclude Include="WindowInfo.h" />
<ClInclude Include="Threading.h" />
<ClInclude Include="PersistentThread.h" />
<ClInclude Include="RwMutex.h" />
<ClInclude Include="emitter\implement\bmi.h" />
<ClInclude Include="emitter\cpudetect_internal.h" />

View File

@ -285,9 +285,6 @@
<ClInclude Include="Path.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="PersistentThread.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="PrecompiledHeader.h">
<Filter>Header Files</Filter>
</ClInclude>
@ -324,9 +321,6 @@
<ClInclude Include="emitter\implement\test.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="ThreadingInternal.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="emitter\tools.h">
<Filter>Header Files</Filter>
</ClInclude>

View File

@ -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;

View File

@ -16,7 +16,6 @@
#pragma once
#include "AsyncFileReader.h"
#include "common/PersistentThread.h"
#include <thread>
#include <mutex>

View File

@ -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

View File

@ -20,7 +20,6 @@
#include "GS/GSExtra.h"
#include "PerformanceMetrics.h"
#include "common/StringUtil.h"
#include "common/PersistentThread.h"
#define ENABLE_DRAW_STATS 0

View File

@ -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 <string>
#ifdef _WIN32

View File

@ -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"

View File

@ -15,9 +15,9 @@
#pragma once
#include <wx/wx.h>
#include "common/PersistentThread.h"
#include "DebugTools/DebugInterface.h"
#include "DebugTools/DisassemblyManager.h"
#include "gui/PersistentThread.h"
enum class SEARCHTYPE
{

File diff suppressed because it is too large Load Diff

View File

@ -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);

View File

@ -15,7 +15,7 @@
#pragma once
#include "Threading.h"
#include "common/Threading.h"
using Threading::ScopedLock;
// --------------------------------------------------------------------------------------

View File

@ -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();
// 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();

View File

@ -17,8 +17,8 @@
#include "System.h"
#include "common/PersistentThread.h"
#include "common/emitter/tools.h"
#include "PersistentThread.h"
#include "PINE.h"

View File

@ -15,7 +15,7 @@
#pragma once
#include "common/PersistentThread.h"
#include "gui/PersistentThread.h"
#include "gui/pxEvents.h"
#include <wx/timer.h>
@ -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 )
{

View File

@ -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);

View File

@ -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);

View File

@ -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<bool> 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

View File

@ -355,6 +355,7 @@
<ClCompile Include="gui\DriveList.cpp" />
<ClCompile Include="gui\IniInterface.cpp" />
<ClCompile Include="gui\Panels\MemoryCardListView.cpp" />
<ClCompile Include="gui\PersistentThread.cpp" />
<ClCompile Include="gui\pxCheckBox.cpp" />
<ClCompile Include="gui\pxRadioPanel.cpp" />
<ClCompile Include="gui\pxStaticText.cpp" />
@ -802,6 +803,7 @@
<ClInclude Include="gui\i18n.h" />
<ClInclude Include="gui\DriveList.h" />
<ClInclude Include="gui\IniInterface.h" />
<ClInclude Include="gui\PersistentThread.h" />
<ClInclude Include="gui\pxCheckBox.h" />
<ClInclude Include="gui\pxEvents.h" />
<ClInclude Include="gui\pxRadioPanel.h" />
@ -816,6 +818,7 @@
<ClInclude Include="gui\Debugger\DebuggerLists.h" />
<ClInclude Include="gui\Debugger\DisassemblyDialog.h" />
<ClInclude Include="gui\Panels\MemoryCardPanels.h" />
<ClInclude Include="gui\ScopedPtrMT.h" />
<ClInclude Include="gui\ThreadingDialogs.h" />
<ClInclude Include="gui\wxAppWithHelpers.h" />
<ClInclude Include="gui\wxSettingsInterface.h" />

View File

@ -1769,6 +1769,7 @@
<ClCompile Include="GS\Renderers\DX12\GSDevice12.cpp">
<Filter>System\Ps2\GS\Renderers\Direct3D12</Filter>
</ClCompile>
<ClCompile Include="gui\PersistentThread.cpp" />
</ItemGroup>
<ItemGroup>
<ClInclude Include="Patch.h">
@ -2945,6 +2946,8 @@
<ClInclude Include="GS\Renderers\DX12\GSTexture12.h">
<Filter>System\Ps2\GS\Renderers\Direct3D12</Filter>
</ClInclude>
<ClInclude Include="gui\ScopedPtrMT.h" />
<ClInclude Include="gui\PersistentThread.h" />
</ItemGroup>
<ItemGroup>
<ResourceCompile Include="windows\wxResources.rc">