mirror of https://github.com/PCSX2/pcsx2.git
391 lines
10 KiB
C++
391 lines
10 KiB
C++
/* 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/>.
|
|
*/
|
|
|
|
#include "common/Threading.h"
|
|
#include "common/wxBaseTools.h"
|
|
#include "common/ThreadingInternal.h"
|
|
|
|
namespace Threading
|
|
{
|
|
static std::atomic<int> _attr_refcount(0);
|
|
static pthread_mutexattr_t _attr_recursive;
|
|
} // namespace Threading
|
|
|
|
// --------------------------------------------------------------------------------------
|
|
// Mutex Implementations
|
|
// --------------------------------------------------------------------------------------
|
|
|
|
#if defined(_WIN32) || (defined(_POSIX_TIMEOUTS) && _POSIX_TIMEOUTS >= 200112L)
|
|
// good, we have pthread_mutex_timedlock
|
|
#define xpthread_mutex_timedlock pthread_mutex_timedlock
|
|
#else
|
|
// We have to emulate pthread_mutex_timedlock(). This could be a serious
|
|
// performance drain if its used a lot.
|
|
|
|
#include <sys/time.h> // gettimeofday()
|
|
|
|
// sleep for 10ms at a time
|
|
#define TIMEDLOCK_EMU_SLEEP_NS 10000000ULL
|
|
|
|
// Original POSIX docs:
|
|
//
|
|
// The pthread_mutex_timedlock() function shall lock the mutex object
|
|
// referenced by mutex. If the mutex is already locked, the calling thread
|
|
// shall block until the mutex becomes available as in the
|
|
// pthread_mutex_lock() function. If the mutex cannot be locked without
|
|
// waiting for another thread to unlock the mutex, this wait shall be
|
|
// terminated when the specified timeout expires.
|
|
//
|
|
// This is an implementation that emulates pthread_mutex_timedlock() via
|
|
// pthread_mutex_trylock().
|
|
static int xpthread_mutex_timedlock(
|
|
pthread_mutex_t* mutex,
|
|
const struct timespec* abs_timeout)
|
|
{
|
|
int err = 0;
|
|
|
|
while ((err = pthread_mutex_trylock(mutex)) == EBUSY)
|
|
{
|
|
// check if the timeout has expired, gettimeofday() is implemented
|
|
// efficiently (in userspace) on OSX
|
|
struct timeval now;
|
|
gettimeofday(&now, NULL);
|
|
if (now.tv_sec > abs_timeout->tv_sec || (now.tv_sec == abs_timeout->tv_sec && (u64)now.tv_usec * 1000ULL > (u64)abs_timeout->tv_nsec))
|
|
{
|
|
return ETIMEDOUT;
|
|
}
|
|
|
|
// acquiring lock failed, sleep some
|
|
struct timespec ts;
|
|
ts.tv_sec = 0;
|
|
ts.tv_nsec = TIMEDLOCK_EMU_SLEEP_NS;
|
|
while (nanosleep(&ts, &ts) == -1)
|
|
;
|
|
}
|
|
|
|
return err;
|
|
}
|
|
#endif
|
|
|
|
Threading::Mutex::Mutex()
|
|
{
|
|
pthread_mutex_init(&m_mutex, NULL);
|
|
}
|
|
|
|
static wxTimeSpan def_detach_timeout(0, 0, 6, 0);
|
|
|
|
void Threading::Mutex::Detach()
|
|
{
|
|
if (EBUSY != pthread_mutex_destroy(&m_mutex))
|
|
return;
|
|
|
|
if (IsRecursive())
|
|
{
|
|
// Sanity check: Recursive locks could be held by our own thread, which would
|
|
// be considered an assertion failure, but can also be handled gracefully.
|
|
// (note: if the mutex is locked recursively more than twice then this assert won't
|
|
// detect it)
|
|
|
|
Release();
|
|
Release(); // in case of double recursion.
|
|
int result = pthread_mutex_destroy(&m_mutex);
|
|
if (pxAssertDev(result != EBUSY, "Detachment of a recursively-locked mutex (self-locked!)."))
|
|
return;
|
|
}
|
|
|
|
if (Wait(def_detach_timeout))
|
|
pthread_mutex_destroy(&m_mutex);
|
|
else
|
|
Console.Error("(Thread Log) Mutex cleanup failed due to possible deadlock.");
|
|
}
|
|
|
|
Threading::Mutex::~Mutex()
|
|
{
|
|
try
|
|
{
|
|
Mutex::Detach();
|
|
}
|
|
DESTRUCTOR_CATCHALL;
|
|
}
|
|
|
|
Threading::MutexRecursive::MutexRecursive()
|
|
: Mutex(false)
|
|
{
|
|
if (++_attr_refcount == 1)
|
|
{
|
|
if (0 != pthread_mutexattr_init(&_attr_recursive))
|
|
throw Exception::OutOfMemory(L"Recursive mutexing attributes");
|
|
|
|
pthread_mutexattr_settype(&_attr_recursive, PTHREAD_MUTEX_RECURSIVE);
|
|
}
|
|
|
|
if (pthread_mutex_init(&m_mutex, &_attr_recursive))
|
|
Console.Error("(Thread Log) Failed to initialize mutex.");
|
|
}
|
|
|
|
Threading::MutexRecursive::~MutexRecursive()
|
|
{
|
|
if (--_attr_refcount == 0)
|
|
pthread_mutexattr_destroy(&_attr_recursive);
|
|
}
|
|
|
|
// This is a bit of a hackish function, which is technically unsafe, but can be useful for allowing
|
|
// the application to survive unexpected or inconvenient failures, where a mutex is deadlocked by
|
|
// a rogue thread. This function allows us to Recreate the mutex and let the deadlocked one ponder
|
|
// the deeper meanings of the universe for eternity.
|
|
void Threading::Mutex::Recreate()
|
|
{
|
|
Detach();
|
|
pthread_mutex_init(&m_mutex, NULL);
|
|
}
|
|
|
|
// Returns:
|
|
// true if the mutex had to be recreated due to lock contention, or false if the mutex is safely
|
|
// unlocked.
|
|
bool Threading::Mutex::RecreateIfLocked()
|
|
{
|
|
if (!Wait(def_detach_timeout))
|
|
{
|
|
Recreate();
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
|
|
// This is a direct blocking action -- very fast, very efficient, and generally very dangerous
|
|
// if used from the main GUI thread, since it typically results in an unresponsive program.
|
|
// Call this method directly only if you know the code in question will be run from threads
|
|
// other than the main thread.
|
|
void Threading::Mutex::AcquireWithoutYield()
|
|
{
|
|
pxAssertMsg(!wxThread::IsMain(), "Unyielding mutex acquire issued from the main/gui thread. Please use Acquire() instead.");
|
|
pthread_mutex_lock(&m_mutex);
|
|
}
|
|
|
|
bool Threading::Mutex::AcquireWithoutYield(const wxTimeSpan& timeout)
|
|
{
|
|
wxDateTime megafail(wxDateTime::UNow() + timeout);
|
|
const timespec fail = {megafail.GetTicks(), megafail.GetMillisecond() * 1000000};
|
|
return xpthread_mutex_timedlock(&m_mutex, &fail) == 0;
|
|
}
|
|
|
|
void Threading::Mutex::Release()
|
|
{
|
|
pthread_mutex_unlock(&m_mutex);
|
|
}
|
|
|
|
bool Threading::Mutex::TryAcquire()
|
|
{
|
|
return EBUSY != pthread_mutex_trylock(&m_mutex);
|
|
}
|
|
|
|
// This is a wxApp-safe rendition of AcquireWithoutYield, which makes sure to execute pending app events
|
|
// 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
|
|
// specific task, and to block until the task is finished (PersistentThread uses it to
|
|
// determine if the thread is running or completed, for example).
|
|
//
|
|
// Implemented internally as a simple Acquire/Release pair.
|
|
//
|
|
void Threading::Mutex::Wait()
|
|
{
|
|
Acquire();
|
|
Release();
|
|
}
|
|
|
|
// Like wait but spins for a while before sleeping the thread
|
|
void Threading::Mutex::WaitWithSpin()
|
|
{
|
|
u32 waited = 0;
|
|
while (true)
|
|
{
|
|
if (TryAcquire())
|
|
{
|
|
Release();
|
|
return;
|
|
}
|
|
if (waited >= SPIN_TIME_NS)
|
|
break;
|
|
waited += ShortSpin();
|
|
}
|
|
Wait();
|
|
}
|
|
|
|
void Threading::Mutex::WaitWithoutYield()
|
|
{
|
|
AcquireWithoutYield();
|
|
Release();
|
|
}
|
|
|
|
// Performs a wait on a locked mutex, or returns instantly if the mutex is unlocked.
|
|
// (Implemented internally as a simple Acquire/Release pair.)
|
|
//
|
|
// Returns:
|
|
// true if the mutex was freed and is in an unlocked state; or false if the wait timed out
|
|
// and the mutex is still locked by another thread.
|
|
//
|
|
bool Threading::Mutex::Wait(const wxTimeSpan& timeout)
|
|
{
|
|
if (Acquire(timeout))
|
|
{
|
|
Release();
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool Threading::Mutex::WaitWithoutYield(const wxTimeSpan& timeout)
|
|
{
|
|
if (AcquireWithoutYield(timeout))
|
|
{
|
|
Release();
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// --------------------------------------------------------------------------------------
|
|
// ScopedLock Implementations
|
|
// --------------------------------------------------------------------------------------
|
|
|
|
Threading::ScopedLock::~ScopedLock()
|
|
{
|
|
if (m_IsLocked && m_lock)
|
|
m_lock->Release();
|
|
}
|
|
|
|
Threading::ScopedLock::ScopedLock(const Mutex* locker)
|
|
{
|
|
m_IsLocked = false;
|
|
AssignAndLock(locker);
|
|
}
|
|
|
|
Threading::ScopedLock::ScopedLock(const Mutex& locker)
|
|
{
|
|
m_IsLocked = false;
|
|
AssignAndLock(locker);
|
|
}
|
|
|
|
void Threading::ScopedLock::AssignAndLock(const Mutex& locker)
|
|
{
|
|
AssignAndLock(&locker);
|
|
}
|
|
|
|
void Threading::ScopedLock::AssignAndLock(const Mutex* locker)
|
|
{
|
|
pxAssert(!m_IsLocked); // if we're already locked, changing the lock is bad mojo.
|
|
|
|
m_lock = const_cast<Mutex*>(locker);
|
|
if (!m_lock)
|
|
return;
|
|
|
|
m_IsLocked = true;
|
|
m_lock->Acquire();
|
|
}
|
|
|
|
void Threading::ScopedLock::Assign(const Mutex& locker)
|
|
{
|
|
m_lock = const_cast<Mutex*>(&locker);
|
|
}
|
|
|
|
void Threading::ScopedLock::Assign(const Mutex* locker)
|
|
{
|
|
m_lock = const_cast<Mutex*>(locker);
|
|
}
|
|
|
|
// Provides manual unlocking of a scoped lock prior to object destruction.
|
|
void Threading::ScopedLock::Release()
|
|
{
|
|
if (!m_IsLocked)
|
|
return;
|
|
m_IsLocked = false;
|
|
if (m_lock)
|
|
m_lock->Release();
|
|
}
|
|
|
|
// provides manual locking of a scoped lock, to re-lock after a manual unlocking.
|
|
void Threading::ScopedLock::Acquire()
|
|
{
|
|
if (m_IsLocked || !m_lock)
|
|
return;
|
|
m_lock->Acquire();
|
|
m_IsLocked = true;
|
|
}
|
|
|
|
Threading::ScopedLock::ScopedLock(const Mutex& locker, bool isTryLock)
|
|
{
|
|
m_lock = const_cast<Mutex*>(&locker);
|
|
if (!m_lock)
|
|
return;
|
|
m_IsLocked = isTryLock ? m_lock->TryAcquire() : false;
|
|
}
|