X-Git-Url: https://git.saurik.com/wxWidgets.git/blobdiff_plain/e9576ca53db96b462ed4c0b4bdf47d64c40203e4..8064223b7b1b3657363b7a635c381b9269d95e55:/src/mac/carbon/thread.cpp?ds=inline diff --git a/src/mac/carbon/thread.cpp b/src/mac/carbon/thread.cpp index 82fff92ba3..04383b0f94 100644 --- a/src/mac/carbon/thread.cpp +++ b/src/mac/carbon/thread.cpp @@ -1,261 +1,1774 @@ ///////////////////////////////////////////////////////////////////////////// -// Name: thread.cpp -// Purpose: wxThread Implementation. For Unix ports, see e.g. src/gtk -// Author: Original from Wolfram Gloger/Guilhem Lavaux -// Modified by: +// Name: src/mac/carbon/thread.cpp +// Purpose: wxThread Implementation +// Author: Original from Wolfram Gloger/Guilhem Lavaux/Vadim Zeitlin +// Modified by: Aj Lavin, Stefan Csomor // Created: 04/22/98 // RCS-ID: $Id$ -// Copyright: (c) Wolfram Gloger (1996, 1997); Guilhem Lavaux (1998) +// Copyright: (c) Wolfram Gloger (1996, 1997); Guilhem Lavaux (1998), +// Vadim Zeitlin (1999), Stefan Csomor (2000) // Licence: wxWindows licence ///////////////////////////////////////////////////////////////////////////// -#ifdef __GNUG__ -#pragma implementation "thread.h" +#include "wx/wxprec.h" + +#if defined(__BORLANDC__) + #pragma hdrstop +#endif + +#ifndef WX_PRECOMP + #include "wx/wx.h" + #include "wx/module.h" #endif -#include "wx/module.h" +#if wxUSE_THREADS + #include "wx/thread.h" -#include "wx/utils.h" -enum thread_state { - STATE_IDLE = 0, - STATE_RUNNING, - STATE_CANCELED, - STATE_EXITED +#include +#include "wx/mac/uma.h" + +// the possible states of the thread: +// ("=>" shows all possible transitions from this state) +enum wxThreadState +{ + STATE_NEW, // didn't start execution yet (=> RUNNING) + STATE_RUNNING, // thread is running (=> PAUSED, CANCELED) + STATE_PAUSED, // thread is temporarily suspended (=> RUNNING) + STATE_CANCELED, // thread should terminate a.s.a.p. (=> EXITED) + STATE_EXITED // thread is terminating }; -///////////////////////////////////////////////////////////////////////////// -// Static variables -///////////////////////////////////////////////////////////////////////////// +// ---------------------------------------------------------------------------- +// globals +// ---------------------------------------------------------------------------- -wxMutex *wxMainMutex; // controls access to all GUI functions +// the task ID of the main thread +static wxThreadIdType gs_idMainThread = kInvalidID; + +// this is the Per-Task Storage for the pointer to the appropriate wxThread +TaskStorageIndex gs_tlsForWXThread = 0; + +// if it's false, some secondary thread is holding the GUI lock +static bool gs_bGuiOwnedByMainThread = true; + +// critical section which controls access to all GUI functions: any secondary +// thread (i.e. except the main one) must enter this crit section before doing +// any GUI calls +static wxCriticalSection *gs_critsectGui = NULL; + +// critical section which protects gs_nWaitingForGui variable +static wxCriticalSection *gs_critsectWaitingForGui = NULL; + +// number of threads waiting for GUI in wxMutexGuiEnter() +static size_t gs_nWaitingForGui = 0; + +// overall number of threads, needed for determining +// the sleep value of the main event loop +size_t g_numberOfThreads = 0; -///////////////////////////////////////////////////////////////////////////// -// Windows implementation -///////////////////////////////////////////////////////////////////////////// -class wxMutexInternal { +#if wxUSE_GUI +MPCriticalRegionID gs_guiCritical = kInvalidID; +#endif + +// ============================================================================ +// MacOS implementation of thread classes +// ============================================================================ + +/* + Notes : + + The implementation is very close to the phtreads implementation, the reason for + using MPServices is the fact that these are also available under OS 9. Thus allowing + for one common API for all current builds. + + As soon as wxThreads are on a 64 bit address space, the TLS must be extended + to use two indices one for each 32 bit part as the MP implementation is limited + to longs. + + I have three implementations for mutexes : + version A based on a binary semaphore, problem - not reentrant, version B based + on a critical region, allows for reentrancy, performance implications not + yet tested, and third a plain pthreads implementation + + The same for condition internal, one implementation by Aj Lavin and the other one + copied from the thrimpl.cpp which I assume has been more broadly tested, I've just + replaced the interlock increment with the appropriate PPC calls +*/ + +// ---------------------------------------------------------------------------- +// wxCriticalSection +// ---------------------------------------------------------------------------- + +wxCriticalSection::wxCriticalSection() +{ + MPCreateCriticalRegion( (MPCriticalRegionID*) &m_critRegion ); +} + +wxCriticalSection::~wxCriticalSection() +{ + MPDeleteCriticalRegion( (MPCriticalRegionID) m_critRegion ); +} + +void wxCriticalSection::Enter() +{ + MPEnterCriticalRegion( (MPCriticalRegionID) m_critRegion, kDurationForever ); +} + +void wxCriticalSection::Leave() +{ + MPExitCriticalRegion( (MPCriticalRegionID) m_critRegion ); +} + +// ---------------------------------------------------------------------------- +// wxMutex implementation +// ---------------------------------------------------------------------------- + +#define wxUSE_MAC_SEMAPHORE_MUTEX 0 +#define wxUSE_MAC_CRITICAL_REGION_MUTEX 1 +#define wxUSE_MAC_PTHREADS_MUTEX 0 + +#if wxUSE_MAC_PTHREADS_MUTEX + +#include + + +class wxMutexInternal +{ public: - // TODO: internal mutex handle + wxMutexInternal( wxMutexType mutexType ); + ~wxMutexInternal(); + + wxMutexError Lock(); + wxMutexError TryLock(); + wxMutexError Unlock(); + + bool IsOk() const + { return m_isOk; } + +private: + pthread_mutex_t m_mutex; + bool m_isOk; + + // wxConditionInternal uses our m_mutex + friend class wxConditionInternal; }; -wxMutex::wxMutex() +#ifdef HAVE_PTHREAD_MUTEXATTR_T +// on some systems pthread_mutexattr_settype() is not in the headers (but it is +// in the library, otherwise we wouldn't compile this code at all) +extern "C" int pthread_mutexattr_settype( pthread_mutexattr_t *, int ); +#endif + +wxMutexInternal::wxMutexInternal( wxMutexType mutexType ) { - p_internal = new wxMutexInternal; - // TODO: create internal mutext handle - m_locked = 0; + int err; + switch ( mutexType ) + { + case wxMUTEX_RECURSIVE: + // support recursive locks like Win32, i.e. a thread can lock a + // mutex which it had itself already locked + // + // unfortunately initialization of recursive mutexes is non + // portable, so try several methods +#ifdef HAVE_PTHREAD_MUTEXATTR_T + { + pthread_mutexattr_t attr; + pthread_mutexattr_init( &attr ); + pthread_mutexattr_settype( &attr, PTHREAD_MUTEX_RECURSIVE ); + + err = pthread_mutex_init( &m_mutex, &attr ); + } +#elif defined(HAVE_PTHREAD_RECURSIVE_MUTEX_INITIALIZER) + // we can use this only as initializer so we have to assign it + // first to a temp var - assigning directly to m_mutex wouldn't + // even compile + { + pthread_mutex_t mutex = PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP; + m_mutex = mutex; + } +#else // no recursive mutexes + err = EINVAL; +#endif // HAVE_PTHREAD_MUTEXATTR_T/... + break; + + default: + wxFAIL_MSG( wxT("unknown mutex type") ); + // fall through + + case wxMUTEX_DEFAULT: + err = pthread_mutex_init( &m_mutex, NULL ); + break; + } + + m_isOk = err == 0; + if ( !m_isOk ) + { + wxLogApiError( wxT("pthread_mutex_init()"), err ); + } } -wxMutex::~wxMutex() +wxMutexInternal::~wxMutexInternal() { - if (m_locked > 0) - wxDebugMsg("wxMutex warning: freeing a locked mutex (%d locks)\n", m_locked); - // TODO: free internal mutext handle + if ( m_isOk ) + { + int err = pthread_mutex_destroy( &m_mutex ); + if ( err != 0 ) + { + wxLogApiError( wxT("pthread_mutex_destroy()"), err ); + } + } +} + +wxMutexError wxMutexInternal::Lock() +{ + int err = pthread_mutex_lock( &m_mutex ); + switch ( err ) + { + case EDEADLK: + // only error checking mutexes return this value and so it's an + // unexpected situation -- hence use assert, not wxLogDebug + wxFAIL_MSG( wxT("mutex deadlock prevented") ); + return wxMUTEX_DEAD_LOCK; + + case EINVAL: + wxLogDebug( wxT("pthread_mutex_lock(): mutex not initialized.") ); + break; + + case 0: + return wxMUTEX_NO_ERROR; + + default: + wxLogApiError( wxT("pthread_mutex_lock()"), err ); + } + + return wxMUTEX_MISC_ERROR; } -wxMutexError wxMutex::Lock() +wxMutexError wxMutexInternal::TryLock() { - // TODO - m_locked++; + int err = pthread_mutex_trylock( &m_mutex ); + switch ( err ) + { + case EBUSY: + // not an error: mutex is already locked, but we're prepared for this case + return wxMUTEX_BUSY; + + case EINVAL: + wxLogDebug( wxT("pthread_mutex_trylock(): mutex not initialized.") ); + break; + + case 0: + return wxMUTEX_NO_ERROR; + + default: + wxLogApiError( wxT("pthread_mutex_trylock()"), err ); + } + + return wxMUTEX_MISC_ERROR; +} + +wxMutexError wxMutexInternal::Unlock() +{ + int err = pthread_mutex_unlock( &m_mutex ); + switch ( err ) + { + case EPERM: + // we don't own the mutex + return wxMUTEX_UNLOCKED; + + case EINVAL: + wxLogDebug( wxT("pthread_mutex_unlock(): mutex not initialized.") ); + break; + + case 0: + return wxMUTEX_NO_ERROR; + + default: + wxLogApiError( wxT("pthread_mutex_unlock()"), err ); + } + + return wxMUTEX_MISC_ERROR; +} + +#endif + +#if wxUSE_MAC_SEMAPHORE_MUTEX + +class wxMutexInternal +{ +public: + wxMutexInternal( wxMutexType mutexType ); + virtual ~wxMutexInternal(); + + bool IsOk() const + { return m_isOk; } + + wxMutexError Lock(); + wxMutexError TryLock(); + wxMutexError Unlock(); + +private: + MPSemaphoreID m_semaphore; + bool m_isOk; +}; + +wxMutexInternal::wxMutexInternal(wxMutexType mutexType ) +{ + m_isOk = false; + m_semaphore = kInvalidID; + OSStatus err = noErr; + + switch ( mutexType ) + { + case wxMUTEX_DEFAULT : + verify_noerr( MPCreateBinarySemaphore( &m_semaphore ) ); + m_isOk = ( m_semaphore != kInvalidID ); + break; + + case wxMUTEX_RECURSIVE : + wxFAIL_MSG( wxT("Recursive Mutex not supported yet") ); + break; + + default : + wxFAIL_MSG( wxT("Unknown mutex type") ); + break; + } +} + +wxMutexInternal::~wxMutexInternal() +{ + if ( m_semaphore != kInvalidID ) + MPDeleteSemaphore( m_semaphore ); + + MPYield(); +} + +wxMutexError wxMutexInternal::Lock() +{ + wxCHECK_MSG( m_isOk, wxMUTEX_MISC_ERROR, wxT("Invalid Mutex") ); + OSStatus err = MPWaitOnSemaphore( m_semaphore, kDurationForever ); + if (err != noErr) + { + wxLogSysError( wxT("Could not lock mutex") ); + + return wxMUTEX_MISC_ERROR; + } + return wxMUTEX_NO_ERROR; } -wxMutexError wxMutex::TryLock() +wxMutexError wxMutexInternal::TryLock() { - // TODO - m_locked++; + wxCHECK_MSG( m_isOk, wxMUTEX_MISC_ERROR, wxT("Invalid Mutex") ); + OSStatus err = MPWaitOnSemaphore( m_semaphore, kDurationImmediate ); + if (err != noErr) + { + if (err == kMPTimeoutErr) + return wxMUTEX_BUSY; + + wxLogSysError( wxT("Could not try lock mutex") ); + + return wxMUTEX_MISC_ERROR; + } + return wxMUTEX_NO_ERROR; } -wxMutexError wxMutex::Unlock() +wxMutexError wxMutexInternal::Unlock() { - if (m_locked > 0) - m_locked--; + wxCHECK_MSG( m_isOk, wxMUTEX_MISC_ERROR, wxT("Invalid Mutex") ); + OSStatus err = MPSignalSemaphore( m_semaphore ); + + MPYield(); + if (err != noErr) + { + wxLogSysError( wxT("Could not unlock mutex") ); + return wxMUTEX_MISC_ERROR; + } - // TODO return wxMUTEX_NO_ERROR; } -class wxConditionInternal { +#endif + +#if wxUSE_MAC_CRITICAL_REGION_MUTEX + +class wxMutexInternal +{ public: - // TODO: internal handle - int waiters; + wxMutexInternal( wxMutexType mutexType ); + virtual ~wxMutexInternal(); + + bool IsOk() const { return m_isOk; } + + wxMutexError Lock() { return Lock(kDurationForever); } + wxMutexError Lock(unsigned long ms); + wxMutexError TryLock(); + wxMutexError Unlock(); + +private: + MPCriticalRegionID m_critRegion; + bool m_isOk ; }; -wxCondition::wxCondition() +wxMutexInternal::wxMutexInternal( wxMutexType WXUNUSED(mutexType) ) +{ + m_isOk = false; + m_critRegion = kInvalidID; + + verify_noerr( MPCreateCriticalRegion( &m_critRegion ) ); + m_isOk = ( m_critRegion != kInvalidID ); + if ( !IsOk() ) + { + wxFAIL_MSG( wxT("Error when creating mutex") ); + } +} + +wxMutexInternal::~wxMutexInternal() +{ + if ( m_critRegion != kInvalidID ) + MPDeleteCriticalRegion( m_critRegion ); + + MPYield(); +} + +wxMutexError wxMutexInternal::Lock(unsigned long ms) +{ + wxCHECK_MSG( m_isOk , wxMUTEX_MISC_ERROR , wxT("Invalid Mutex") ); + + OSStatus err = MPEnterCriticalRegion( m_critRegion, ms ); + switch ( err ) + { + case noErr: + break; + + case kMPTimeoutErr: + wxASSERT_MSG( ms != kDurationForever, wxT("unexpected timeout") ); + return wxMUTEX_TIMEOUT; + + default: + wxLogSysError(wxT("Could not lock mutex")); + return wxMUTEX_MISC_ERROR; + } + + return wxMUTEX_NO_ERROR; +} + +wxMutexError wxMutexInternal::TryLock() { - p_internal = new wxConditionInternal; - // TODO: create internal handle - p_internal->waiters = 0; + wxCHECK_MSG( m_isOk , wxMUTEX_MISC_ERROR , wxT("Invalid Mutex") ) ; + + OSStatus err = MPEnterCriticalRegion( m_critRegion, kDurationImmediate); + if (err != noErr) + { + if ( err == kMPTimeoutErr) + return wxMUTEX_BUSY; + + wxLogSysError( wxT("Could not try lock mutex") ); + return wxMUTEX_MISC_ERROR; + } + + return wxMUTEX_NO_ERROR; } -wxCondition::~wxCondition() +wxMutexError wxMutexInternal::Unlock() { - // TODO: destroy internal handle + wxCHECK_MSG( m_isOk , wxMUTEX_MISC_ERROR , wxT("Invalid Mutex") ) ; + + OSStatus err = MPExitCriticalRegion( m_critRegion ); + MPYield() ; + + if (err != noErr) + { + wxLogSysError( wxT("Could not unlock mutex") ); + + return wxMUTEX_MISC_ERROR; + } + + return wxMUTEX_NO_ERROR; } -void wxCondition::Wait(wxMutex& mutex) +#endif + +// -------------------------------------------------------------------------- +// wxSemaphore +// -------------------------------------------------------------------------- + +class wxSemaphoreInternal { - mutex.Unlock(); - p_internal->waiters++; - // TODO wait here - p_internal->waiters--; - mutex.Lock(); +public: + wxSemaphoreInternal( int initialcount, int maxcount ); + virtual ~wxSemaphoreInternal(); + + bool IsOk() const + { return m_isOk; } + + wxSemaError Post(); + wxSemaError WaitTimeout( unsigned long milliseconds ); + + wxSemaError Wait() + { return WaitTimeout( kDurationForever); } + + wxSemaError TryWait() + { + wxSemaError err = WaitTimeout( kDurationImmediate ); + if (err == wxSEMA_TIMEOUT) + err = wxSEMA_BUSY; + + return err; + } + +private: + MPSemaphoreID m_semaphore; + bool m_isOk; +}; + +wxSemaphoreInternal::wxSemaphoreInternal( int initialcount, int maxcount) +{ + m_isOk = false; + m_semaphore = kInvalidID; + if ( maxcount == 0 ) + // make it practically infinite + maxcount = INT_MAX; + + verify_noerr( MPCreateSemaphore( maxcount, initialcount, &m_semaphore ) ); + m_isOk = ( m_semaphore != kInvalidID ); + + if ( !IsOk() ) + { + wxFAIL_MSG( wxT("Error when creating semaphore") ); + } +} + +wxSemaphoreInternal::~wxSemaphoreInternal() +{ + if (m_semaphore != kInvalidID) + MPDeleteSemaphore( m_semaphore ); + + MPYield(); } -bool wxCondition::Wait(wxMutex& mutex, unsigned long sec, - unsigned long nsec) +wxSemaError wxSemaphoreInternal::WaitTimeout( unsigned long milliseconds ) { - mutex.Unlock(); - p_internal->waiters++; + OSStatus err = MPWaitOnSemaphore( m_semaphore, milliseconds ); + if (err != noErr) + { + if (err == kMPTimeoutErr) + return wxSEMA_TIMEOUT; - // TODO wait here - p_internal->waiters--; - mutex.Lock(); + return wxSEMA_MISC_ERROR; + } - return FALSE; + return wxSEMA_NO_ERROR; } -void wxCondition::Signal() +wxSemaError wxSemaphoreInternal::Post() { - // TODO + OSStatus err = MPSignalSemaphore( m_semaphore ); + MPYield(); + if (err != noErr) + return wxSEMA_MISC_ERROR; + + return wxSEMA_NO_ERROR; } -void wxCondition::Broadcast() +// ---------------------------------------------------------------------------- +// wxCondition implementation +// ---------------------------------------------------------------------------- + +#if 0 + +class wxConditionInternal +{ +public: + wxConditionInternal( wxMutex& mutex ) + : + m_mutex( mutex ), + m_semaphore( 0, 1 ), + m_gate( 1, 1 ) + { + m_waiters = 0; + m_signals = 0; + m_canceled = 0; + } + + virtual ~wxConditionInternal() {} + + bool IsOk() const + { return m_mutex.IsOk(); } + + wxCondError Wait() + { return WaitTimeout( kDurationForever ); } + + wxCondError WaitTimeout( unsigned long msectimeout ); + + wxCondError Signal() + { return DoSignal( false); } + + wxCondError Broadcast() + { return DoSignal( true ); } + +private: + wxCondError DoSignal( bool signalAll ); + + wxMutex& m_mutex; + wxSemaphoreInternal m_semaphore; // Signals the waiting threads. + wxSemaphoreInternal m_gate; + wxCriticalSection m_varSection; + size_t m_waiters; // Number of threads waiting for a signal. + size_t m_signals; // Number of signals to send. + size_t m_canceled; // Number of canceled waiters in m_waiters. +}; + +wxCondError wxConditionInternal::WaitTimeout( unsigned long msectimeout ) { - // TODO + m_gate.Wait(); + + if ( ++ m_waiters == INT_MAX ) + { + m_varSection.Enter(); + + m_waiters -= m_canceled; + m_signals -= m_canceled; + m_canceled = 0; + + m_varSection.Leave(); + } + + m_gate.Post(); + m_mutex.Unlock(); + + wxSemaError err = m_semaphore.WaitTimeout( msectimeout); + wxASSERT( err == wxSEMA_NO_ERROR || err == wxSEMA_TIMEOUT); + + m_varSection.Enter(); + + if ( err != wxSEMA_NO_ERROR ) + { + if ( m_signals > m_canceled ) + { + // A signal is being sent after we timed out. + if ( m_waiters == m_signals ) + { + // There are no excess waiters to catch the signal, so + // we must throw it away. + wxSemaError err2 = m_semaphore.Wait(); + if ( err2 != wxSEMA_NO_ERROR ) + { + wxLogSysError( wx("Error while waiting on semaphore") ); + } + + wxASSERT( err2 == wxSEMA_NO_ERROR); + + --m_waiters; + if ( --m_signals == m_canceled ) + { + // This was the last signal. open the gate. + wxASSERT( m_waiters == m_canceled ); + m_gate.Post(); + } + } + else + { + // There are excess waiters to catch the signal, leave it be. + --m_waiters; + } + } + else + { + // No signals is being sent: + // the gate may be open or closed, so we can't touch m_waiters. + ++m_canceled; + ++m_signals; + } + } + else + { + // We caught a signal. + wxASSERT( m_signals > m_canceled ); + + --m_waiters; + + if ( --m_signals == m_canceled) + { + // This was the last signal. open the gate. + wxASSERT( m_waiters == m_canceled ); + + m_gate.Post(); + } + } + + m_varSection.Leave(); + m_mutex.Lock(); + + if (err != noErr) + return err == wxSEMA_TIMEOUT ? wxCOND_TIMEOUT : wxCOND_MISC_ERROR; + + return wxCOND_NO_ERROR; } -class wxThreadInternal { + +wxCondError wxConditionInternal::DoSignal( bool signalAll) +{ + m_gate.Wait(); + m_varSection.Enter(); + + wxASSERT( m_signals == m_canceled ); + + if ( m_waiters == m_canceled) + { + m_varSection.Leave(); + m_gate.Post(); + return wxCOND_NO_ERROR; + } + + if ( m_canceled > 0) + { + m_waiters -= m_canceled; + m_signals = 0; + m_canceled = 0; + } + + m_signals = signalAll ? m_waiters : 1; + size_t n = m_signals; + + m_varSection.Leave(); + + // Let the waiters inherit the gate lock. + + do + { + wxSemaError err = m_semaphore.Post(); + wxASSERT( err == wxSEMA_NO_ERROR ); + } + while ( --n ); + + return wxCOND_NO_ERROR; +} + +#else +class wxConditionInternal +{ public: - // TODO + wxConditionInternal( wxMutex& mutex ); + + bool IsOk() const + { return m_mutex.IsOk() && m_semaphore.IsOk(); } + + wxCondError Wait(); + wxCondError WaitTimeout( unsigned long milliseconds ); + + wxCondError Signal(); + wxCondError Broadcast(); + +private: + // the number of threads currently waiting for this condition + SInt32 m_numWaiters; + + // the critical section protecting m_numWaiters + wxCriticalSection m_csWaiters; + + wxMutex& m_mutex; + wxSemaphore m_semaphore; + + DECLARE_NO_COPY_CLASS(wxConditionInternal) }; -wxThreadError wxThread::Create() +wxConditionInternal::wxConditionInternal( wxMutex& mutex ) + : m_mutex(mutex) +{ + // another thread can't access it until we return from ctor, so no need to + // protect access to m_numWaiters here + m_numWaiters = 0; +} + +wxCondError wxConditionInternal::Wait() +{ + // increment the number of waiters + IncrementAtomic( &m_numWaiters ); + + m_mutex.Unlock(); + + // a potential race condition can occur here + // + // after a thread increments nwaiters, and unlocks the mutex and before the + // semaphore.Wait() is called, if another thread can cause a signal to be + // generated + // + // this race condition is handled by using a semaphore and incrementing the + // semaphore only if 'nwaiters' is greater that zero since the semaphore, + // can 'remember' signals the race condition will not occur + + // wait ( if necessary ) and decrement semaphore + wxSemaError err = m_semaphore.Wait(); + m_mutex.Lock(); + + return err == wxSEMA_NO_ERROR ? wxCOND_NO_ERROR : wxCOND_MISC_ERROR; +} + +wxCondError wxConditionInternal::WaitTimeout( unsigned long milliseconds ) { - // TODO + IncrementAtomic( &m_numWaiters ); + + m_mutex.Unlock(); + + // a race condition can occur at this point in the code + // + // please see the comments in Wait(), for details + + wxSemaError err = m_semaphore.WaitTimeout(milliseconds); + + if ( err == wxSEMA_TIMEOUT ) + { + // another potential race condition exists here it is caused when a + // 'waiting' thread timesout, and returns from WaitForSingleObject, but + // has not yet decremented 'nwaiters'. + // + // at this point if another thread calls signal() then the semaphore + // will be incremented, but the waiting thread will miss it. + // + // to handle this particular case, the waiting thread calls + // WaitForSingleObject again with a timeout of 0, after locking + // 'nwaiters_mutex'. this call does not block because of the zero + // timeout, but will allow the waiting thread to catch the missed + // signals. + wxCriticalSectionLocker lock(m_csWaiters); + + err = m_semaphore.WaitTimeout(0); + + if ( err != wxSEMA_NO_ERROR ) + { + m_numWaiters--; + } + } + + m_mutex.Lock(); + + return err == wxSEMA_NO_ERROR ? wxCOND_NO_ERROR : wxCOND_MISC_ERROR; +} + +wxCondError wxConditionInternal::Signal() +{ + wxCriticalSectionLocker lock(m_csWaiters); + + if ( m_numWaiters > 0 ) + { + // increment the semaphore by 1 + if ( m_semaphore.Post() != wxSEMA_NO_ERROR ) + return wxCOND_MISC_ERROR; + + m_numWaiters--; + } + + return wxCOND_NO_ERROR; +} + +wxCondError wxConditionInternal::Broadcast() +{ + wxCriticalSectionLocker lock(m_csWaiters); + + while ( m_numWaiters > 0 ) + { + if ( m_semaphore.Post() != wxSEMA_NO_ERROR ) + return wxCOND_MISC_ERROR; + + m_numWaiters--; + } + + return wxCOND_NO_ERROR; +} +#endif + +// ---------------------------------------------------------------------------- +// wxCriticalSection implementation +// ---------------------------------------------------------------------------- + +// XXX currently implemented as mutex in headers. Change to critical section. + +// ---------------------------------------------------------------------------- +// wxThread implementation +// ---------------------------------------------------------------------------- + +// wxThreadInternal class +// ---------------------- + +class wxThreadInternal +{ +public: + wxThreadInternal() + { + m_tid = kInvalidID; + m_state = STATE_NEW; + m_prio = WXTHREAD_DEFAULT_PRIORITY; + m_notifyQueueId = kInvalidID; + m_exitcode = 0; + m_cancelled = false ; + + // set to true only when the thread starts waiting on m_semSuspend + m_isPaused = false; + + // defaults for joinable threads + m_shouldBeJoined = true; + m_isDetached = false; + } + + virtual ~wxThreadInternal() + { + if ( m_notifyQueueId) + { + MPDeleteQueue( m_notifyQueueId ); + m_notifyQueueId = kInvalidID ; + } + } + + // thread function + static OSStatus MacThreadStart(void* arg); + + // create a new (suspended) thread (for the given thread object) + bool Create(wxThread *thread, unsigned int stackSize); + + // thread actions + + // start the thread + wxThreadError Run(); + + // unblock the thread allowing it to run + void SignalRun() { m_semRun.Post(); } + + // ask the thread to terminate + void Wait(); + + // 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); + + // state + wxThreadState GetState() const + { return m_state; } + void SetState(wxThreadState state) + { m_state = state; } + + // Get the ID of this thread's underlying MP Services task. + MPTaskID GetId() const + { return m_tid; } + + void SetCancelFlag() + { m_cancelled = true; } + + bool WasCancelled() const + { return m_cancelled; } + + // exit code + void SetExitCode(wxThread::ExitCode exitcode) + { m_exitcode = 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 + void Detach() + { + wxCriticalSectionLocker lock(m_csJoinFlag); + + m_shouldBeJoined = false; + m_isDetached = true; + } + +private: + // the thread we're associated with + wxThread * m_thread; + + MPTaskID m_tid; // thread id + MPQueueID m_notifyQueueId; // its notification queue + + wxThreadState m_state; // see wxThreadState enum + int m_prio; // in wxWidgets units: from 0 to 100 + + // this flag is set when the thread should terminate + bool m_cancelled; + + // this flag is set when the thread is blocking on m_semSuspend + bool m_isPaused; + + // the thread exit code - only used for joinable (!detached) threads and + // is only valid after the thread termination + wxThread::ExitCode m_exitcode; + + // many threads may call Wait(), but only one of them should call + // pthread_join(), so we have to keep track of this + wxCriticalSection m_csJoinFlag; + bool m_shouldBeJoined; + bool m_isDetached; + + // this semaphore is posted by Run() and the threads Entry() is not + // called before it is done + wxSemaphore m_semRun; + + // this one is signaled when the thread should resume after having been + // Pause()d + wxSemaphore m_semSuspend; +}; + +OSStatus wxThreadInternal::MacThreadStart(void *parameter) +{ + wxThread* thread = (wxThread*) parameter ; + wxThreadInternal *pthread = thread->m_internal; + + // add to TLS so that This() will work + verify_noerr( MPSetTaskStorageValue( gs_tlsForWXThread , (TaskStorageValue) thread ) ) ; + + // have to declare this before pthread_cleanup_push() which defines a + // block! + bool dontRunAtAll; + + // wait for the semaphore to be posted from Run() + pthread->m_semRun.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 ) + { + pthread->m_exitcode = thread->Entry(); + + { + wxCriticalSectionLocker lock(thread->m_critsect); + pthread->SetState( STATE_EXITED ); + } + } + + if ( dontRunAtAll ) + { + if ( pthread->m_isDetached ) + delete thread; + + return -1; + } + else + { + // on Mac for the running code, + // the correct thread termination is to return + + // terminate the thread + thread->Exit( pthread->m_exitcode ); + + return (OSStatus) NULL; // pthread->m_exitcode; + } +} + +bool wxThreadInternal::Create( wxThread *thread, unsigned int stackSize ) +{ + wxASSERT_MSG( m_state == STATE_NEW && !m_tid, + wxT("Create()ing thread twice?") ); + + OSStatus err = noErr; + m_thread = thread; + + if ( m_notifyQueueId == kInvalidID ) + { + OSStatus err = MPCreateQueue( &m_notifyQueueId ); + if (err != noErr) + { + wxLogSysError( wxT("Cant create the thread event queue") ); + + return false; + } + } + + m_state = STATE_NEW; + + err = MPCreateTask( + MacThreadStart, (void*)m_thread, stackSize, + m_notifyQueueId, &m_exitcode, 0, 0, &m_tid ); + + if (err != noErr) + { + wxLogSysError( wxT("Can't create thread") ); + + return false; + } + + if ( m_prio != WXTHREAD_DEFAULT_PRIORITY ) + SetPriority( m_prio ); + + return true; +} + +void wxThreadInternal::SetPriority( int priority ) +{ + m_prio = priority; + + if (m_tid) + { + // Mac priorities range from 1 to 10,000, with a default of 100. + // wxWidgets priorities range from 0 to 100 with a default of 50. + // We can map wxWidgets to Mac priorities easily by assuming + // the former uses a logarithmic scale. + const unsigned int macPriority = (int)( exp( priority / 25.0 * log( 10.0)) + 0.5); + + MPSetTaskWeight( m_tid, macPriority ); + } +} + +wxThreadError wxThreadInternal::Run() +{ + wxCHECK_MSG( GetState() == STATE_NEW, wxTHREAD_RUNNING, + wxT("thread may only be started once after Create()") ); + + SetState( STATE_RUNNING ); + + // wake up threads waiting for our start + SignalRun(); + return wxTHREAD_NO_ERROR; } -wxThreadError wxThread::Destroy() +void wxThreadInternal::Wait() +{ + wxCHECK_RET( !m_isDetached, wxT("can't wait for a detached thread") ); + + // 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() ) + { + // give the thread we're waiting for chance to do the GUI call + // it might be in, we don't do this conditionally as the to be waited on + // thread might have to acquire the mutex later but before terminating + if ( wxGuiOwnedByMainThread() ) + wxMutexGuiLeave(); + } + + { + wxCriticalSectionLocker lock(m_csJoinFlag); + + if ( m_shouldBeJoined ) + { + void *param1, *param2, *rc; + + OSStatus err = MPWaitOnQueue( + m_notifyQueueId, + ¶m1, + ¶m2, + &rc, + kDurationForever ); + if (err != noErr) + { + wxLogSysError( wxT( "Cannot wait for thread termination.")); + rc = (void*) -1; + } + + // actually param1 would be the address of m_exitcode + // but we don't need this here + m_exitcode = rc; + + m_shouldBeJoined = false; + } + } +} + +void wxThreadInternal::Pause() +{ + // the state is set from the thread which pauses us first, this function + // is called later so the state should have been already set + wxCHECK_RET( m_state == STATE_PAUSED, + wxT("thread must first be paused with wxThread::Pause().") ); + + // wait until the semaphore is Post()ed from Resume() + m_semSuspend.Wait(); +} + +void wxThreadInternal::Resume() +{ + wxCHECK_RET( m_state == STATE_PAUSED, + 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() ) + { + // wake up Pause() + m_semSuspend.Post(); + + // reset the flag + SetReallyPaused( false ); + } + + SetState( STATE_RUNNING ); +} + +// static functions +// ---------------- + +wxThread *wxThread::This() +{ + wxThread* thr = (wxThread*) MPGetTaskStorageValue( gs_tlsForWXThread ) ; + + return thr; +} + +bool wxThread::IsMain() +{ + return GetCurrentId() == gs_idMainThread || gs_idMainThread == kInvalidID ; +} + +#ifdef Yield +#undef Yield +#endif + +void wxThread::Yield() +{ + CFRunLoopRunInMode( kCFRunLoopDefaultMode , 0 , true ) ; + + MPYield(); +} + +void wxThread::Sleep( unsigned long milliseconds ) +{ + AbsoluteTime wakeup = AddDurationToAbsolute( milliseconds, UpTime() ); + MPDelayUntil( &wakeup ); +} + +int wxThread::GetCPUCount() +{ + return MPProcessors(); +} + +unsigned long wxThread::GetCurrentId() +{ + return (unsigned long)MPCurrentTaskID(); +} + +bool wxThread::SetConcurrency( size_t WXUNUSED(level) ) +{ + // Cannot be set in MacOS. + return false; +} + +wxThread::wxThread( wxThreadKind kind ) { - // TODO + g_numberOfThreads++; + m_internal = new wxThreadInternal(); + + m_isDetached = (kind == wxTHREAD_DETACHED); +} + +wxThread::~wxThread() +{ + wxASSERT_MSG( g_numberOfThreads>0 , wxT("More threads deleted than created.") ) ; + + g_numberOfThreads--; + +#ifdef __WXDEBUG__ + m_critsect.Enter(); + + // check that the thread either exited or couldn't be created + if ( m_internal->GetState() != STATE_EXITED && + m_internal->GetState() != STATE_NEW ) + { + wxLogDebug( + wxT("The thread %ld is being destroyed although it is still running! The application may crash."), + GetId() ); + } + + m_critsect.Leave(); +#endif + + wxDELETE( m_internal ) ; +} + +wxThreadError wxThread::Create( unsigned int stackSize ) +{ + wxCriticalSectionLocker lock(m_critsect); + + if ( m_isDetached ) + m_internal->Detach() ; + + if ( !m_internal->Create(this, stackSize) ) + { + m_internal->SetState( STATE_EXITED ); + + return wxTHREAD_NO_RESOURCE; + } + return wxTHREAD_NO_ERROR; } +wxThreadError wxThread::Run() +{ + wxCriticalSectionLocker lock(m_critsect); + + wxCHECK_MSG( m_internal->GetId(), wxTHREAD_MISC_ERROR, + wxT("must call wxThread::Create() first") ); + + return m_internal->Run(); +} + +// ----------------------------------------------------------------------------- +// pause/resume +// ----------------------------------------------------------------------------- + wxThreadError wxThread::Pause() { - // TODO + wxCHECK_MSG( This() != this, wxTHREAD_MISC_ERROR, + _T("a thread can't pause itself") ); + + wxCriticalSectionLocker lock(m_critsect); + + if ( m_internal->GetState() != STATE_RUNNING ) + { + wxLogDebug( wxT("Can't pause thread which is not running.") ); + + return wxTHREAD_NOT_RUNNING; + } + + // just set a flag, the thread will be really paused only during the next + // call to TestDestroy() + m_internal->SetState( STATE_PAUSED ); + return wxTHREAD_NO_ERROR; } wxThreadError wxThread::Resume() { - // TODO - return wxTHREAD_NO_ERROR; + wxCHECK_MSG( This() != this, wxTHREAD_MISC_ERROR, + wxT("a thread can't resume itself") ); + + wxCriticalSectionLocker lock(m_critsect); + + wxThreadState state = m_internal->GetState(); + + switch ( state ) + { + case STATE_PAUSED: + m_internal->Resume(); + return wxTHREAD_NO_ERROR; + + case STATE_EXITED: + return wxTHREAD_NO_ERROR; + + default: + wxLogDebug( wxT("Attempt to resume a thread which is not paused.") ); + + return wxTHREAD_MISC_ERROR; + } +} + +// ----------------------------------------------------------------------------- +// exiting thread +// ----------------------------------------------------------------------------- + +wxThread::ExitCode wxThread::Wait() +{ + wxCHECK_MSG( This() != this, (ExitCode)-1, + wxT("a thread can't wait for itself") ); + + wxCHECK_MSG( !m_isDetached, (ExitCode)-1, + wxT("can't wait for detached thread") ); + + m_internal->Wait(); + + return m_internal->GetExitCode(); } -void wxThread::Exit(void *status) +wxThreadError wxThread::Delete(ExitCode *rc) { - // TODO + wxCHECK_MSG( This() != this, wxTHREAD_MISC_ERROR, + wxT("a thread can't delete itself") ); + + bool isDetached = m_isDetached; + + m_critsect.Enter(); + wxThreadState state = m_internal->GetState(); + + // ask the thread to stop + m_internal->SetCancelFlag(); + + m_critsect.Leave(); + + switch ( state ) + { + case STATE_NEW: + // we need to wake up the thread so that PthreadStart() will + // terminate - right now it's blocking on run semaphore in + // PthreadStart() + m_internal->SignalRun(); + + // fall through + + case STATE_EXITED: + // nothing to do + break; + + case STATE_PAUSED: + // resume the thread first + m_internal->Resume(); + + // fall through + + default: + if ( !isDetached ) + { + // wait until the thread stops + m_internal->Wait(); + + if ( rc ) + { + // return the exit code of the thread + *rc = m_internal->GetExitCode(); + } + } + } + + return wxTHREAD_NO_ERROR; } -void wxThread::SetPriority(int prio) +wxThreadError wxThread::Kill() { - // TODO + wxCHECK_MSG( This() != this, wxTHREAD_MISC_ERROR, + wxT("a thread can't kill itself") ); + + switch ( m_internal->GetState() ) + { + case STATE_NEW: + case STATE_EXITED: + return wxTHREAD_NOT_RUNNING; + + case STATE_PAUSED: + // resume the thread first + Resume(); + + // fall through + + default: + OSStatus err = MPTerminateTask( m_internal->GetId() , -1 ) ; + if (err != noErr) + { + wxLogError( wxT("Failed to terminate a thread.") ); + + return wxTHREAD_MISC_ERROR; + } + + if ( m_isDetached ) + { + delete this ; + } + else + { + // this should be retrieved by Wait actually + m_internal->SetExitCode( (void*)-1 ); + } + + return wxTHREAD_NO_ERROR; + } } -int wxThread::GetPriority() const +void wxThread::Exit( ExitCode status ) { - // TODO - return 0; + wxASSERT_MSG( This() == this, + wxT("wxThread::Exit() can only be called in the context of the same thread") ); + + // don't enter m_critsect before calling OnExit() because the user code + // might deadlock if, for example, it signals a condition in OnExit() (a + // common case) while the main thread calls any of functions entering + // m_critsect on us (almost all of them do) + OnExit(); + + MPTaskID threadid = m_internal->GetId(); + + if ( IsDetached() ) + { + delete this; + } + else // joinable + { + // update the status of the joinable thread + wxCriticalSectionLocker lock( m_critsect ); + m_internal->SetState( STATE_EXITED ); + } + + MPTerminateTask( threadid, (long)status ); } -void wxThread::DeferDestroy(bool on) +// also test whether we were paused +bool wxThread::TestDestroy() { - // TODO + wxASSERT_MSG( This() == this, + wxT("wxThread::TestDestroy() can only be called in the context of the same thread") ); + + m_critsect.Enter(); + + if ( m_internal->GetState() == STATE_PAUSED ) + { + m_internal->SetReallyPaused( true ); + + // leave the crit section or the other threads will stop too if they attempt + // to call any of (seemingly harmless) IsXXX() functions while we sleep + m_critsect.Leave(); + + m_internal->Pause(); + } + else + { + // thread wasn't requested to pause, nothing to do + m_critsect.Leave(); + } + + return m_internal->WasCancelled(); } -void wxThread::TestDestroy() +// ----------------------------------------------------------------------------- +// priority setting +// ----------------------------------------------------------------------------- + +void wxThread::SetPriority(unsigned int prio) { - // TODO + wxCHECK_RET( ((int)WXTHREAD_MIN_PRIORITY <= (int)prio) && + ((int)prio <= (int)WXTHREAD_MAX_PRIORITY), + wxT("invalid thread priority") ); + + wxCriticalSectionLocker lock(m_critsect); + + switch ( m_internal->GetState() ) + { + case STATE_RUNNING: + case STATE_PAUSED: + case STATE_NEW: + // thread not yet started, priority will be set when it is + m_internal->SetPriority( prio ); + break; + + case STATE_EXITED: + default: + wxFAIL_MSG( wxT("impossible to set thread priority in this state") ); + } } -void *wxThread::Join() +unsigned int wxThread::GetPriority() const { - // TODO - return (void*) NULL; + wxCriticalSectionLocker lock((wxCriticalSection &)m_critsect); // const_cast + + return m_internal->GetPriority(); } -unsigned long wxThread::GetID() const +unsigned long wxThread::GetId() const { - // TODO - return 0; + wxCriticalSectionLocker lock((wxCriticalSection &)m_critsect); // const_cast + + return (unsigned long)m_internal->GetId(); } -/* is this needed somewhere ? -wxThread *wxThread::GetThreadFromID(unsigned long id) +// ----------------------------------------------------------------------------- +// state tests +// ----------------------------------------------------------------------------- + +bool wxThread::IsRunning() const { - // TODO - return NULL; + wxCriticalSectionLocker lock((wxCriticalSection &)m_critsect); + + return m_internal->GetState() == STATE_RUNNING; } -*/ bool wxThread::IsAlive() const { - // TODO - return FALSE; + wxCriticalSectionLocker lock((wxCriticalSection&)m_critsect); + + switch ( m_internal->GetState() ) + { + case STATE_RUNNING: + case STATE_PAUSED: + return true; + + default: + return false; + } } -bool wxThread::IsRunning() const +bool wxThread::IsPaused() const { - // TODO - return FALSE; + wxCriticalSectionLocker lock((wxCriticalSection&)m_critsect); + + return (m_internal->GetState() == STATE_PAUSED); } -bool wxThread::IsMain() +// ---------------------------------------------------------------------------- +// Automatic initialization for thread module +// ---------------------------------------------------------------------------- + +class wxThreadModule : public wxModule +{ +public: + virtual bool OnInit(); + virtual void OnExit(); + +private: + DECLARE_DYNAMIC_CLASS(wxThreadModule) +}; + +IMPLEMENT_DYNAMIC_CLASS(wxThreadModule, wxModule) + +bool wxThreadModule::OnInit() +{ + bool hasThreadManager = +#ifdef __LP64__ + true ; // TODO VERIFY IN NEXT BUILD +#else + MPLibraryIsLoaded(); +#endif + + if ( !hasThreadManager ) + { + wxLogError( wxT("MP thread support is not available on this system" ) ) ; + + return false; + } + + // main thread's This() is NULL + verify_noerr( MPAllocateTaskStorageIndex( &gs_tlsForWXThread ) ) ; + verify_noerr( MPSetTaskStorageValue( gs_tlsForWXThread, 0 ) ) ; + + gs_idMainThread = wxThread::GetCurrentId(); + gs_critsectWaitingForGui = new wxCriticalSection(); + + gs_critsectGui = new wxCriticalSection(); + gs_critsectGui->Enter(); + + return true; +} + +void wxThreadModule::OnExit() { - // TODO - return FALSE; + if ( gs_critsectGui ) + { + if ( !wxGuiOwnedByMainThread() ) + { + gs_critsectGui->Enter(); + gs_bGuiOwnedByMainThread = true; + } + + gs_critsectGui->Leave(); + delete gs_critsectGui; + gs_critsectGui = NULL; + } + + delete gs_critsectWaitingForGui; + gs_critsectWaitingForGui = NULL; } -wxThread::wxThread() +// ---------------------------------------------------------------------------- +// GUI Serialization copied from MSW implementation +// ---------------------------------------------------------------------------- + +void wxMutexGuiEnterImpl() { - p_internal = new wxThreadInternal(); + // this would dead lock everything... + wxASSERT_MSG( !wxThread::IsMain(), + wxT("main thread doesn't want to block in wxMutexGuiEnter()!") ); - // TODO + // the order in which we enter the critical sections here is crucial!! + + // set the flag telling to the main thread that we want to do some GUI + { + wxCriticalSectionLocker enter(*gs_critsectWaitingForGui); + + gs_nWaitingForGui++; + } + + wxWakeUpMainThread(); + + // now we may block here because the main thread will soon let us in + // (during the next iteration of OnIdle()) + gs_critsectGui->Enter(); } -wxThread::~wxThread() +void wxMutexGuiLeaveImpl() +{ + wxCriticalSectionLocker enter(*gs_critsectWaitingForGui); + + if ( wxThread::IsMain() ) + { + gs_bGuiOwnedByMainThread = false; + } + else + { + // decrement the number of threads waiting for GUI access now + wxASSERT_MSG( gs_nWaitingForGui > 0, + wxT("calling wxMutexGuiLeave() without entering it first?") ); + + gs_nWaitingForGui--; + + wxWakeUpMainThread(); + } + + gs_critsectGui->Leave(); +} + +void WXDLLIMPEXP_BASE wxMutexGuiLeaveOrEnter() { - Destroy(); - Join(); - delete p_internal; + wxASSERT_MSG( wxThread::IsMain(), + wxT("only main thread may call wxMutexGuiLeaveOrEnter()!") ); + + if ( !gs_critsectWaitingForGui ) + return; + + wxCriticalSectionLocker enter(*gs_critsectWaitingForGui); + + if ( gs_nWaitingForGui == 0 ) + { + // no threads are waiting for GUI - so we may acquire the lock without + // any danger (but only if we don't already have it) + if ( !wxGuiOwnedByMainThread() ) + { + gs_critsectGui->Enter(); + + gs_bGuiOwnedByMainThread = true; + } + //else: already have it, nothing to do + } + else + { + // some threads are waiting, release the GUI lock if we have it + if ( wxGuiOwnedByMainThread() ) + wxMutexGuiLeave(); + //else: some other worker thread is doing GUI + } } -// The default callback just joins the thread and throws away the result. -void wxThread::OnExit() +bool WXDLLIMPEXP_BASE wxGuiOwnedByMainThread() { - Join(); + return gs_bGuiOwnedByMainThread; } -// Automatic initialization -class wxThreadModule : public wxModule { - DECLARE_DYNAMIC_CLASS(wxThreadModule) -public: - virtual bool OnInit() { - /* TODO p_mainid = GetCurrentThread(); */ - wxMainMutex = new wxMutex(); - wxMainMutex->Lock(); - return TRUE; - } - - // Global cleanup - virtual void OnExit() { - wxMainMutex->Unlock(); - delete wxMainMutex; - } -}; +// wake up the main thread +void WXDLLEXPORT wxWakeUpMainThread() +{ + wxMacWakeUp(); +} -IMPLEMENT_DYNAMIC_CLASS(wxThreadModule, wxModule) +// ---------------------------------------------------------------------------- +// include common implementation code +// ---------------------------------------------------------------------------- + +#include "wx/thrimpl.cpp" +#endif // wxUSE_THREADS