X-Git-Url: https://git.saurik.com/wxWidgets.git/blobdiff_plain/2501ce513b571d6d0a1e793cfb47f4533981e314..f8c5636c42238296eb67bf73d1da3c5acb010fee:/src/gtk1/threadpsx.cpp diff --git a/src/gtk1/threadpsx.cpp b/src/gtk1/threadpsx.cpp index f4fd5908db..99239908ef 100644 --- a/src/gtk1/threadpsx.cpp +++ b/src/gtk1/threadpsx.cpp @@ -12,38 +12,61 @@ ///////////////////////////////////////////////////////////////////////////// #ifdef __GNUG__ -#pragma implementation "thread.h" + #pragma implementation "thread.h" #endif #include #include #include #include + +#ifdef __linux__ + #include +#endif + +#ifdef __SUN__ +extern int usleep(unsigned int useconds); +#endif + + #include "wx/thread.h" #include "wx/module.h" #include "wx/utils.h" #include "wx/log.h" +#include "wx/intl.h" +#include "wx/dynarray.h" #include "gdk/gdk.h" #include "gtk/gtk.h" -enum thread_state +enum thread_state { - STATE_IDLE = 0, - STATE_RUNNING, - STATE_PAUSING, - STATE_PAUSED, - STATE_CANCELED, - STATE_EXITED + STATE_NEW, // didn't start execution yet (=> RUNNING) + STATE_RUNNING, + STATE_PAUSED, + STATE_CANCELED, + STATE_EXITED }; -//-------------------------------------------------------------------- +WX_DEFINE_ARRAY(wxThread *, wxArrayThread); + +// ----------------------------------------------------------------------------- // global data -//-------------------------------------------------------------------- +// ----------------------------------------------------------------------------- + +// we keep the list of all threads created by the application to be able to +// terminate them on exit if there are some left - otherwise the process would +// be left in memory +static wxArrayThread gs_allThreads; + +// the id of the main thread +static pthread_t gs_tidMain; -static pthread_t p_mainid; +// the key for the pointer to the associated wxThread object +static pthread_key_t gs_keySelf; -wxMutex *wxMainMutex; /* controls access to all GUI functions */ +// this mutex must be acquired before any call to a GUI function +static wxMutex *gs_mutexGui; //-------------------------------------------------------------------- // common GUI thread code @@ -55,7 +78,7 @@ wxMutex *wxMainMutex; /* controls access to all GUI functions */ // wxMutex (Posix implementation) //-------------------------------------------------------------------- -class wxMutexInternal +class wxMutexInternal { public: pthread_mutex_t p_mutex; @@ -71,7 +94,7 @@ wxMutex::wxMutex() wxMutex::~wxMutex() { if (m_locked > 0) - wxLogDebug( "wxMutex warning: freeing a locked mutex (%d locks)\n", m_locked ); + wxLogDebug("Freeing a locked mutex (%d locks)", m_locked); pthread_mutex_destroy( &(p_internal->p_mutex) ); delete p_internal; @@ -82,11 +105,13 @@ wxMutexError wxMutex::Lock() int err = pthread_mutex_lock( &(p_internal->p_mutex) ); if (err == EDEADLK) { + wxLogDebug("Locking this mutex would lead to deadlock!"); + return wxMUTEX_DEAD_LOCK; } - + m_locked++; - + return wxMUTEX_NO_ERROR; } @@ -96,15 +121,15 @@ wxMutexError wxMutex::TryLock() { return wxMUTEX_BUSY; } - + int err = pthread_mutex_trylock( &(p_internal->p_mutex) ); - switch (err) + switch (err) { case EBUSY: return wxMUTEX_BUSY; } - + m_locked++; - + return wxMUTEX_NO_ERROR; } @@ -116,11 +141,13 @@ wxMutexError wxMutex::Unlock() } else { + wxLogDebug("Unlocking not locked mutex."); + return wxMUTEX_UNLOCKED; } - + pthread_mutex_unlock( &(p_internal->p_mutex) ); - + return wxMUTEX_NO_ERROR; } @@ -128,7 +155,7 @@ wxMutexError wxMutex::Unlock() // wxCondition (Posix implementation) //-------------------------------------------------------------------- -class wxConditionInternal +class wxConditionInternal { public: pthread_cond_t p_condition; @@ -143,7 +170,7 @@ wxCondition::wxCondition() wxCondition::~wxCondition() { pthread_cond_destroy( &(p_internal->p_condition) ); - + delete p_internal; } @@ -175,218 +202,506 @@ void wxCondition::Broadcast() // wxThread (Posix implementation) //-------------------------------------------------------------------- -class wxThreadInternal +class wxThreadInternal { public: - wxThreadInternal() { state = STATE_IDLE; } - ~wxThreadInternal() {} - static void *PthreadStart(void *ptr); - pthread_t thread_id; - int state; - int prio; - int defer_destroy; + wxThreadInternal(); + ~wxThreadInternal(); + + // thread entry function + static void *PthreadStart(void *ptr); + + // thread actions + // start the thread + wxThreadError Run(); + // ask the thread to terminate + void Cancel(); + // wake up threads waiting for our termination + void SignalExit(); + // go to sleep until Resume() is called + void Pause(); + // resume the thread + void Resume(); + + // accessors + // priority + int GetPriority() const { return m_prio; } + void SetPriority(int prio) { m_prio = prio; } + // state + thread_state GetState() const { return m_state; } + void SetState(thread_state state) { m_state = state; } + // id + pthread_t GetId() const { return thread_id; } + // "cancelled" flag + bool WasCancelled() const { return m_cancelled; } + +//private: -- should be! + pthread_t thread_id; + +private: + thread_state m_state; // see thread_state enum + int m_prio; // in wxWindows units: from 0 to 100 + + // set when the thread should terminate + bool m_cancelled; + + // this (mutex, cond) pair is used to synchronize the main thread and this + // thread in several situations: + // 1. The thread function blocks until condition is signaled by Run() when + // it's initially created - this allows create thread in "suspended" + // state + // 2. The Delete() function blocks until the condition is signaled when the + // thread exits. + wxMutex m_mutex; + wxCondition m_cond; + + // another (mutex, cond) pair for Pause()/Resume() usage + // + // VZ: it's possible that we might reuse the mutex and condition from above + // for this too, but as I'm not at all sure that it won't create subtle + // problems with race conditions between, say, Pause() and Delete() I + // prefer this may be a bit less efficient but much safer solution + wxMutex m_mutexSuspend; + wxCondition m_condSuspend; }; void *wxThreadInternal::PthreadStart(void *ptr) { wxThread *thread = (wxThread *)ptr; + wxThreadInternal *pthread = thread->p_internal; + + if ( pthread_setspecific(gs_keySelf, thread) != 0 ) + { + wxLogError(_("Can not start thread: error writing TLS.")); + + return (void *)-1; + } + + // wait for the condition to be signaled from Run() + // mutex state: currently locked by the thread which created us + pthread->m_cond.Wait(pthread->m_mutex); - /* Call the main entry */ - pthread_setcanceltype( PTHREAD_CANCEL_ASYNCHRONOUS, (int*) NULL ); + // mutex state: locked again on exit of Wait() + + // call the main entry void* status = thread->Entry(); + // terminate the thread thread->Exit(status); + wxFAIL_MSG("wxThread::Exit() can't return."); + return NULL; } -wxThreadError wxThread::Create() +wxThreadInternal::wxThreadInternal() { - pthread_attr_t a; - int min_prio, max_prio, p; - struct sched_param sp; + m_state = STATE_NEW; + m_cancelled = FALSE; - if (p_internal->state != STATE_IDLE) - return wxTHREAD_RUNNING; + // this mutex is locked during almost all thread lifetime - it will only be + // unlocked in the very end + m_mutex.Lock(); - /* Change thread priority */ - pthread_attr_init(&a); - pthread_attr_getschedpolicy(&a, &p); + // this mutex is used in Pause()/Resume() and is also locked all the time + // unless the thread is paused + m_mutexSuspend.Lock(); +} - min_prio = sched_get_priority_min(p); - max_prio = sched_get_priority_max(p); +wxThreadInternal::~wxThreadInternal() +{ + m_mutexSuspend.Unlock(); - pthread_attr_getschedparam(&a, &sp); - sp.sched_priority = min_prio + - (p_internal->prio*(max_prio-min_prio))/100; - pthread_attr_setschedparam(&a, &sp); + // note that m_mutex will be unlocked by the thread which waits for our + // termination +} - // this is the point of no return - p_internal->state = STATE_RUNNING; - if (pthread_create(&p_internal->thread_id, &a, - wxThreadInternal::PthreadStart, (void *)this) != 0) - { - p_internal->state = STATE_IDLE; - pthread_attr_destroy(&a); - return wxTHREAD_NO_RESOURCE; - } - pthread_attr_destroy(&a); +wxThreadError wxThreadInternal::Run() +{ + wxCHECK_MSG( GetState() == STATE_NEW, wxTHREAD_RUNNING, + "thread may only be started once after successful Create()" ); + + // the mutex was locked on Create(), so we will be able to lock it again + // only when the thread really starts executing and enters the wait - + // otherwise we might signal the condition before anybody is waiting for it + wxMutexLocker lock(m_mutex); + m_cond.Signal(); + + m_state = STATE_RUNNING; return wxTHREAD_NO_ERROR; + + // now the mutex is unlocked back - but just to allow Wait() function to + // terminate by relocking it, so the net result is that the worker thread + // starts executing and the mutex is still locked } -void wxThread::SetPriority(int prio) +void wxThreadInternal::Cancel() { - if (p_internal->state == STATE_RUNNING) - return; + // if the thread we're waiting for is waiting for the GUI mutex, we will + // deadlock so make sure we release it temporarily + if ( wxThread::IsMain() ) + wxMutexGuiLeave(); + + // nobody ever writes this variable so it's safe to not use any + // synchronization here + m_cancelled = TRUE; - if (prio > 100) prio = 100; - - if (prio < 0) prio = 0; - - p_internal->prio = prio; + // entering Wait() releases the mutex thus allowing SignalExit() to acquire + // it and to signal us its termination + m_cond.Wait(m_mutex); + + // mutex is still in the locked state - relocked on exit from Wait(), so + // unlock it - we don't need it any more, the thread has already terminated + m_mutex.Unlock(); + + // reacquire GUI mutex + if ( wxThread::IsMain() ) + wxMutexGuiEnter(); } -int wxThread::GetPriority() const +void wxThreadInternal::SignalExit() { - return p_internal->prio; + // as mutex is currently locked, this will block until some other thread + // (normally the same which created this one) unlocks it by entering Wait() + m_mutex.Lock(); + + // wake up all the threads waiting for our termination + m_cond.Broadcast(); + + // after this call mutex will be finally unlocked + m_mutex.Unlock(); } -void wxThread::DeferDestroy(bool on) +void wxThreadInternal::Pause() { - if (on) - pthread_setcanceltype(PTHREAD_CANCEL_DEFERRED, (int*) NULL); - else - pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, (int*) NULL); + wxCHECK_RET( m_state == STATE_PAUSED, + "thread must first be paused with wxThread::Pause()." ); + + // wait until the condition is signaled from Resume() + m_condSuspend.Wait(m_mutexSuspend); +} + +void wxThreadInternal::Resume() +{ + wxCHECK_RET( m_state == STATE_PAUSED, + "can't resume thread which is not suspended." ); + + // we will be able to lock this mutex only when Pause() starts waiting + wxMutexLocker lock(m_mutexSuspend); + m_condSuspend.Signal(); + + SetState(STATE_RUNNING); +} + +// ----------------------------------------------------------------------------- +// static functions +// ----------------------------------------------------------------------------- + +wxThread *wxThread::This() +{ + return (wxThread *)pthread_getspecific(gs_keySelf); +} + +bool wxThread::IsMain() +{ + return (bool)pthread_equal(pthread_self(), gs_tidMain); +} + +void wxThread::Yield() +{ +#ifdef HAVE_SCHED_YIELD + sched_yield(); +#else // !HAVE_SCHED_YIELD + // may be it will have the desired effect? + Sleep(0); +#endif // HAVE_SCHED_YIELD +} + +void wxThread::Sleep(unsigned long milliseconds) +{ + wxUsleep(milliseconds); } -wxThreadError wxThread::Destroy() +// ----------------------------------------------------------------------------- +// creating thread +// ----------------------------------------------------------------------------- + +wxThread::wxThread() { - int res = 0; + // add this thread to the global list of all threads + gs_allThreads.Add(this); - if (p_internal->state == STATE_RUNNING) + p_internal = new wxThreadInternal(); +} + +wxThreadError wxThread::Create() +{ + if (p_internal->GetState() != STATE_NEW) + return wxTHREAD_RUNNING; + + // set up the thread attribute: right now, we only set thread priority + pthread_attr_t attr; + pthread_attr_init(&attr); + +#ifdef HAVE_THREAD_PRIORITY_FUNCTIONS + int prio; + if ( pthread_attr_getschedpolicy(&attr, &prio) != 0 ) + { + wxLogError(_("Can not retrieve thread scheduling policy.")); + } + + int min_prio = sched_get_priority_min(prio), + max_prio = sched_get_priority_max(prio); + + if ( min_prio == -1 || max_prio == -1 ) + { + wxLogError(_("Can not get priority range for scheduling policy %d."), + prio); + } + else { - res = pthread_cancel(p_internal->thread_id); - if (res == 0) - p_internal->state = STATE_CANCELED; + struct sched_param sp; + pthread_attr_getschedparam(&attr, &sp); + sp.sched_priority = min_prio + + (p_internal->GetPriority()*(max_prio-min_prio))/100; + pthread_attr_setschedparam(&attr, &sp); + } +#endif // HAVE_THREAD_PRIORITY_FUNCTIONS + + // create the new OS thread object + int rc = pthread_create(&p_internal->thread_id, &attr, + wxThreadInternal::PthreadStart, (void *)this); + pthread_attr_destroy(&attr); + + if ( rc != 0 ) + { + p_internal->SetState(STATE_EXITED); + return wxTHREAD_NO_RESOURCE; } return wxTHREAD_NO_ERROR; } +wxThreadError wxThread::Run() +{ + return p_internal->Run(); +} + +// ----------------------------------------------------------------------------- +// misc accessors +// ----------------------------------------------------------------------------- + +void wxThread::SetPriority(unsigned int prio) +{ + wxCHECK_RET( ((int)WXTHREAD_MIN_PRIORITY <= (int)prio) && + ((int)prio <= (int)WXTHREAD_MAX_PRIORITY), + "invalid thread priority" ); + + wxCriticalSectionLocker lock(m_critsect); + + switch ( p_internal->GetState() ) + { + case STATE_NEW: + // thread not yet started, priority will be set when it is + p_internal->SetPriority(prio); + break; + + case STATE_RUNNING: + case STATE_PAUSED: +#ifdef HAVE_THREAD_PRIORITY_FUNCTIONS + { + struct sched_param sparam; + sparam.sched_priority = prio; + + if ( pthread_setschedparam(p_internal->GetId(), + SCHED_OTHER, &sparam) != 0 ) + { + wxLogError(_("Failed to set thread priority %d."), prio); + } + } +#endif // HAVE_THREAD_PRIORITY_FUNCTIONS + break; + + case STATE_EXITED: + default: + wxFAIL_MSG("impossible to set thread priority in this state"); + } +} + +unsigned int wxThread::GetPriority() const +{ + wxCriticalSectionLocker lock((wxCriticalSection &)m_critsect); + + return p_internal->GetPriority(); +} + +unsigned long wxThread::GetID() const +{ + return (unsigned long)p_internal->thread_id; +} + +// ----------------------------------------------------------------------------- +// pause/resume +// ----------------------------------------------------------------------------- + wxThreadError wxThread::Pause() { - if (p_internal->state != STATE_RUNNING) + wxCriticalSectionLocker lock(m_critsect); + + if ( p_internal->GetState() != STATE_RUNNING ) + { + wxLogDebug("Can't pause thread which is not running."); + return wxTHREAD_NOT_RUNNING; + } - if (!p_internal->defer_destroy) - return wxTHREAD_MISC_ERROR; + p_internal->SetState(STATE_PAUSED); - p_internal->state = STATE_PAUSING; return wxTHREAD_NO_ERROR; } wxThreadError wxThread::Resume() { - if (p_internal->state == STATE_PAUSING || p_internal->state == STATE_PAUSED) - p_internal->state = STATE_RUNNING; + wxCriticalSectionLocker lock(m_critsect); - return wxTHREAD_NO_ERROR; + if ( p_internal->GetState() == STATE_PAUSED ) + { + p_internal->Resume(); + + return wxTHREAD_NO_ERROR; + } + else + { + wxLogDebug("Attempt to resume a thread which is not paused."); + + return wxTHREAD_MISC_ERROR; + } } -void *wxThread::Join() +// ----------------------------------------------------------------------------- +// exiting thread +// ----------------------------------------------------------------------------- + +wxThread::ExitCode wxThread::Delete() { - void* status = 0; + m_critsect.Enter(); + thread_state state = p_internal->GetState(); + m_critsect.Leave(); - if (p_internal->state != STATE_IDLE) + switch ( state ) { - bool do_unlock = wxThread::IsMain(); + case STATE_NEW: + case STATE_EXITED: + // nothing to do + break; - while (p_internal->state == STATE_RUNNING) - wxYield(); + case STATE_PAUSED: + // resume the thread first + Resume(); - if (do_unlock) wxMainMutex->Unlock(); - - pthread_join(p_internal->thread_id, &status); - - if (do_unlock) wxMainMutex->Lock(); + // fall through - p_internal->state = STATE_IDLE; + default: + // set the flag telling to the thread to stop and wait + p_internal->Cancel(); } - - return status; + + return NULL; } -unsigned long wxThread::GetID() const +wxThreadError wxThread::Kill() { - return p_internal->thread_id; + switch ( p_internal->GetState() ) + { + case STATE_NEW: + case STATE_EXITED: + return wxTHREAD_NOT_RUNNING; + + default: +#ifdef HAVE_PTHREAD_CANCEL + if ( pthread_cancel(p_internal->GetId()) != 0 ) +#endif + { + wxLogError(_("Failed to terminate a thread.")); + + return wxTHREAD_MISC_ERROR; + } + + return wxTHREAD_NO_ERROR; + } } void wxThread::Exit(void *status) { - wxThread* ptr = this; + // first call user-level clean up code + OnExit(); + + // next wake up the threads waiting for us (OTOH, this function won't return + // until someone waited for us!) + p_internal->SignalExit(); + + p_internal->SetState(STATE_EXITED); - THREAD_SEND_EXIT_MSG(ptr); - p_internal->state = STATE_EXITED; + // delete both C++ thread object and terminate the OS thread object + delete this; pthread_exit(status); } +// also test whether we were paused bool wxThread::TestDestroy() { - if (p_internal->state == STATE_PAUSING) + wxCriticalSectionLocker lock((wxCriticalSection&)m_critsect); + + if ( p_internal->GetState() == STATE_PAUSED ) { - p_internal->state = STATE_PAUSED; - while (p_internal->state == STATE_PAUSED) - { - pthread_testcancel(); - usleep(1); - } + // leave the crit section or the other threads will stop too if they try + // to call any of (seemingly harmless) IsXXX() functions while we sleep + m_critsect.Leave(); + + p_internal->Pause(); + + // enter it back before it's finally left in lock object dtor + m_critsect.Enter(); } - // VZ: do I understand it correctly that it will terminate the thread all by - // itself if it was cancelled? - pthread_testcancel(); - - return FALSE; + return p_internal->WasCancelled(); } -bool wxThread::IsMain() +wxThread::~wxThread() { - return (bool)pthread_equal(pthread_self(), p_mainid); + // remove this thread from the global array + gs_allThreads.Remove(this); } +// ----------------------------------------------------------------------------- +// state tests +// ----------------------------------------------------------------------------- + bool wxThread::IsRunning() const { - return (p_internal->state == STATE_RUNNING); -} + wxCriticalSectionLocker lock((wxCriticalSection &)m_critsect); -bool wxThread::IsAlive() const -{ - return (p_internal->state == STATE_RUNNING) || - (p_internal->state == STATE_PAUSING) || - (p_internal->state == STATE_PAUSED); + return p_internal->GetState() == STATE_RUNNING; } -wxThread::wxThread() +bool wxThread::IsAlive() const { - p_internal = new wxThreadInternal(); -} + wxCriticalSectionLocker lock((wxCriticalSection&)m_critsect); -wxThread::~wxThread() -{ - Destroy(); - Join(); - delete p_internal; -} + switch ( p_internal->GetState() ) + { + case STATE_RUNNING: + case STATE_PAUSED: + return TRUE; -/* The default callback just joins the thread and throws away the result. */ -void wxThread::OnExit() -{ - Join(); + default: + return FALSE; + } } //-------------------------------------------------------------------- -// wxThreadModule +// wxThreadModule //-------------------------------------------------------------------- class wxThreadModule : public wxModule @@ -401,20 +716,43 @@ private: IMPLEMENT_DYNAMIC_CLASS(wxThreadModule, wxModule) -bool wxThreadModule::OnInit() +bool wxThreadModule::OnInit() { - wxMainMutex = new wxMutex(); + if ( pthread_key_create(&gs_keySelf, NULL /* dtor function */) != 0 ) + { + wxLogError(_("Thread module initialization failed: " + "failed to create pthread key.")); + + return FALSE; + } + + gs_mutexGui = new wxMutex(); wxThreadGuiInit(); - p_mainid = (int)getpid(); - wxMainMutex->Lock(); + gs_tidMain = pthread_self(); + gs_mutexGui->Lock(); + return TRUE; } void wxThreadModule::OnExit() { - wxMainMutex->Unlock(); - wxThreadGuiExit(); - delete wxMainMutex; -} + wxASSERT_MSG( wxThread::IsMain(), "only main thread can be here" ); + + // terminate any threads left + size_t count = gs_allThreads.GetCount(); + if ( count != 0u ) + wxLogDebug("Some threads were not terminated by the application."); + for ( size_t n = 0u; n < count; n++ ) + { + gs_allThreads[n]->Delete(); + } + // destroy GUI mutex + gs_mutexGui->Unlock(); + wxThreadGuiExit(); + delete gs_mutexGui; + + // and free TLD slot + (void)pthread_key_delete(gs_keySelf); +}