]> git.saurik.com Git - wxWidgets.git/blobdiff - src/gtk1/threadpsx.cpp
wxWindow::OnClose() removed completely from wxGTK, wxCloseEvent is now
[wxWidgets.git] / src / gtk1 / threadpsx.cpp
index 63dc81b2c99412f8649fa0b3051db345f5eb3423..79542d5e192882a87861188ff6b3dbfea461d838 100644 (file)
 /////////////////////////////////////////////////////////////////////////////
 
 #ifdef __GNUG__
-#pragma implementation "thread.h"
+    #pragma implementation "thread.h"
 #endif
 
 #include <stdio.h>
 #include <unistd.h>
 #include <pthread.h>
 #include <errno.h>
+
+#ifdef __linux__
+    #include <sched.h>
+#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
-//--------------------------------------------------------------------
+// -----------------------------------------------------------------------------
 
-static pthread_t p_mainid;
+// 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;
 
-wxMutex *wxMainMutex; /* controls access to all GUI functions */
+// the id of the main thread
+static pthread_t gs_tidMain;
+
+// the key for the pointer to the associated wxThread object
+static pthread_key_t gs_keySelf;
+
+// this mutex must be acquired before any call to a GUI function
+static wxMutex *gs_mutexGui;
 
 //--------------------------------------------------------------------
 // common GUI thread code
@@ -55,7 +73,7 @@ wxMutex *wxMainMutex; /* controls access to all GUI functions */
 // wxMutex (Posix implementation)
 //--------------------------------------------------------------------
 
-class wxMutexInternal 
+class wxMutexInternal
 {
 public:
   pthread_mutex_t p_mutex;
@@ -71,7 +89,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 +100,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 +116,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 +136,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 +150,7 @@ wxMutexError wxMutex::Unlock()
 // wxCondition (Posix implementation)
 //--------------------------------------------------------------------
 
-class wxConditionInternal 
+class wxConditionInternal
 {
 public:
   pthread_cond_t p_condition;
@@ -143,7 +165,7 @@ wxCondition::wxCondition()
 wxCondition::~wxCondition()
 {
     pthread_cond_destroy( &(p_internal->p_condition) );
-    
+
     delete p_internal;
 }
 
@@ -175,215 +197,496 @@ 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;
+    }
 
-    /* Call the main entry */
-    pthread_setcanceltype( PTHREAD_CANCEL_ASYNCHRONOUS, (int*) NULL );
+    // 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);
+
+    // 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 wxThreadInternal::Cancel()
+{
+    // 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;
+
+    // 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();
 }
 
-void wxThread::SetPriority(int prio)
+void wxThreadInternal::SignalExit()
 {
-    if (p_internal->state == STATE_RUNNING)
-        return;
+    // 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();
 
-    if (prio > 100) prio = 100;
-       
-    if (prio < 0) prio = 0;
-    
-    p_internal->prio = prio;
+    // after this call mutex will be finally unlocked
+    m_mutex.Unlock();
 }
 
-int wxThread::GetPriority() const
+void wxThreadInternal::Pause()
 {
-    return p_internal->prio;
+    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 wxThread::DeferDestroy(bool on)
+void wxThreadInternal::Resume()
 {
-    if (on)
-        pthread_setcanceltype(PTHREAD_CANCEL_DEFERRED, (int*) NULL);
-    else
-        pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, (int*) NULL);
+    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()
+{
+    sched_yield();
+}
+
+void wxThread::Sleep(unsigned long milliseconds)
+{
+    // FIXME how to test for nanosleep() availability?
+
+    usleep(milliseconds * 1000); // usleep(3) wants microseconds
 }
 
-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);
+
+    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 )
     {
-        res = pthread_cancel(p_internal->thread_id);
-        if (res == 0)
-           p_internal->state = STATE_CANCELED;
+        wxLogError(_("Can not get priority range for scheduling policy %d."),
+                   prio);
+    }
+    else
+    {
+        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);
+    }
+
+    // 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( (WXTHREAD_MIN_PRIORITY <= prio) &&
+                 (prio <= 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:
+            {
+                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);
+                }
+            }
+            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:
+            if ( pthread_cancel(p_internal->GetId()) != 0 )
+            {
+                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();
 
-    THREAD_SEND_EXIT_MSG(ptr);
-    p_internal->state = STATE_EXITED;
+    p_internal->SetState(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();
     }
-    pthread_testcancel();
-    
-    return TRUE; /* what is this for? */
+
+    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
@@ -398,20 +701,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);
+}