diff --git a/common/include/Utilities/Threading.h b/common/include/Utilities/Threading.h index 9a9d01b927..31a3b068b9 100644 --- a/common/include/Utilities/Threading.h +++ b/common/include/Utilities/Threading.h @@ -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(); diff --git a/common/src/Utilities/ThreadTools.cpp b/common/src/Utilities/ThreadTools.cpp index e1e0af6800..a6427b6951 100644 --- a/common/src/Utilities/ThreadTools.cpp +++ b/common/src/Utilities/ThreadTools.cpp @@ -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; +} diff --git a/pcsx2/SaveState.cpp b/pcsx2/SaveState.cpp index cedd2549f5..a76f3c2a6a 100644 --- a/pcsx2/SaveState.cpp +++ b/pcsx2/SaveState.cpp @@ -332,7 +332,8 @@ bool SaveStateBase::FreezeSection( int seek_section ) break; } - wxSafeYield( NULL ); + if( wxThread::IsMain() ) + wxSafeYield( NULL, true ); return true; } diff --git a/pcsx2/System/SysThreadBase.cpp b/pcsx2/System/SysThreadBase.cpp index fc52742ff0..62fd9ddfb7 100644 --- a/pcsx2/System/SysThreadBase.cpp +++ b/pcsx2/System/SysThreadBase.cpp @@ -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." ); } } diff --git a/pcsx2/gui/AppMain.cpp b/pcsx2/gui/AppMain.cpp index 0b82807aee..0f51d93407 100644 --- a/pcsx2/gui/AppMain.cpp +++ b/pcsx2/gui/AppMain.cpp @@ -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() );