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:
Jake.Stine 2009-11-22 09:47:52 +00:00
parent ef2c64a28b
commit 87b443a873
5 changed files with 211 additions and 39 deletions

View File

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

View File

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

View File

@ -332,7 +332,8 @@ bool SaveStateBase::FreezeSection( int seek_section )
break;
}
wxSafeYield( NULL );
if( wxThread::IsMain() )
wxSafeYield( NULL, true );
return true;
}

View File

@ -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."
);
}
}

View File

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