mirror of https://github.com/PCSX2/pcsx2.git
Some preliminary work for improving multithread recoverability and deadlock handling. (not really doing much useful yet)
git-svn-id: http://pcsx2.googlecode.com/svn/trunk@2234 96395faa-99c1-11dd-bbfe-3dabce05a288
This commit is contained in:
parent
ef2c64a28b
commit
87b443a873
|
@ -23,17 +23,67 @@
|
|||
#include "ScopedPtr.h"
|
||||
|
||||
#undef Yield // release th burden of windows.h global namespace spam.
|
||||
class wxTimeSpan;
|
||||
|
||||
#define AllowFromMainThreadOnly() \
|
||||
pxAssertMsg( wxThread::IsMain(), "Thread affinity violation: Call allowed from main thread only." )
|
||||
|
||||
class wxTimeSpan;
|
||||
|
||||
namespace Threading
|
||||
{
|
||||
class PersistentThread;
|
||||
}
|
||||
|
||||
namespace Exception
|
||||
{
|
||||
class ThreadCreationError : public virtual RuntimeError
|
||||
class BaseThreadError : public virtual RuntimeError
|
||||
{
|
||||
public:
|
||||
DEFINE_RUNTIME_EXCEPTION( ThreadCreationError, wxLt("Thread could not be created.") );
|
||||
Threading::PersistentThread* m_thread;
|
||||
|
||||
DEFINE_EXCEPTION_COPYTORS( BaseThreadError )
|
||||
|
||||
explicit BaseThreadError( Threading::PersistentThread* _thread=NULL )
|
||||
{
|
||||
m_thread = _thread;
|
||||
BaseException::InitBaseEx( "Unspecified thread error" );
|
||||
}
|
||||
|
||||
BaseThreadError( Threading::PersistentThread& _thread )
|
||||
{
|
||||
m_thread = &_thread;
|
||||
BaseException::InitBaseEx( "Unspecified thread error" );
|
||||
}
|
||||
|
||||
virtual wxString FormatDiagnosticMessage() const;
|
||||
virtual wxString FormatDisplayMessage() const;
|
||||
|
||||
Threading::PersistentThread& Thread();
|
||||
const Threading::PersistentThread& Thread() const;
|
||||
};
|
||||
|
||||
class ThreadCreationError : public virtual BaseThreadError
|
||||
{
|
||||
public:
|
||||
DEFINE_EXCEPTION_COPYTORS( ThreadCreationError )
|
||||
|
||||
explicit ThreadCreationError( Threading::PersistentThread* _thread=NULL, const char* msg="Creation of thread '%s' failed." )
|
||||
{
|
||||
m_thread = _thread;
|
||||
BaseException::InitBaseEx( msg );
|
||||
}
|
||||
|
||||
ThreadCreationError( Threading::PersistentThread& _thread, const char* msg="Creation of thread '%s' failed." )
|
||||
{
|
||||
m_thread = &_thread;
|
||||
BaseException::InitBaseEx( msg );
|
||||
}
|
||||
|
||||
ThreadCreationError( Threading::PersistentThread& _thread, const wxString& msg_diag, const wxString& msg_user )
|
||||
{
|
||||
m_thread = &_thread;
|
||||
BaseException::InitBaseEx( msg_diag, msg_user );
|
||||
}
|
||||
};
|
||||
|
||||
#if wxUSE_GUI
|
||||
|
@ -48,10 +98,28 @@ namespace Exception
|
|||
// * If the user-specified timeout is less than the deadlock timeout.
|
||||
// * If the method is run from a thread *other* than the MainGui thread.
|
||||
//
|
||||
class ThreadTimedOut : public virtual RuntimeError
|
||||
class ThreadTimedOut : public virtual BaseThreadError
|
||||
{
|
||||
public:
|
||||
DEFINE_RUNTIME_EXCEPTION( ThreadTimedOut, "Blocking action timed out due to potential deadlock." );
|
||||
DEFINE_EXCEPTION_COPYTORS( ThreadTimedOut )
|
||||
|
||||
explicit ThreadTimedOut( Threading::PersistentThread* _thread=NULL, const char* msg="Blocking action timed out waiting for '%s' (potential thread deadlock)." )
|
||||
{
|
||||
m_thread = _thread;
|
||||
BaseException::InitBaseEx( msg );
|
||||
}
|
||||
|
||||
ThreadTimedOut( Threading::PersistentThread& _thread, const char* msg="Blocking action timed out waiting for '%s' (potential thread deadlock)." )
|
||||
{
|
||||
m_thread = &_thread;
|
||||
BaseException::InitBaseEx( msg );
|
||||
}
|
||||
|
||||
ThreadTimedOut( Threading::PersistentThread& _thread, const wxString& msg_diag, const wxString& msg_user )
|
||||
{
|
||||
m_thread = &_thread;
|
||||
BaseException::InitBaseEx( msg_diag, msg_user );
|
||||
}
|
||||
};
|
||||
#endif
|
||||
}
|
||||
|
@ -298,8 +366,10 @@ namespace Threading
|
|||
virtual void Block();
|
||||
virtual void RethrowException() const;
|
||||
|
||||
void WaitOnSelf( Semaphore& mutex );
|
||||
void WaitOnSelf( Mutex& mutex );
|
||||
void WaitOnSelf( Semaphore& mutex ) const;
|
||||
void WaitOnSelf( Mutex& mutex ) const;
|
||||
bool WaitOnSelf( Semaphore& mutex, const wxTimeSpan& timeout ) const;
|
||||
bool WaitOnSelf( Mutex& mutex, const wxTimeSpan& timeout ) const;
|
||||
|
||||
bool IsRunning() const;
|
||||
bool IsSelf() const;
|
||||
|
@ -344,6 +414,7 @@ namespace Threading
|
|||
// ----------------------------------------------------------------------------
|
||||
// Section of methods for internal use only.
|
||||
|
||||
void _selfRunningTest( const wxChar* name ) const;
|
||||
void _DoSetThreadName( const wxString& name );
|
||||
void _DoSetThreadName( const char* name );
|
||||
void _internal_execute();
|
||||
|
|
|
@ -28,6 +28,8 @@
|
|||
#include "wxBaseTools.h"
|
||||
#include "ThreadingInternal.h"
|
||||
|
||||
using namespace Threading;
|
||||
|
||||
// 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 );
|
||||
|
@ -71,9 +73,9 @@ Threading::PersistentThread::PersistentThread() :
|
|||
, m_sem_event()
|
||||
, m_lock_InThread()
|
||||
, m_lock_start()
|
||||
, m_detached( true ) // start out with m_thread in detached/invalid state
|
||||
, m_running( false )
|
||||
{
|
||||
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
|
||||
|
@ -233,56 +235,104 @@ void Threading::PersistentThread::RethrowException() const
|
|||
m_except->Rethrow();
|
||||
}
|
||||
|
||||
void Threading::PersistentThread::_selfRunningTest( const wxChar* name ) const
|
||||
{
|
||||
if( HasPendingException() )
|
||||
{
|
||||
Console.Error( L"(Thread Error) An exception was thrown from blocking thread '%s' while waiting on a %s.",
|
||||
GetName().c_str(), name
|
||||
);
|
||||
RethrowException();
|
||||
}
|
||||
|
||||
if( !m_running )
|
||||
{
|
||||
throw Exception::CancelEvent( wxsFormat(
|
||||
L"Blocking thread %s was terminated while another thread was waiting on a %s.",
|
||||
GetName().c_str(), name )
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// This helper function is a deadlock-safe method of waiting on a semaphore in a PersistentThread. 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 ThreadTimedOut exception.
|
||||
// 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:
|
||||
// ThreadTimedOut
|
||||
// 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::PersistentThread::WaitOnSelf( Semaphore& sem )
|
||||
void Threading::PersistentThread::WaitOnSelf( Semaphore& sem ) const
|
||||
{
|
||||
if( !pxAssertDev( !IsSelf(), "WaitOnSelf called from inside the thread (invalid operation!)" ) ) return;
|
||||
if( !pxAssertDev( !IsSelf(), "WaitOnSelf called from inside the blocking thread (invalid operation!)" ) ) return;
|
||||
|
||||
while( true )
|
||||
{
|
||||
if( sem.Wait( wxTimeSpan(0, 0, 0, 250) ) ) return;
|
||||
if( !m_running )
|
||||
{
|
||||
wxString msg( m_name + L": thread was terminated while another thread was waiting on a semaphore." );
|
||||
throw Exception::ThreadTimedOut( msg, msg );
|
||||
}
|
||||
if( sem.Wait( wxTimeSpan(0, 0, 0, 333) ) ) return;
|
||||
_selfRunningTest( L"semaphore" );
|
||||
}
|
||||
}
|
||||
|
||||
// This helper function is a deadlock-safe method of waiting on a mutex in a PersistentThread. 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 throw a ThreadTimedOut exception.
|
||||
// This helper function is a deadlock-safe method of waiting on a mutex in a PersistentThread.
|
||||
// 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 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.
|
||||
// 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:
|
||||
// ThreadTimedOut
|
||||
// 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::PersistentThread::WaitOnSelf( Mutex& mutex )
|
||||
void Threading::PersistentThread::WaitOnSelf( Mutex& mutex ) const
|
||||
{
|
||||
if( !pxAssertDev( !IsSelf(), "WaitOnSelf called from inside the thread (invalid operation!)" ) ) return;
|
||||
if( !pxAssertDev( !IsSelf(), "WaitOnSelf called from inside the blocking thread (invalid operation!)" ) ) return;
|
||||
|
||||
while( true )
|
||||
{
|
||||
if( mutex.Wait( wxTimeSpan(0, 0, 0, 250) ) ) return;
|
||||
if( !m_running )
|
||||
{
|
||||
wxString msg( m_name + L": thread was terminated while another thread was waiting on a mutex." );
|
||||
throw Exception::ThreadTimedOut( msg, msg );
|
||||
}
|
||||
if( mutex.Wait( wxTimeSpan(0, 0, 0, 333) ) ) return;
|
||||
_selfRunningTest( L"mutex" );
|
||||
}
|
||||
}
|
||||
|
||||
static const wxTimeSpan SelfWaitInterval( 0,0,0,333 );
|
||||
|
||||
bool Threading::PersistentThread::WaitOnSelf( Semaphore& sem, const wxTimeSpan& timeout ) const
|
||||
{
|
||||
if( !pxAssertDev( !IsSelf(), "WaitOnSelf called from inside the blocking thread (invalid operation!)" ) ) return true;
|
||||
|
||||
wxTimeSpan runningout( timeout );
|
||||
|
||||
while( runningout.GetMilliseconds() > 0 )
|
||||
{
|
||||
const wxTimeSpan interval( (SelfWaitInterval < runningout) ? SelfWaitInterval : runningout );
|
||||
if( sem.Wait( interval ) ) return true;
|
||||
_selfRunningTest( L"semaphore" );
|
||||
runningout -= interval;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Threading::PersistentThread::WaitOnSelf( Mutex& mutex, const wxTimeSpan& timeout ) const
|
||||
{
|
||||
if( !pxAssertDev( !IsSelf(), "WaitOnSelf called from inside the blocking thread (invalid operation!)" ) ) return true;
|
||||
|
||||
wxTimeSpan runningout( timeout );
|
||||
|
||||
while( runningout.GetMilliseconds() > 0 )
|
||||
{
|
||||
const wxTimeSpan interval( (SelfWaitInterval < runningout) ? SelfWaitInterval : runningout );
|
||||
if( mutex.Wait( 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++
|
||||
|
@ -554,7 +604,6 @@ void Threading::WaitEvent::Wait()
|
|||
}
|
||||
#endif
|
||||
|
||||
|
||||
// --------------------------------------------------------------------------------------
|
||||
// InterlockedExchanges / AtomicExchanges (PCSX2's Helper versions)
|
||||
// --------------------------------------------------------------------------------------
|
||||
|
@ -599,3 +648,28 @@ __forceinline s32 Threading::AtomicDecrement( volatile s32& Target )
|
|||
{
|
||||
return _InterlockedExchangeAdd( (volatile long*)&Target, -1 );
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------------------------
|
||||
// BaseThreadError
|
||||
// --------------------------------------------------------------------------------------
|
||||
|
||||
wxString Exception::BaseThreadError::FormatDiagnosticMessage() const
|
||||
{
|
||||
return wxsFormat( m_message_diag, (m_thread==NULL) ? L"Null Thread Object" : m_thread->GetName() );
|
||||
}
|
||||
|
||||
wxString Exception::BaseThreadError::FormatDisplayMessage() const
|
||||
{
|
||||
return wxsFormat( m_message_user, (m_thread==NULL) ? _("Null Thread Object") : m_thread->GetName() );
|
||||
}
|
||||
|
||||
PersistentThread& Exception::BaseThreadError::Thread()
|
||||
{
|
||||
pxAssertDev( m_thread != NULL, "NULL thread object on ThreadError exception." );
|
||||
return *m_thread;
|
||||
}
|
||||
const PersistentThread& Exception::BaseThreadError::Thread() const
|
||||
{
|
||||
pxAssertDev( m_thread != NULL, "NULL thread object on ThreadError exception." );
|
||||
return *m_thread;
|
||||
}
|
||||
|
|
|
@ -332,7 +332,8 @@ bool SaveStateBase::FreezeSection( int seek_section )
|
|||
break;
|
||||
}
|
||||
|
||||
wxSafeYield( NULL );
|
||||
if( wxThread::IsMain() )
|
||||
wxSafeYield( NULL, true );
|
||||
|
||||
return true;
|
||||
}
|
||||
|
|
|
@ -45,8 +45,8 @@ void SysThreadBase::Start()
|
|||
RethrowException();
|
||||
if( pxAssertDev( m_ExecMode == ExecMode_Closing, "Unexpected thread status during SysThread startup." ) )
|
||||
{
|
||||
throw Exception::ThreadCreationError(
|
||||
wxsFormat( L"Timeout occurred while attempting to start the %s thread.", m_name.c_str() ),
|
||||
throw Exception::ThreadCreationError( *this,
|
||||
L"Timeout occurred while attempting to start the '%s' thread.",
|
||||
wxEmptyString
|
||||
);
|
||||
}
|
||||
|
@ -129,8 +129,9 @@ bool SysThreadBase::Suspend( bool isBlocking )
|
|||
// [TODO] : Implement proper deadlock handler here that lets the user continue
|
||||
// to wait, or issue a cancel to the thread.
|
||||
|
||||
throw Exception::ThreadTimedOut( L"Possible deadlock while suspending the " + m_name,
|
||||
m_name + L" is not responding to suspend requests. It may be deadlocked or just running *really* slow."
|
||||
throw Exception::ThreadTimedOut( *this,
|
||||
L"Possible deadlock while suspending thread '%s'",
|
||||
L"'%s' thread is not responding to suspend requests. It may be deadlocked or just running *really* slow."
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -286,6 +286,31 @@ void Pcsx2App::HandleEvent(wxEvtHandler* handler, wxEventFunction func, wxEvent&
|
|||
}
|
||||
}
|
||||
// ----------------------------------------------------------------------------
|
||||
catch( Exception::ThreadTimedOut& ex )
|
||||
{
|
||||
Console.Warning( ex.FormatDiagnosticMessage() );
|
||||
int result = Dialogs::ExtensibleConfirmation( NULL, ConfButtons().Ignore().Cancel().Custom( _("Terminate") ),
|
||||
_("PCSX2 Unresponsive Thread"), ex.FormatDisplayMessage() + L"\n\n" +
|
||||
pxE( ".Popup Error:Thread Deadlock Actions",
|
||||
L"'Ignore' to continue waiting for the thread to respond.\n"
|
||||
L"'Cancel' to attempt to cancel the thread.\n"
|
||||
L"'Terminate' to quit PCSX2 immediately.\n"
|
||||
)
|
||||
).ShowModal();
|
||||
|
||||
if( result == pxID_CUSTOM )
|
||||
{
|
||||
exit(-5); // fastest way to kill the process?
|
||||
}
|
||||
else if( result == wxID_CANCEL )
|
||||
{
|
||||
// Attempt to cancel the thread:
|
||||
ex.Thread().Cancel();
|
||||
}
|
||||
|
||||
// Ignore does nothing...
|
||||
}
|
||||
// ----------------------------------------------------------------------------
|
||||
catch( Exception::CancelEvent& ex )
|
||||
{
|
||||
Console.Warning( ex.FormatDiagnosticMessage() );
|
||||
|
|
Loading…
Reference in New Issue