X-Git-Url: https://git.saurik.com/wxWidgets.git/blobdiff_plain/169935ad4ed842421ef24470a06d1aa298f90fbe..3a922bb4bd8dbcb04e31e324648c2912e167e0cd:/src/mac/thread.cpp diff --git a/src/mac/thread.cpp b/src/mac/thread.cpp index 8dd23bdce9..a37865ee7a 100644 --- a/src/mac/thread.cpp +++ b/src/mac/thread.cpp @@ -1,264 +1,860 @@ ///////////////////////////////////////////////////////////////////////////// // Name: thread.cpp -// Purpose: wxThread Implementation. For Unix ports, see e.g. src/gtk -// Author: Original from Wolfram Gloger/Guilhem Lavaux -// Modified by: +// Purpose: wxThread Implementation +// Author: Original from Wolfram Gloger/Guilhem Lavaux/Vadim Zeitlin +// Modified by: 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" + #pragma implementation "thread.h" #endif +// ---------------------------------------------------------------------------- +// headers +// ---------------------------------------------------------------------------- + +// For compilers that support precompilation, includes "wx.h". +#include "wx/wxprec.h" + +#if defined(__BORLANDC__) + #pragma hdrstop +#endif + +#ifndef WX_PRECOMP + #include "wx/wx.h" +#endif + +#if wxUSE_THREADS + #include "wx/module.h" #include "wx/thread.h" -#include "wx/utils.h" -enum thread_state { - STATE_IDLE = 0, - STATE_RUNNING, - STATE_CANCELED, - STATE_EXITED +// ---------------------------------------------------------------------------- +// constants +// ---------------------------------------------------------------------------- + +// 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 }; -#if wxUSE_THREADS +// ---------------------------------------------------------------------------- +// this module globals +// ---------------------------------------------------------------------------- -///////////////////////////////////////////////////////////////////////////// -// Static variables -///////////////////////////////////////////////////////////////////////////// +static ThreadID gs_idMainThread = kNoThreadID ; +static bool gs_waitingForThread = FALSE ; -wxMutex *wxMainMutex; // controls access to all GUI functions +// ============================================================================ +// MacOS implementation of thread classes +// ============================================================================ -///////////////////////////////////////////////////////////////////////////// -// Windows implementation -///////////////////////////////////////////////////////////////////////////// +class wxMacStCritical +{ +public : + wxMacStCritical() + { + ThreadBeginCritical() ; + } + ~wxMacStCritical() + { + ThreadEndCritical() ; + } +} ; + +// ---------------------------------------------------------------------------- +// wxMutex implementation +// ---------------------------------------------------------------------------- + +class wxMutexInternal +{ +public: + wxMutexInternal() + { + m_owner = kNoThreadID ; + } + + ~wxMutexInternal() + { + } -class wxMutexInternal { public: - // TODO: internal mutex handle + ThreadID m_owner ; + wxArrayLong m_waiters ; }; wxMutex::wxMutex() { - p_internal = new wxMutexInternal; - // TODO: create internal mutext handle + m_internal = new wxMutexInternal; + m_locked = 0; } wxMutex::~wxMutex() { - if (m_locked > 0) - wxDebugMsg("wxMutex warning: freeing a locked mutex (%d locks)\n", m_locked); - // TODO: free internal mutext handle + if ( m_locked > 0 ) + { + wxLogDebug(_T("Warning: freeing a locked mutex (%d locks)."), m_locked); + } + + delete m_internal; } wxMutexError wxMutex::Lock() { - // TODO + wxMacStCritical critical ; + + OSErr err ; + ThreadID current = kNoThreadID; + err = ::MacGetCurrentThread(¤t); + // if we are not the owner, add this thread to the list of waiting threads, stop this thread + // and invoke the scheduler to continue executing the owner's thread + while ( m_internal->m_owner != kNoThreadID && m_internal->m_owner != current) + { + m_internal->m_waiters.Add(current); + err = ::SetThreadStateEndCritical(kCurrentThreadID, kStoppedThreadState, m_internal->m_owner); + err = ::ThreadBeginCritical(); + } + m_internal->m_owner = current; m_locked++; + return wxMUTEX_NO_ERROR; } wxMutexError wxMutex::TryLock() { - // TODO + wxMacStCritical critical ; + + OSErr err ; + ThreadID current = kNoThreadID; + ::MacGetCurrentThread(¤t); + // if we are not the owner, give an error back + if ( m_internal->m_owner != kNoThreadID && m_internal->m_owner != current ) + return wxMUTEX_BUSY; + + m_internal->m_owner = current; m_locked++; - return wxMUTEX_NO_ERROR; + + return wxMUTEX_NO_ERROR; } wxMutexError wxMutex::Unlock() { + OSErr err; + err = ::ThreadBeginCritical(); + if (m_locked > 0) m_locked--; - // TODO + // this mutex is not owned by anybody anmore + m_internal->m_owner = kNoThreadID; + + // now pass on to the first waiting thread + ThreadID firstWaiting = kNoThreadID; + bool found = false; + while (!m_internal->m_waiters.IsEmpty() && !found) + { + firstWaiting = m_internal->m_waiters[0]; + err = ::SetThreadState(firstWaiting, kReadyThreadState, kNoThreadID); + // in case this was not successful (dead thread), we just loop on and reset the id + found = (err != threadNotFoundErr); + if ( !found ) + firstWaiting = kNoThreadID ; + m_internal->m_waiters.RemoveAt(0) ; + } + // now we have a valid firstWaiting thread, which has been scheduled to run next, just end the + // critical section and invoke the scheduler + err = ::SetThreadStateEndCritical(kCurrentThreadID, kReadyThreadState, firstWaiting); + return wxMUTEX_NO_ERROR; } -class wxConditionInternal { +// ---------------------------------------------------------------------------- +// wxCondition implementation +// ---------------------------------------------------------------------------- + +class wxConditionInternal +{ public: - // TODO: internal handle - int waiters; + wxConditionInternal() + { + m_excessSignals = 0 ; + } + ~wxConditionInternal() + { + } + + bool Wait(unsigned long msectimeout) + { + wxMacStCritical critical ; + if ( m_excessSignals > 0 ) + { + --m_excessSignals ; + return TRUE ; + } + else if ( msectimeout == 0 ) + { + return FALSE ; + } + else + { + } + /* + waiters++; + + // FIXME this should be MsgWaitForMultipleObjects() as well probably + DWORD rc = ::WaitForSingleObject(event, timeout); + + waiters--; + + return rc != WAIT_TIMEOUT; + */ + return TRUE ; + } + void Signal() + { + wxMacStCritical critical ; + } + + wxArrayLong m_waiters ; + wxInt32 m_excessSignals ; }; wxCondition::wxCondition() { - p_internal = new wxConditionInternal; - // TODO: create internal handle - p_internal->waiters = 0; + m_internal = new wxConditionInternal; } wxCondition::~wxCondition() { - // TODO: destroy internal handle + delete m_internal; } -void wxCondition::Wait(wxMutex& mutex) +void wxCondition::Wait() { - mutex.Unlock(); - p_internal->waiters++; - // TODO wait here - p_internal->waiters--; - mutex.Lock(); + (void)m_internal->Wait(0xFFFFFFFFL ); } -bool wxCondition::Wait(wxMutex& mutex, unsigned long sec, +bool wxCondition::Wait(unsigned long sec, unsigned long nsec) { - mutex.Unlock(); - p_internal->waiters++; - - // TODO wait here - p_internal->waiters--; - mutex.Lock(); - - return FALSE; + return m_internal->Wait(sec*1000 + nsec/1000000); } void wxCondition::Signal() { - // TODO + // set the event to signaled: if a thread is already waiting on it, it will + // be woken up, otherwise the event will remain in the signaled state until + // someone waits on it. In any case, the system will return it to a non + // signalled state afterwards. If multiple threads are waiting, only one + // will be woken up. + m_internal->Signal() ; } void wxCondition::Broadcast() { - // TODO + // this works because all these threads are already waiting and so each + // SetEvent() inside Signal() is really a PulseEvent() because the event + // state is immediately returned to non-signaled + for ( int i = 0; i < m_internal->m_waiters.Count(); i++ ) + { + Signal(); + } } -class wxThreadInternal { +// ---------------------------------------------------------------------------- +// wxCriticalSection implementation +// ---------------------------------------------------------------------------- + +// it's implemented as a mutex on mac os, so it is defined in the headers + +// ---------------------------------------------------------------------------- +// wxThread implementation +// ---------------------------------------------------------------------------- + +// wxThreadInternal class +// ---------------------- + +class wxThreadInternal +{ public: - // TODO + wxThreadInternal() + { + m_tid = kNoThreadID ; + m_state = STATE_NEW; + m_priority = WXTHREAD_DEFAULT_PRIORITY; + } + + ~wxThreadInternal() + { + } + + void Free() + { + } + + // create a new (suspended) thread (for the given thread object) + bool Create(wxThread *thread); + + // suspend/resume/terminate + bool Suspend(); + bool Resume(); + void Cancel() { m_state = STATE_CANCELED; } + + // thread state + void SetState(wxThreadState state) { m_state = state; } + wxThreadState GetState() const { return m_state; } + + // thread priority + void SetPriority(unsigned int priority); + unsigned int GetPriority() const { return m_priority; } + + void SetResult( void *res ) { m_result = res ; } + void *GetResult() { return m_result ; } + + // thread handle and id + ThreadID GetId() const { return m_tid; } + + // thread function + static pascal void* MacThreadStart(wxThread* arg); + +private: + wxThreadState m_state; // state, see wxThreadState enum + unsigned int m_priority; // thread priority in "wx" units + ThreadID m_tid; // thread id + void * m_result ; + static ThreadEntryUPP s_threadEntry ; +public : }; -wxThreadError wxThread::Create() +static wxArrayPtrVoid s_threads ; + +ThreadEntryUPP wxThreadInternal::s_threadEntry = NULL ; +pascal void* wxThreadInternal::MacThreadStart(wxThread *thread) { - // TODO - return wxTHREAD_NO_ERROR; + // first of all, check whether we hadn't been cancelled already + if ( thread->m_internal->GetState() == STATE_EXITED ) + { + return (void*)-1; + } + + void* rc = thread->Entry(); + + // enter m_critsect before changing the thread state + thread->m_critsect.Enter(); + bool wasCancelled = thread->m_internal->GetState() == STATE_CANCELED; + thread->m_internal->SetState(STATE_EXITED); + thread->m_critsect.Leave(); + + thread->OnExit(); + + // if the thread was cancelled (from Delete()), then it the handle is still + // needed there + if ( thread->IsDetached() && !wasCancelled ) + { + // auto delete + delete thread; + } + //else: the joinable threads handle will be closed when Wait() is done + + return rc; +} +void wxThreadInternal::SetPriority(unsigned int priority) +{ + // Priorities don't exist on Mac } -wxThreadError wxThread::Destroy() +bool wxThreadInternal::Create(wxThread *thread) { - // TODO - return wxTHREAD_NO_ERROR; + if ( s_threadEntry == NULL ) + { + s_threadEntry = NewThreadEntryUPP( (ThreadEntryProcPtr) MacThreadStart ) ; + } + OSErr err = NewThread(kCooperativeThread, + s_threadEntry, + (void*) thread , + 0 , + kNewSuspend , + &m_result , + &m_tid ) ; + + if ( err != noErr ) + { + wxLogSysError(_("Can't create thread")); + return FALSE; + } + + if ( m_priority != WXTHREAD_DEFAULT_PRIORITY ) + { + SetPriority(m_priority); + } + + return TRUE; } -wxThreadError wxThread::Pause() +bool wxThreadInternal::Suspend() { - // TODO - return wxTHREAD_NO_ERROR; + OSErr err ; + + ::ThreadBeginCritical(); + + if ( m_state != STATE_RUNNING ) + { + ::ThreadEndCritical() ; + wxLogSysError(_("Can not suspend thread %x"), m_tid); + return FALSE; + } + + m_state = STATE_PAUSED; + + err = ::SetThreadStateEndCritical(m_tid, kStoppedThreadState, kNoThreadID); + + return TRUE; } -wxThreadError wxThread::Resume() +bool wxThreadInternal::Resume() { - // TODO - return wxTHREAD_NO_ERROR; + ThreadID current ; + OSErr err ; + err = MacGetCurrentThread( ¤t ) ; + + wxASSERT( err == noErr ) ; + wxASSERT( current != m_tid ) ; + + ::ThreadBeginCritical(); + if ( m_state != STATE_PAUSED && m_state != STATE_NEW ) + { + ::ThreadEndCritical() ; + wxLogSysError(_("Can not resume thread %x"), m_tid); + return FALSE; + + } + err = ::SetThreadStateEndCritical(m_tid, kReadyThreadState, kNoThreadID); + wxASSERT( err == noErr ) ; + + m_state = STATE_RUNNING; + ::ThreadEndCritical() ; + ::YieldToAnyThread() ; + return TRUE; +} + +// static functions +// ---------------- +wxThread *wxThread::This() +{ + wxMacStCritical critical ; + + ThreadID current ; + OSErr err ; + + err = MacGetCurrentThread( ¤t ) ; + + for ( int i = 0 ; i < s_threads.Count() ; ++i ) + { + if ( ( (wxThread*) s_threads[i] )->GetId() == current ) + return (wxThread*) s_threads[i] ; + } + + wxLogSysError(_("Couldn't get the current thread pointer")); + return NULL; +} + +bool wxThread::IsMain() +{ + ThreadID current ; + OSErr err ; + + err = MacGetCurrentThread( ¤t ) ; + return current == gs_idMainThread; } -void wxThread::Exit(void *status) +#ifdef Yield +#undef Yield +#endif + +void wxThread::Yield() { - // TODO + ::YieldToAnyThread() ; } -void wxThread::SetPriority(int prio) +void wxThread::Sleep(unsigned long milliseconds) { - // TODO + clock_t start = clock() ; + do + { + YieldToAnyThread() ; + } while( clock() - start < milliseconds / CLOCKS_PER_SEC ) ; } -int wxThread::GetPriority() const +int wxThread::GetCPUCount() { - // TODO - return 0; + // we will use whatever MP API will be used for the new MP Macs + return 1; } -void wxThread::DeferDestroy(bool on) +bool wxThread::SetConcurrency(size_t level) { - // TODO + wxASSERT_MSG( IsMain(), _T("should only be called from the main thread") ); + + // ok only for the default one + if ( level == 0 ) + return 0; + + // how many CPUs have we got? + if ( GetCPUCount() == 1 ) + { + // don't bother with all this complicated stuff - on a single + // processor system it doesn't make much sense anyhow + return level == 1; + } + + return TRUE ; } -void wxThread::TestDestroy() +// ctor and dtor +// ------------- + +wxThread::wxThread(wxThreadKind kind) { - // TODO + m_internal = new wxThreadInternal(); + + m_isDetached = kind == wxTHREAD_DETACHED; + s_threads.Add( (void*) this ) ; } -void *wxThread::Join() +wxThread::~wxThread() { - // TODO - return (void*) NULL; + s_threads.Remove( (void*) this ) ; + delete m_internal; } -unsigned long wxThread::GetID() const +// create/start thread +// ------------------- + +wxThreadError wxThread::Create() { - // TODO - return 0; + wxCriticalSectionLocker lock(m_critsect); + + if ( !m_internal->Create(this) ) + return wxTHREAD_NO_RESOURCE; + + return wxTHREAD_NO_ERROR; } -/* is this needed somewhere ? -wxThread *wxThread::GetThreadFromID(unsigned long id) +wxThreadError wxThread::Run() { - // TODO - return NULL; + wxCriticalSectionLocker lock(m_critsect); + + if ( m_internal->GetState() != STATE_NEW ) + { + // actually, it may be almost any state at all, not only STATE_RUNNING + return wxTHREAD_RUNNING; + } + + // the thread has just been created and is still suspended - let it run + return Resume(); +} + +// suspend/resume thread +// --------------------- + +wxThreadError wxThread::Pause() +{ + wxCriticalSectionLocker lock(m_critsect); + + return m_internal->Suspend() ? wxTHREAD_NO_ERROR : wxTHREAD_MISC_ERROR; +} + +wxThreadError wxThread::Resume() +{ + wxCriticalSectionLocker lock(m_critsect); + + return m_internal->Resume() ? wxTHREAD_NO_ERROR : wxTHREAD_MISC_ERROR; +} + +// stopping thread +// --------------- + +wxThread::ExitCode wxThread::Wait() +{ + // although under MacOS we can wait for any thread, it's an error to + // wait for a detached one in wxWin API + wxCHECK_MSG( !IsDetached(), (ExitCode)-1, + _T("can't wait for detached thread") ); + + ExitCode rc = (ExitCode)-1; + + (void)Delete(&rc); + + m_internal->Free(); + + return rc; } + +wxThreadError wxThread::Delete(ExitCode *pRc) +{ + ExitCode rc = 0; + + // Delete() is always safe to call, so consider all possible states + + // has the thread started to run? + bool shouldResume = FALSE; + + { + wxCriticalSectionLocker lock(m_critsect); + + if ( m_internal->GetState() == STATE_NEW ) + { + // WinThreadStart() will see it and terminate immediately + m_internal->SetState(STATE_EXITED); + + shouldResume = TRUE; + } + } + + // is the thread paused? + if ( shouldResume || IsPaused() ) + Resume(); + + // does is still run? + if ( IsRunning() ) + { + if ( IsMain() ) + { + // set flag for wxIsWaitingForThread() + gs_waitingForThread = TRUE; + +#if wxUSE_GUI + wxBeginBusyCursor(); +#endif // wxUSE_GUI + } + + // ask the thread to terminate + { + wxCriticalSectionLocker lock(m_critsect); + + m_internal->Cancel(); + } + +#if wxUSE_GUI + // simply wait for the thread to terminate + while( TestDestroy() ) + { + ::YieldToAnyThread() ; + } +#else // !wxUSE_GUI + // simply wait for the thread to terminate + while( TestDestroy() ) + { + ::YieldToAnyThread() ; + } +#endif // wxUSE_GUI/!wxUSE_GUI + + if ( IsMain() ) + { + gs_waitingForThread = FALSE; + +#if wxUSE_GUI + wxEndBusyCursor(); +#endif // wxUSE_GUI + } + } + + // if ( !::GetExitCodeThread(hThread, (LPDWORD)&rc) ) + { + wxLogLastError("GetExitCodeThread"); + + rc = (ExitCode)-1; + } + + if ( IsDetached() ) + { + // if the thread exits normally, this is done in WinThreadStart, but in + // this case it would have been too early because + // MsgWaitForMultipleObject() would fail if the therad handle was + // closed while we were waiting on it, so we must do it here + delete this; + } + + // wxASSERT_MSG( (DWORD)rc != STILL_ACTIVE, + // wxT("thread must be already terminated.") ); + + if ( pRc ) + *pRc = rc; + + return rc == (ExitCode)-1 ? wxTHREAD_MISC_ERROR : wxTHREAD_NO_ERROR; +} + +wxThreadError wxThread::Kill() +{ + if ( !IsRunning() ) + return wxTHREAD_NOT_RUNNING; + +// if ( !::TerminateThread(m_internal->GetHandle(), (DWORD)-1) ) + { + wxLogSysError(_("Couldn't terminate thread")); + + return wxTHREAD_MISC_ERROR; + } + + m_internal->Free(); + + if ( IsDetached() ) + { + delete this; + } + + return wxTHREAD_NO_ERROR; +} + +void wxThread::Exit(ExitCode status) +{ + m_internal->Free(); + + if ( IsDetached() ) + { + delete this; + } + + m_internal->SetResult( status ) ; + +/* +#if defined(__VISUALC__) || (defined(__BORLANDC__) && (__BORLANDC__ >= 0x500)) + _endthreadex((unsigned)status); +#else // !VC++ + ::ExitThread((DWORD)status); +#endif // VC++/!VC++ */ + wxFAIL_MSG(wxT("Couldn't return from ExitThread()!")); +} -bool wxThread::IsAlive() const +// priority setting +// ---------------- + +// since all these calls are execute cooperatively we don't have to use the critical section + +void wxThread::SetPriority(unsigned int prio) { - // TODO - return FALSE; + m_internal->SetPriority(prio); } -bool wxThread::IsRunning() const +unsigned int wxThread::GetPriority() const { - // TODO - return FALSE; + return m_internal->GetPriority(); } -bool wxThread::IsMain() +unsigned long wxThread::GetId() const { - // TODO - return FALSE; + return (unsigned long)m_internal->GetId(); } -wxThread::wxThread() +bool wxThread::IsRunning() const { - p_internal = new wxThreadInternal(); + return m_internal->GetState() == STATE_RUNNING; +} - // TODO +bool wxThread::IsAlive() const +{ + return (m_internal->GetState() == STATE_RUNNING) || + (m_internal->GetState() == STATE_PAUSED); } -wxThread::~wxThread() +bool wxThread::IsPaused() const { - Destroy(); - Join(); - delete p_internal; + return m_internal->GetState() == STATE_PAUSED; } -// The default callback just joins the thread and throws away the result. -void wxThread::OnExit() +bool wxThread::TestDestroy() { - Join(); + return m_internal->GetState() == STATE_CANCELED; } -// Automatic initialization -class wxThreadModule : public wxModule { - DECLARE_DYNAMIC_CLASS(wxThreadModule) +// ---------------------------------------------------------------------------- +// Automatic initialization for thread module +// ---------------------------------------------------------------------------- + +class wxThreadModule : public wxModule +{ public: - virtual bool OnInit() { - /* TODO p_mainid = GetCurrentThread(); */ - wxMainMutex = new wxMutex(); - wxMainMutex->Lock(); - return TRUE; - } + virtual bool OnInit(); + virtual void OnExit(); - // Global cleanup - virtual void OnExit() { - wxMainMutex->Unlock(); - delete wxMainMutex; - } +private: + DECLARE_DYNAMIC_CLASS(wxThreadModule) }; IMPLEMENT_DYNAMIC_CLASS(wxThreadModule, wxModule) +bool wxThreadModule::OnInit() +{ + long response; + bool hasThreadManager ; + hasThreadManager = Gestalt( gestaltThreadMgrAttr, &response) == noErr && response & 1; +#if !TARGET_CARBON +#if GENERATINGCFM + // verify presence of shared library + hasThreadManager = hasThreadManager && ((Ptr)NewThread != (Ptr)kUnresolvedCFragSymbolAddress); +#endif #endif + if ( !hasThreadManager ) + { + wxMessageBox( "Error" , "Thread Support is not available on this System" , wxOK ) ; + return FALSE ; + } + + // no error return for GetCurrentThreadId() + MacGetCurrentThread( &gs_idMainThread ) ; + + return TRUE; +} + +void wxThreadModule::OnExit() +{ +} + +// ---------------------------------------------------------------------------- +// under MacOS we don't have currently preemptive threads, so any thread may access +// the GUI at any time +// ---------------------------------------------------------------------------- + +void WXDLLEXPORT wxMutexGuiEnter() +{ +} + +void WXDLLEXPORT wxMutexGuiLeave() +{ +} + +void WXDLLEXPORT wxMutexGuiLeaveOrEnter() +{ +} + +bool WXDLLEXPORT wxGuiOwnedByMainThread() +{ + return false ; +} + +// wake up the main thread +void WXDLLEXPORT wxWakeUpMainThread() +{ + wxMacWakeUp() ; +} + +bool WXDLLEXPORT wxIsWaitingForThread() +{ + return false ; +} + +#endif // wxUSE_THREADS +