more threds fixes, more threads tests - seems to work ok for non GUI case

git-svn-id: https://svn.wxwidgets.org/svn/wx/wxWidgets/trunk@4798 c3d73ce0-8a6f-49c7-b76d-6d57e0e08775
This commit is contained in:
Vadim Zeitlin
1999-12-02 17:59:35 +00:00
parent 48c8a76b81
commit 4c460b340f
2 changed files with 288 additions and 87 deletions

View File

@@ -32,8 +32,8 @@
//#define TEST_ARRAYS //#define TEST_ARRAYS
//#define TEST_LOG //#define TEST_LOG
//#define TEST_STRINGS //#define TEST_STRINGS
//#define TEST_THREADS #define TEST_THREADS
#define TEST_TIME //#define TEST_TIME
//#define TEST_LONGLONG //#define TEST_LONGLONG
// ============================================================================ // ============================================================================
@@ -392,6 +392,8 @@ void TestThreadSuspend()
thread->Resume(); thread->Resume();
} }
puts("Waiting until it terminates now");
// wait until the thread terminates // wait until the thread terminates
gs_cond.Wait(); gs_cond.Wait();
@@ -407,6 +409,12 @@ void TestThreadDelete()
puts("\n*** Testing thread delete function ***"); puts("\n*** Testing thread delete function ***");
MyDetachedThread *thread0 = new MyDetachedThread(30, 'W');
thread0->Delete();
puts("\nDeleted a thread which didn't start to run yet.");
MyDetachedThread *thread1 = new MyDetachedThread(30, 'Y'); MyDetachedThread *thread1 = new MyDetachedThread(30, 'Y');
thread1->Run(); thread1->Run();
@@ -429,19 +437,19 @@ void TestThreadDelete()
puts("\nDeleted a sleeping thread."); puts("\nDeleted a sleeping thread.");
MyJoinableThread *thread3 = new MyJoinableThread(20); MyJoinableThread thread3(20);
thread3->Run(); thread3.Run();
thread3->Delete(); thread3.Delete();
puts("\nDeleted a joinable thread."); puts("\nDeleted a joinable thread.");
MyJoinableThread *thread4 = new MyJoinableThread(2); MyJoinableThread thread4(2);
thread4->Run(); thread4.Run();
wxThread::Sleep(300); wxThread::Sleep(300);
thread4->Delete(); thread4.Delete();
puts("\nDeleted a joinable thread which already terminated."); puts("\nDeleted a joinable thread which already terminated.");
@@ -598,11 +606,11 @@ int main(int argc, char **argv)
if ( argc > 1 && argv[1][0] == 't' ) if ( argc > 1 && argv[1][0] == 't' )
wxLog::AddTraceMask("thread"); wxLog::AddTraceMask("thread");
if ( 0 ) if ( 1 )
TestDetachedThreads(); TestDetachedThreads();
if ( 0 ) if ( 1 )
TestJoinableThreads(); TestJoinableThreads();
if ( 0 ) if ( 1 )
TestThreadSuspend(); TestThreadSuspend();
if ( 1 ) if ( 1 )
TestThreadDelete(); TestThreadDelete();

View File

@@ -224,9 +224,12 @@ wxMutexError wxMutex::Unlock()
// wxCondition (Posix implementation) // wxCondition (Posix implementation)
//-------------------------------------------------------------------- //--------------------------------------------------------------------
// notice that we must use a mutex with POSIX condition variables to ensure // The native POSIX condition variables are dumb: if the condition is signaled
// that the worker thread doesn't signal condition before the waiting thread // before another thread starts to wait on it, the signal is lost and so this
// starts to wait for it // other thread will be never woken up. It's much more convenient to us to
// remember that the condition was signaled and to return from Wait()
// immediately in this case (this is more like Win32 automatic event objects)
class wxConditionInternal class wxConditionInternal
{ {
public: public:
@@ -239,20 +242,34 @@ public:
void Signal(); void Signal();
void Broadcast(); void Broadcast();
void WaitDone();
bool ShouldWait();
bool HasWaiters();
private: private:
pthread_mutex_t m_mutex; bool m_wasSignaled; // TRUE if condition was signaled while
pthread_cond_t m_condition; // nobody waited for it
size_t m_nWaiters; // TRUE if someone already waits for us
pthread_mutex_t m_mutexProtect; // protects access to vars above
pthread_mutex_t m_mutex; // the mutex used with the condition
pthread_cond_t m_condition; // the condition itself
}; };
wxConditionInternal::wxConditionInternal() wxConditionInternal::wxConditionInternal()
{ {
m_wasSignaled = FALSE;
m_nWaiters = 0;
if ( pthread_cond_init(&m_condition, (pthread_condattr_t *)NULL) != 0 ) if ( pthread_cond_init(&m_condition, (pthread_condattr_t *)NULL) != 0 )
{ {
// this is supposed to never happen // this is supposed to never happen
wxFAIL_MSG( _T("pthread_cond_init() failed") ); wxFAIL_MSG( _T("pthread_cond_init() failed") );
} }
if ( pthread_mutex_init(&m_mutex, (pthread_mutexattr_t*)NULL) != 0 ) if ( pthread_mutex_init(&m_mutex, (pthread_mutexattr_t *)NULL) != 0 ||
pthread_mutex_init(&m_mutexProtect, NULL) != 0 )
{ {
// neither this // neither this
wxFAIL_MSG( _T("wxCondition: pthread_mutex_init() failed") ); wxFAIL_MSG( _T("wxCondition: pthread_mutex_init() failed") );
@@ -279,28 +296,83 @@ wxConditionInternal::~wxConditionInternal()
wxLogDebug(_T("wxCondition: failed to unlock the mutex")); wxLogDebug(_T("wxCondition: failed to unlock the mutex"));
} }
if ( pthread_mutex_destroy( &m_mutex ) != 0 ) if ( pthread_mutex_destroy( &m_mutex ) != 0 ||
pthread_mutex_destroy( &m_mutexProtect ) != 0 )
{ {
wxLogDebug(_T("Failed to destroy mutex (it is probably locked)")); wxLogDebug(_T("Failed to destroy mutex (it is probably locked)"));
} }
} }
void wxConditionInternal::WaitDone()
{
MutexLock lock(m_mutexProtect);
m_wasSignaled = FALSE;
m_nWaiters--;
}
bool wxConditionInternal::ShouldWait()
{
MutexLock lock(m_mutexProtect);
if ( m_wasSignaled )
{
// the condition was signaled before we started to wait, reset the
// flag and return
m_wasSignaled = FALSE;
return FALSE;
}
// we start to wait for it
m_nWaiters++;
return TRUE;
}
bool wxConditionInternal::HasWaiters()
{
MutexLock lock(m_mutexProtect);
if ( m_nWaiters )
{
// someone waits for us, signal the condition normally
return TRUE;
}
// nobody waits for us and may be never will - so just remember that the
// condition was signaled and don't do anything else
m_wasSignaled = TRUE;
return FALSE;
}
void wxConditionInternal::Wait() void wxConditionInternal::Wait()
{ {
if ( ShouldWait() )
{
if ( pthread_cond_wait( &m_condition, &m_mutex ) != 0 ) if ( pthread_cond_wait( &m_condition, &m_mutex ) != 0 )
{ {
// not supposed to ever happen // not supposed to ever happen
wxFAIL_MSG( _T("pthread_cond_wait() failed") ); wxFAIL_MSG( _T("pthread_cond_wait() failed") );
} }
}
WaitDone();
} }
bool wxConditionInternal::WaitWithTimeout(const timespec* ts) bool wxConditionInternal::WaitWithTimeout(const timespec* ts)
{ {
bool ok;
if ( ShouldWait() )
{
switch ( pthread_cond_timedwait( &m_condition, &m_mutex, ts ) ) switch ( pthread_cond_timedwait( &m_condition, &m_mutex, ts ) )
{ {
case 0: case 0:
// condition signaled // condition signaled
return TRUE; ok = TRUE;
break;
default: default:
wxLogDebug(_T("pthread_cond_timedwait() failed")); wxLogDebug(_T("pthread_cond_timedwait() failed"));
@@ -310,12 +382,24 @@ bool wxConditionInternal::WaitWithTimeout(const timespec* ts)
case ETIMEDOUT: case ETIMEDOUT:
case EINTR: case EINTR:
// wait interrupted or timeout elapsed // wait interrupted or timeout elapsed
return FALSE; ok = FALSE;
} }
}
else
{
// the condition had already been signaled before
ok = TRUE;
}
WaitDone();
return ok;
} }
void wxConditionInternal::Signal() void wxConditionInternal::Signal()
{ {
if ( HasWaiters() )
{
MutexLock lock(m_mutex); MutexLock lock(m_mutex);
if ( pthread_cond_signal( &m_condition ) != 0 ) if ( pthread_cond_signal( &m_condition ) != 0 )
@@ -323,10 +407,13 @@ void wxConditionInternal::Signal()
// shouldn't ever happen // shouldn't ever happen
wxFAIL_MSG(_T("pthread_cond_signal() failed")); wxFAIL_MSG(_T("pthread_cond_signal() failed"));
} }
}
} }
void wxConditionInternal::Broadcast() void wxConditionInternal::Broadcast()
{ {
if ( HasWaiters() )
{
MutexLock lock(m_mutex); MutexLock lock(m_mutex);
if ( pthread_cond_broadcast( &m_condition ) != 0 ) if ( pthread_cond_broadcast( &m_condition ) != 0 )
@@ -334,6 +421,7 @@ void wxConditionInternal::Broadcast()
// shouldn't ever happen // shouldn't ever happen
wxFAIL_MSG(_T("pthread_cond_broadcast() failed")); wxFAIL_MSG(_T("pthread_cond_broadcast() failed"));
} }
}
} }
wxCondition::wxCondition() wxCondition::wxCondition()
@@ -396,6 +484,8 @@ public:
void Wait(); void Wait();
// wake up threads waiting for our termination // wake up threads waiting for our termination
void SignalExit(); void SignalExit();
// wake up threads waiting for our start
void SignalRun() { m_condRun.Signal(); }
// go to sleep until Resume() is called // go to sleep until Resume() is called
void Pause(); void Pause();
// resume the thread // resume the thread
@@ -418,8 +508,16 @@ public:
void SetExitCode(wxThread::ExitCode exitcode) { m_exitcode = exitcode; } void SetExitCode(wxThread::ExitCode exitcode) { m_exitcode = exitcode; }
wxThread::ExitCode GetExitCode() const { return m_exitcode; } wxThread::ExitCode GetExitCode() const { return m_exitcode; }
// the pause flag
void SetReallyPaused(bool paused) { m_isPaused = paused; }
bool IsReallyPaused() const { return m_isPaused; }
// tell the thread that it is a detached one // tell the thread that it is a detached one
void Detach() { m_shouldBeJoined = m_shouldBroadcast = FALSE; } void Detach()
{
m_shouldBeJoined = m_shouldBroadcast = FALSE;
m_isDetached = TRUE;
}
// but even detached threads need to notifyus about their termination // but even detached threads need to notifyus about their termination
// sometimes - tell the thread that it should do it // sometimes - tell the thread that it should do it
void Notify() { m_shouldBroadcast = TRUE; } void Notify() { m_shouldBroadcast = TRUE; }
@@ -432,6 +530,9 @@ private:
// this flag is set when the thread should terminate // this flag is set when the thread should terminate
bool m_cancelled; bool m_cancelled;
// this flag is set when the thread is blocking on m_condSuspend
bool m_isPaused;
// the thread exit code - only used for joinable (!detached) threads and // the thread exit code - only used for joinable (!detached) threads and
// is only valid after the thread termination // is only valid after the thread termination
wxThread::ExitCode m_exitcode; wxThread::ExitCode m_exitcode;
@@ -441,6 +542,7 @@ private:
wxCriticalSection m_csJoinFlag; wxCriticalSection m_csJoinFlag;
bool m_shouldBeJoined; bool m_shouldBeJoined;
bool m_shouldBroadcast; bool m_shouldBroadcast;
bool m_isDetached;
// VZ: it's possible that we might do with less than three different // VZ: it's possible that we might do with less than three different
// condition objects - for example, m_condRun and m_condEnd a priori // condition objects - for example, m_condRun and m_condEnd a priori
@@ -479,6 +581,10 @@ void *wxThreadInternal::PthreadStart(void *ptr)
return (void *)-1; return (void *)-1;
} }
// have to declare this before pthread_cleanup_push() which defines a
// block!
bool dontRunAtAll;
#if HAVE_THREAD_CLEANUP_FUNCTIONS #if HAVE_THREAD_CLEANUP_FUNCTIONS
// install the cleanup handler which will be called if the thread is // install the cleanup handler which will be called if the thread is
// cancelled // cancelled
@@ -488,6 +594,17 @@ void *wxThreadInternal::PthreadStart(void *ptr)
// wait for the condition to be signaled from Run() // wait for the condition to be signaled from Run()
pthread->m_condRun.Wait(); pthread->m_condRun.Wait();
// test whether we should run the run at all - may be it was deleted
// before it started to Run()?
{
wxCriticalSectionLocker lock(thread->m_critsect);
dontRunAtAll = pthread->GetState() == STATE_NEW &&
pthread->WasCancelled();
}
if ( !dontRunAtAll )
{
// call the main entry // call the main entry
pthread->m_exitcode = thread->Entry(); pthread->m_exitcode = thread->Entry();
@@ -500,11 +617,12 @@ void *wxThreadInternal::PthreadStart(void *ptr)
wxLogTrace(TRACE_THREADS, _T("Thread %ld changes state to EXITED."), wxLogTrace(TRACE_THREADS, _T("Thread %ld changes state to EXITED."),
pthread->GetId()); pthread->GetId());
// change the state of the thread to "exited" so that PthreadCleanup // change the state of the thread to "exited" so that
// handler won't do anything from now (if it's called before we do // PthreadCleanup handler won't do anything from now (if it's
// pthread_cleanup_pop below) // called before we do pthread_cleanup_pop below)
pthread->SetState(STATE_EXITED); pthread->SetState(STATE_EXITED);
} }
}
// NB: at least under Linux, pthread_cleanup_push/pop are macros and pop // NB: at least under Linux, pthread_cleanup_push/pop are macros and pop
// contains the matching '}' for the '{' in push, so they must be used // contains the matching '}' for the '{' in push, so they must be used
@@ -514,12 +632,21 @@ void *wxThreadInternal::PthreadStart(void *ptr)
pthread_cleanup_pop(FALSE); pthread_cleanup_pop(FALSE);
#endif // HAVE_THREAD_CLEANUP_FUNCTIONS #endif // HAVE_THREAD_CLEANUP_FUNCTIONS
if ( dontRunAtAll )
{
delete thread;
return EXITCODE_CANCELLED;
}
else
{
// terminate the thread // terminate the thread
thread->Exit(pthread->m_exitcode); thread->Exit(pthread->m_exitcode);
wxFAIL_MSG(wxT("wxThread::Exit() can't return.")); wxFAIL_MSG(wxT("wxThread::Exit() can't return."));
return NULL; return NULL;
}
} }
#if HAVE_THREAD_CLEANUP_FUNCTIONS #if HAVE_THREAD_CLEANUP_FUNCTIONS
@@ -556,9 +683,13 @@ wxThreadInternal::wxThreadInternal()
m_threadId = 0; m_threadId = 0;
m_exitcode = 0; m_exitcode = 0;
// set to TRUE only when the thread starts waiting on m_condSuspend
m_isPaused = FALSE;
// defaults for joinable threads // defaults for joinable threads
m_shouldBeJoined = TRUE; m_shouldBeJoined = TRUE;
m_shouldBroadcast = TRUE; m_shouldBroadcast = TRUE;
m_isDetached = FALSE;
} }
wxThreadInternal::~wxThreadInternal() wxThreadInternal::~wxThreadInternal()
@@ -570,7 +701,7 @@ wxThreadError wxThreadInternal::Run()
wxCHECK_MSG( GetState() == STATE_NEW, wxTHREAD_RUNNING, wxCHECK_MSG( GetState() == STATE_NEW, wxTHREAD_RUNNING,
wxT("thread may only be started once after Create()") ); wxT("thread may only be started once after Create()") );
m_condRun.Signal(); SignalRun();
SetState(STATE_RUNNING); SetState(STATE_RUNNING);
@@ -584,30 +715,40 @@ void wxThreadInternal::Wait()
if ( wxThread::IsMain() ) if ( wxThread::IsMain() )
wxMutexGuiLeave(); wxMutexGuiLeave();
bool isDetached = m_isDetached;
long id = GetId();
wxLogTrace(TRACE_THREADS, _T("Starting to wait for thread %ld to exit."), wxLogTrace(TRACE_THREADS, _T("Starting to wait for thread %ld to exit."),
GetId()); id);
// wait until the thread terminates (we're blocking in _another_ thread, // wait until the thread terminates (we're blocking in _another_ thread,
// of course) // of course)
m_condEnd.Wait(); m_condEnd.Wait();
// to avoid memory leaks we should call pthread_join(), but it must only wxLogTrace(TRACE_THREADS, _T("Finished waiting for thread %ld."), id);
// be done once
// we can't use any member variables any more if the thread is detached
// because it could be already deleted
if ( !isDetached )
{
// to avoid memory leaks we should call pthread_join(), but it must
// only be done once
wxCriticalSectionLocker lock(m_csJoinFlag); wxCriticalSectionLocker lock(m_csJoinFlag);
if ( m_shouldBeJoined ) if ( m_shouldBeJoined )
{ {
// FIXME shouldn't we set cancellation type to DISABLED here? If we're // FIXME shouldn't we set cancellation type to DISABLED here? If
// cancelled inside pthread_join(), things will almost certainly // we're cancelled inside pthread_join(), things will almost
// break - but if we disable the cancellation, we might deadlock // certainly break - but if we disable the cancellation, we
if ( pthread_join(GetId(), &m_exitcode) != 0 ) // might deadlock
if ( pthread_join(id, &m_exitcode) != 0 )
{ {
wxLogError(_T("Failed to join a thread, potential memory leak " wxLogError(_("Failed to join a thread, potential memory leak "
"detected - please restart the program")); "detected - please restart the program"));
} }
m_shouldBeJoined = FALSE; m_shouldBeJoined = FALSE;
} }
}
// reacquire GUI mutex // reacquire GUI mutex
if ( wxThread::IsMain() ) if ( wxThread::IsMain() )
@@ -648,11 +789,24 @@ void wxThreadInternal::Resume()
wxCHECK_RET( m_state == STATE_PAUSED, wxCHECK_RET( m_state == STATE_PAUSED,
wxT("can't resume thread which is not suspended.") ); wxT("can't resume thread which is not suspended.") );
// the thread might be not actually paused yet - if there were no call to
// TestDestroy() since the last call to Pause() for example
if ( IsReallyPaused() )
{
wxLogTrace(TRACE_THREADS, _T("Waking up thread %ld"), GetId()); wxLogTrace(TRACE_THREADS, _T("Waking up thread %ld"), GetId());
// wake up Pause() // wake up Pause()
m_condSuspend.Signal(); m_condSuspend.Signal();
// reset the flag
SetReallyPaused(FALSE);
}
else
{
wxLogTrace(TRACE_THREADS, _T("Thread %ld is not yet really paused"),
GetId());
}
SetState(STATE_RUNNING); SetState(STATE_RUNNING);
} }
@@ -866,6 +1020,9 @@ unsigned long wxThread::GetId() const
wxThreadError wxThread::Pause() wxThreadError wxThread::Pause()
{ {
wxCHECK_MSG( This() != this, wxTHREAD_MISC_ERROR,
_T("a thread can't pause itself") );
wxCriticalSectionLocker lock(m_critsect); wxCriticalSectionLocker lock(m_critsect);
if ( m_internal->GetState() != STATE_RUNNING ) if ( m_internal->GetState() != STATE_RUNNING )
@@ -875,6 +1032,9 @@ wxThreadError wxThread::Pause()
return wxTHREAD_NOT_RUNNING; return wxTHREAD_NOT_RUNNING;
} }
wxLogTrace(TRACE_THREADS, _T("Asking thread %ld to pause."),
GetId());
// just set a flag, the thread will be really paused only during the next // just set a flag, the thread will be really paused only during the next
// call to TestDestroy() // call to TestDestroy()
m_internal->SetState(STATE_PAUSED); m_internal->SetState(STATE_PAUSED);
@@ -884,20 +1044,17 @@ wxThreadError wxThread::Pause()
wxThreadError wxThread::Resume() wxThreadError wxThread::Resume()
{ {
m_critsect.Enter(); wxCHECK_MSG( This() != this, wxTHREAD_MISC_ERROR,
_T("a thread can't resume itself") );
wxCriticalSectionLocker lock(m_critsect);
wxThreadState state = m_internal->GetState(); wxThreadState state = m_internal->GetState();
// the thread might be not actually paused yet - if there were no call to
// TestDestroy() since the last call to Pause(), so avoid that
// TestDestroy() deadlocks trying to enter m_critsect by leaving it before
// calling Resume()
m_critsect.Leave();
switch ( state ) switch ( state )
{ {
case STATE_PAUSED: case STATE_PAUSED:
wxLogTrace(TRACE_THREADS, _T("Thread %ld is suspended, resuming."), wxLogTrace(TRACE_THREADS, _T("Thread %ld suspended, resuming."),
GetId()); GetId());
m_internal->Resume(); m_internal->Resume();
@@ -935,6 +1092,9 @@ wxThread::ExitCode wxThread::Wait()
wxThreadError wxThread::Delete(ExitCode *rc) wxThreadError wxThread::Delete(ExitCode *rc)
{ {
wxCHECK_MSG( This() != this, wxTHREAD_MISC_ERROR,
_T("a thread can't delete itself") );
m_critsect.Enter(); m_critsect.Enter();
wxThreadState state = m_internal->GetState(); wxThreadState state = m_internal->GetState();
@@ -954,6 +1114,12 @@ wxThreadError wxThread::Delete(ExitCode *rc)
switch ( state ) switch ( state )
{ {
case STATE_NEW: case STATE_NEW:
// we need to wake up the thread so that PthreadStart() will
// terminate - right now it's blocking on m_condRun
m_internal->SignalRun();
// fall through
case STATE_EXITED: case STATE_EXITED:
// nothing to do // nothing to do
break; break;
@@ -1032,6 +1198,10 @@ wxThreadError wxThread::Kill()
void wxThread::Exit(ExitCode status) void wxThread::Exit(ExitCode status)
{ {
wxASSERT_MSG( This() == this,
_T("wxThread::Exit() can only be called in the "
"context of the same thread") );
// from the moment we call OnExit(), the main program may terminate at any // from the moment we call OnExit(), the main program may terminate at any
// moment, so mark this thread as being already in process of being // moment, so mark this thread as being already in process of being
// deleted or wxThreadModule::OnExit() will try to delete it again // deleted or wxThreadModule::OnExit() will try to delete it again
@@ -1076,10 +1246,16 @@ void wxThread::Exit(ExitCode status)
// also test whether we were paused // also test whether we were paused
bool wxThread::TestDestroy() bool wxThread::TestDestroy()
{ {
wxASSERT_MSG( This() == this,
_T("wxThread::TestDestroy() can only be called in the "
"context of the same thread") );
m_critsect.Enter(); m_critsect.Enter();
if ( m_internal->GetState() == STATE_PAUSED ) if ( m_internal->GetState() == STATE_PAUSED )
{ {
m_internal->SetReallyPaused(TRUE);
// leave the crit section or the other threads will stop too if they // leave the crit section or the other threads will stop too if they
// try to call any of (seemingly harmless) IsXXX() functions while we // try to call any of (seemingly harmless) IsXXX() functions while we
// sleep // sleep
@@ -1105,8 +1281,8 @@ wxThread::~wxThread()
if ( m_internal->GetState() != STATE_EXITED && if ( m_internal->GetState() != STATE_EXITED &&
m_internal->GetState() != STATE_NEW ) m_internal->GetState() != STATE_NEW )
{ {
wxLogDebug(_T("The thread is being destroyed although it is still " wxLogDebug(_T("The thread %ld is being destroyed although it is still "
"running! The application may crash.")); "running! The application may crash."), GetId());
} }
m_critsect.Leave(); m_critsect.Leave();
@@ -1116,6 +1292,17 @@ wxThread::~wxThread()
// remove this thread from the global array // remove this thread from the global array
gs_allThreads.Remove(this); gs_allThreads.Remove(this);
// detached thread will decrement this counter in DeleteThread(), but it
// is not called for the joinable threads, so do it here
if ( !m_isDetached )
{
MutexLock lock(gs_mutexDeleteThread);
gs_nThreadsBeingDeleted--;
wxLogTrace(TRACE_THREADS, _T("%u scheduled for deletion threads left."),
gs_nThreadsBeingDeleted - 1);
}
} }
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
@@ -1212,7 +1399,10 @@ void wxThreadModule::OnExit()
// terminate any threads left // terminate any threads left
size_t count = gs_allThreads.GetCount(); size_t count = gs_allThreads.GetCount();
if ( count != 0u ) if ( count != 0u )
wxLogDebug(wxT("Some threads were not terminated by the application.")); {
wxLogDebug(wxT("%u threads were not terminated by the application."),
count);
}
for ( size_t n = 0u; n < count; n++ ) for ( size_t n = 0u; n < count; n++ )
{ {
@@ -1268,6 +1458,9 @@ static void DeleteThread(wxThread *This)
"one?") ); "one?") );
} }
wxLogTrace(TRACE_THREADS, _T("%u scheduled for deletion threads left."),
gs_nThreadsBeingDeleted - 1);
if ( !--gs_nThreadsBeingDeleted ) if ( !--gs_nThreadsBeingDeleted )
{ {
// no more threads left, signal it // no more threads left, signal it