trying to fix race conditions. double deletions and memory leaks on thread termination (closes bug 775774 and replaces patch 781918)
git-svn-id: https://svn.wxwidgets.org/svn/wx/wxWidgets/trunk@23788 c3d73ce0-8a6f-49c7-b76d-6d57e0e08775
This commit is contained in:
@@ -112,8 +112,8 @@ static DWORD gs_tlsThisThread = 0xFFFFFFFF;
|
|||||||
// calling wxMutexGuiEnter()
|
// calling wxMutexGuiEnter()
|
||||||
static DWORD gs_idMainThread = 0;
|
static DWORD gs_idMainThread = 0;
|
||||||
|
|
||||||
// if it's FALSE, some secondary thread is holding the GUI lock
|
// if it's false, some secondary thread is holding the GUI lock
|
||||||
static bool gs_bGuiOwnedByMainThread = TRUE;
|
static bool gs_bGuiOwnedByMainThread = true;
|
||||||
|
|
||||||
// critical section which controls access to all GUI functions: any secondary
|
// 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
|
// thread (i.e. except the main one) must enter this crit section before doing
|
||||||
@@ -123,11 +123,16 @@ static wxCriticalSection *gs_critsectGui = NULL;
|
|||||||
// critical section which protects gs_nWaitingForGui variable
|
// critical section which protects gs_nWaitingForGui variable
|
||||||
static wxCriticalSection *gs_critsectWaitingForGui = NULL;
|
static wxCriticalSection *gs_critsectWaitingForGui = NULL;
|
||||||
|
|
||||||
|
// critical section which serializes WinThreadStart() and WaitForTerminate()
|
||||||
|
// (this is a potential bottleneck, we use a single crit sect for all threads
|
||||||
|
// in the system, but normally time spent inside it should be quite short)
|
||||||
|
static wxCriticalSection *gs_critsectThreadDelete = NULL;
|
||||||
|
|
||||||
// number of threads waiting for GUI in wxMutexGuiEnter()
|
// number of threads waiting for GUI in wxMutexGuiEnter()
|
||||||
static size_t gs_nWaitingForGui = 0;
|
static size_t gs_nWaitingForGui = 0;
|
||||||
|
|
||||||
// are we waiting for a thread termination?
|
// are we waiting for a thread termination?
|
||||||
static bool gs_waitingForThread = FALSE;
|
static bool gs_waitingForThread = false;
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
// Windows implementation of thread and related classes
|
// Windows implementation of thread and related classes
|
||||||
@@ -191,7 +196,7 @@ wxMutexInternal::wxMutexInternal(wxMutexType WXUNUSED(mutexType))
|
|||||||
m_mutex = ::CreateMutex
|
m_mutex = ::CreateMutex
|
||||||
(
|
(
|
||||||
NULL, // default secutiry attributes
|
NULL, // default secutiry attributes
|
||||||
FALSE, // not initially locked
|
false, // not initially locked
|
||||||
NULL // no name
|
NULL // no name
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -370,11 +375,13 @@ wxSemaError wxSemaphoreInternal::Post()
|
|||||||
class wxThreadInternal
|
class wxThreadInternal
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
wxThreadInternal()
|
wxThreadInternal(wxThread *thread)
|
||||||
{
|
{
|
||||||
|
m_thread = thread;
|
||||||
m_hThread = 0;
|
m_hThread = 0;
|
||||||
m_state = STATE_NEW;
|
m_state = STATE_NEW;
|
||||||
m_priority = WXTHREAD_DEFAULT_PRIORITY;
|
m_priority = WXTHREAD_DEFAULT_PRIORITY;
|
||||||
|
m_nRef = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
~wxThreadInternal()
|
~wxThreadInternal()
|
||||||
@@ -400,9 +407,9 @@ public:
|
|||||||
|
|
||||||
// wait for the thread to terminate, either by itself, or by asking it
|
// wait for the thread to terminate, either by itself, or by asking it
|
||||||
// (politely, this is not Kill()!) to do it
|
// (politely, this is not Kill()!) to do it
|
||||||
wxThreadError WaitForTerminate(bool shouldCancel,
|
wxThreadError WaitForTerminate(wxCriticalSection& cs,
|
||||||
wxCriticalSection& cs,
|
wxThread::ExitCode *pRc,
|
||||||
wxThread::ExitCode *pRc);
|
wxThread *threadToDelete = NULL);
|
||||||
|
|
||||||
// kill the thread unconditionally
|
// kill the thread unconditionally
|
||||||
wxThreadError Kill();
|
wxThreadError Kill();
|
||||||
@@ -427,27 +434,59 @@ public:
|
|||||||
// thread function
|
// thread function
|
||||||
static THREAD_RETVAL THREAD_CALLCONV WinThreadStart(void *thread);
|
static THREAD_RETVAL THREAD_CALLCONV WinThreadStart(void *thread);
|
||||||
|
|
||||||
|
void KeepAlive()
|
||||||
|
{
|
||||||
|
if ( m_thread->IsDetached() )
|
||||||
|
::InterlockedIncrement(&m_nRef);
|
||||||
|
}
|
||||||
|
|
||||||
|
void LetDie()
|
||||||
|
{
|
||||||
|
if ( m_thread->IsDetached() && !::InterlockedDecrement(&m_nRef) )
|
||||||
|
delete m_thread;
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
// the thread we're associated with
|
||||||
|
wxThread *m_thread;
|
||||||
|
|
||||||
HANDLE m_hThread; // handle of the thread
|
HANDLE m_hThread; // handle of the thread
|
||||||
wxThreadState m_state; // state, see wxThreadState enum
|
wxThreadState m_state; // state, see wxThreadState enum
|
||||||
unsigned int m_priority; // thread priority in "wx" units
|
unsigned int m_priority; // thread priority in "wx" units
|
||||||
DWORD m_tid; // thread id
|
DWORD m_tid; // thread id
|
||||||
|
|
||||||
|
// number of threads which need this thread to remain alive, when the count
|
||||||
|
// reaches 0 we kill the owning wxThread -- and die ourselves with it
|
||||||
|
LONG m_nRef;
|
||||||
|
|
||||||
DECLARE_NO_COPY_CLASS(wxThreadInternal)
|
DECLARE_NO_COPY_CLASS(wxThreadInternal)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// small class which keeps a thread alive during its lifetime
|
||||||
|
class wxThreadKeepAlive
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
wxThreadKeepAlive(wxThreadInternal& thrImpl) : m_thrImpl(thrImpl)
|
||||||
|
{ m_thrImpl.KeepAlive(); }
|
||||||
|
~wxThreadKeepAlive()
|
||||||
|
{ m_thrImpl.LetDie(); }
|
||||||
|
|
||||||
|
private:
|
||||||
|
wxThreadInternal& m_thrImpl;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
THREAD_RETVAL THREAD_CALLCONV wxThreadInternal::WinThreadStart(void *param)
|
THREAD_RETVAL THREAD_CALLCONV wxThreadInternal::WinThreadStart(void *param)
|
||||||
{
|
{
|
||||||
THREAD_RETVAL rc;
|
THREAD_RETVAL rc;
|
||||||
bool wasCancelled;
|
|
||||||
|
wxThread * const thread = (wxThread *)param;
|
||||||
|
|
||||||
// first of all, check whether we hadn't been cancelled already and don't
|
// first of all, check whether we hadn't been cancelled already and don't
|
||||||
// start the user code at all then
|
// start the user code at all then
|
||||||
wxThread *thread = (wxThread *)param;
|
|
||||||
if ( thread->m_internal->GetState() == STATE_EXITED )
|
if ( thread->m_internal->GetState() == STATE_EXITED )
|
||||||
{
|
{
|
||||||
rc = (THREAD_RETVAL)-1;
|
rc = (THREAD_RETVAL)-1;
|
||||||
wasCancelled = TRUE;
|
|
||||||
}
|
}
|
||||||
else // do run thread
|
else // do run thread
|
||||||
{
|
{
|
||||||
@@ -462,22 +501,15 @@ THREAD_RETVAL THREAD_CALLCONV wxThreadInternal::WinThreadStart(void *param)
|
|||||||
rc = (THREAD_RETVAL)thread->Entry();
|
rc = (THREAD_RETVAL)thread->Entry();
|
||||||
|
|
||||||
// enter m_critsect before changing the thread state
|
// enter m_critsect before changing the thread state
|
||||||
thread->m_critsect.Enter();
|
wxCriticalSectionLocker lock(thread->m_critsect);
|
||||||
wasCancelled = thread->m_internal->GetState() == STATE_CANCELED;
|
|
||||||
thread->m_internal->SetState(STATE_EXITED);
|
thread->m_internal->SetState(STATE_EXITED);
|
||||||
thread->m_critsect.Leave();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
thread->OnExit();
|
thread->OnExit();
|
||||||
|
|
||||||
// if the thread was cancelled (from Delete()), then its handle is still
|
// the thread may delete itself now if it wants, we don't need it any more
|
||||||
// needed there
|
thread->m_internal->LetDie();
|
||||||
if ( thread->IsDetached() && !wasCancelled )
|
|
||||||
{
|
|
||||||
// auto delete
|
|
||||||
delete thread;
|
|
||||||
}
|
|
||||||
//else: the joinable threads handle will be closed when Wait() is done
|
|
||||||
|
|
||||||
return rc;
|
return rc;
|
||||||
}
|
}
|
||||||
@@ -552,7 +584,7 @@ bool wxThreadInternal::Create(wxThread *thread, unsigned int stackSize)
|
|||||||
{
|
{
|
||||||
wxLogSysError(_("Can't create thread"));
|
wxLogSysError(_("Can't create thread"));
|
||||||
|
|
||||||
return FALSE;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( m_priority != WXTHREAD_DEFAULT_PRIORITY )
|
if ( m_priority != WXTHREAD_DEFAULT_PRIORITY )
|
||||||
@@ -560,7 +592,7 @@ bool wxThreadInternal::Create(wxThread *thread, unsigned int stackSize)
|
|||||||
SetPriority(m_priority);
|
SetPriority(m_priority);
|
||||||
}
|
}
|
||||||
|
|
||||||
return TRUE;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
wxThreadError wxThreadInternal::Kill()
|
wxThreadError wxThreadInternal::Kill()
|
||||||
@@ -578,18 +610,27 @@ wxThreadError wxThreadInternal::Kill()
|
|||||||
}
|
}
|
||||||
|
|
||||||
wxThreadError
|
wxThreadError
|
||||||
wxThreadInternal::WaitForTerminate(bool shouldCancel,
|
wxThreadInternal::WaitForTerminate(wxCriticalSection& cs,
|
||||||
wxCriticalSection& cs,
|
wxThread::ExitCode *pRc,
|
||||||
wxThread::ExitCode *pRc)
|
wxThread *threadToDelete)
|
||||||
{
|
{
|
||||||
|
// prevent the thread C++ object from disappearing as long as we are using
|
||||||
|
// it here
|
||||||
|
wxThreadKeepAlive keepAlive(*this);
|
||||||
|
|
||||||
|
|
||||||
|
// we may either wait passively for the thread to terminate (when called
|
||||||
|
// from Wait()) or ask it to terminate (when called from Delete())
|
||||||
|
bool shouldDelete = threadToDelete != NULL;
|
||||||
|
|
||||||
wxThread::ExitCode rc = 0;
|
wxThread::ExitCode rc = 0;
|
||||||
|
|
||||||
// Delete() is always safe to call, so consider all possible states
|
// Delete() is always safe to call, so consider all possible states
|
||||||
|
|
||||||
// we might need to resume the thread, but we might also not need to cancel
|
// we might need to resume the thread, but we might also not need to cancel
|
||||||
// it if it doesn't run yet
|
// it if it doesn't run yet
|
||||||
bool shouldResume = FALSE,
|
bool shouldResume = false,
|
||||||
isRunning = FALSE;
|
isRunning = false;
|
||||||
|
|
||||||
// check if the thread already started to run
|
// check if the thread already started to run
|
||||||
{
|
{
|
||||||
@@ -597,7 +638,7 @@ wxThreadInternal::WaitForTerminate(bool shouldCancel,
|
|||||||
|
|
||||||
if ( m_state == STATE_NEW )
|
if ( m_state == STATE_NEW )
|
||||||
{
|
{
|
||||||
if ( shouldCancel )
|
if ( shouldDelete )
|
||||||
{
|
{
|
||||||
// WinThreadStart() will see it and terminate immediately, no
|
// WinThreadStart() will see it and terminate immediately, no
|
||||||
// need to cancel the thread -- but we still need to resume it
|
// need to cancel the thread -- but we still need to resume it
|
||||||
@@ -606,12 +647,12 @@ wxThreadInternal::WaitForTerminate(bool shouldCancel,
|
|||||||
|
|
||||||
Resume(); // it knows about STATE_EXITED special case
|
Resume(); // it knows about STATE_EXITED special case
|
||||||
|
|
||||||
shouldCancel = FALSE;
|
shouldDelete = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
isRunning = TRUE;
|
isRunning = true;
|
||||||
|
|
||||||
// shouldResume is correctly set to FALSE here
|
// shouldResume is correctly set to false here
|
||||||
}
|
}
|
||||||
else if ( m_state == STATE_EXITED )
|
else if ( m_state == STATE_EXITED )
|
||||||
{
|
{
|
||||||
@@ -633,11 +674,11 @@ wxThreadInternal::WaitForTerminate(bool shouldCancel,
|
|||||||
if ( wxThread::IsMain() )
|
if ( wxThread::IsMain() )
|
||||||
{
|
{
|
||||||
// set flag for wxIsWaitingForThread()
|
// set flag for wxIsWaitingForThread()
|
||||||
gs_waitingForThread = TRUE;
|
gs_waitingForThread = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ask the thread to terminate
|
// ask the thread to terminate
|
||||||
if ( shouldCancel )
|
if ( shouldDelete )
|
||||||
{
|
{
|
||||||
wxCriticalSectionLocker lock(cs);
|
wxCriticalSectionLocker lock(cs);
|
||||||
|
|
||||||
@@ -666,7 +707,7 @@ wxThreadInternal::WaitForTerminate(bool shouldCancel,
|
|||||||
(
|
(
|
||||||
1, // number of objects to wait for
|
1, // number of objects to wait for
|
||||||
&m_hThread, // the objects
|
&m_hThread, // the objects
|
||||||
FALSE, // don't wait for all objects
|
false, // don't wait for all objects
|
||||||
INFINITE, // no timeout
|
INFINITE, // no timeout
|
||||||
QS_ALLINPUT | // return as soon as there are any events
|
QS_ALLINPUT | // return as soon as there are any events
|
||||||
QS_ALLPOSTMESSAGE
|
QS_ALLPOSTMESSAGE
|
||||||
@@ -721,7 +762,7 @@ wxThreadInternal::WaitForTerminate(bool shouldCancel,
|
|||||||
|
|
||||||
if ( wxThread::IsMain() )
|
if ( wxThread::IsMain() )
|
||||||
{
|
{
|
||||||
gs_waitingForThread = FALSE;
|
gs_waitingForThread = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -741,11 +782,9 @@ wxThreadInternal::WaitForTerminate(bool shouldCancel,
|
|||||||
if ( pRc )
|
if ( pRc )
|
||||||
*pRc = rc;
|
*pRc = rc;
|
||||||
|
|
||||||
// we don't need the thread handle any more
|
// we don't need the thread handle any more in any case
|
||||||
Free();
|
Free();
|
||||||
|
|
||||||
wxCriticalSectionLocker lock(cs);
|
|
||||||
SetState(STATE_EXITED);
|
|
||||||
|
|
||||||
return rc == (wxThread::ExitCode)-1 ? wxTHREAD_MISC_ERROR
|
return rc == (wxThread::ExitCode)-1 ? wxTHREAD_MISC_ERROR
|
||||||
: wxTHREAD_NO_ERROR;
|
: wxTHREAD_NO_ERROR;
|
||||||
@@ -758,12 +797,12 @@ bool wxThreadInternal::Suspend()
|
|||||||
{
|
{
|
||||||
wxLogSysError(_("Can not suspend thread %x"), m_hThread);
|
wxLogSysError(_("Can not suspend thread %x"), m_hThread);
|
||||||
|
|
||||||
return FALSE;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
m_state = STATE_PAUSED;
|
m_state = STATE_PAUSED;
|
||||||
|
|
||||||
return TRUE;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool wxThreadInternal::Resume()
|
bool wxThreadInternal::Resume()
|
||||||
@@ -773,7 +812,7 @@ bool wxThreadInternal::Resume()
|
|||||||
{
|
{
|
||||||
wxLogSysError(_("Can not resume thread %x"), m_hThread);
|
wxLogSysError(_("Can not resume thread %x"), m_hThread);
|
||||||
|
|
||||||
return FALSE;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// don't change the state from STATE_EXITED because it's special and means
|
// don't change the state from STATE_EXITED because it's special and means
|
||||||
@@ -784,7 +823,7 @@ bool wxThreadInternal::Resume()
|
|||||||
m_state = STATE_RUNNING;
|
m_state = STATE_RUNNING;
|
||||||
}
|
}
|
||||||
|
|
||||||
return TRUE;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// static functions
|
// static functions
|
||||||
@@ -851,7 +890,7 @@ bool wxThread::SetConcurrency(size_t level)
|
|||||||
{
|
{
|
||||||
wxLogLastError(_T("GetProcessAffinityMask"));
|
wxLogLastError(_T("GetProcessAffinityMask"));
|
||||||
|
|
||||||
return FALSE;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// how many CPUs have we got?
|
// how many CPUs have we got?
|
||||||
@@ -890,7 +929,7 @@ bool wxThread::SetConcurrency(size_t level)
|
|||||||
{
|
{
|
||||||
wxLogDebug(_T("bad level %u in wxThread::SetConcurrency()"), level);
|
wxLogDebug(_T("bad level %u in wxThread::SetConcurrency()"), level);
|
||||||
|
|
||||||
return FALSE;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// set it: we can't link to SetProcessAffinityMask() because it doesn't
|
// set it: we can't link to SetProcessAffinityMask() because it doesn't
|
||||||
@@ -918,17 +957,17 @@ bool wxThread::SetConcurrency(size_t level)
|
|||||||
if ( !pfnSetProcessAffinityMask )
|
if ( !pfnSetProcessAffinityMask )
|
||||||
{
|
{
|
||||||
// msg given above - do it only once
|
// msg given above - do it only once
|
||||||
return FALSE;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( pfnSetProcessAffinityMask(hProcess, dwProcMask) == 0 )
|
if ( pfnSetProcessAffinityMask(hProcess, dwProcMask) == 0 )
|
||||||
{
|
{
|
||||||
wxLogLastError(_T("SetProcessAffinityMask"));
|
wxLogLastError(_T("SetProcessAffinityMask"));
|
||||||
|
|
||||||
return FALSE;
|
return false;
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
return TRUE;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ctor and dtor
|
// ctor and dtor
|
||||||
@@ -936,7 +975,7 @@ bool wxThread::SetConcurrency(size_t level)
|
|||||||
|
|
||||||
wxThread::wxThread(wxThreadKind kind)
|
wxThread::wxThread(wxThreadKind kind)
|
||||||
{
|
{
|
||||||
m_internal = new wxThreadInternal();
|
m_internal = new wxThreadInternal(this);
|
||||||
|
|
||||||
m_isDetached = kind == wxTHREAD_DETACHED;
|
m_isDetached = kind == wxTHREAD_DETACHED;
|
||||||
}
|
}
|
||||||
@@ -1002,14 +1041,14 @@ wxThread::ExitCode wxThread::Wait()
|
|||||||
|
|
||||||
ExitCode rc = (ExitCode)-1;
|
ExitCode rc = (ExitCode)-1;
|
||||||
|
|
||||||
(void)m_internal->WaitForTerminate(false, m_critsect, &rc);
|
(void)m_internal->WaitForTerminate(m_critsect, &rc);
|
||||||
|
|
||||||
return rc;
|
return rc;
|
||||||
}
|
}
|
||||||
|
|
||||||
wxThreadError wxThread::Delete(ExitCode *pRc)
|
wxThreadError wxThread::Delete(ExitCode *pRc)
|
||||||
{
|
{
|
||||||
return m_internal->WaitForTerminate(true, m_critsect, pRc);
|
return m_internal->WaitForTerminate(m_critsect, pRc, this);
|
||||||
}
|
}
|
||||||
|
|
||||||
wxThreadError wxThread::Kill()
|
wxThreadError wxThread::Kill()
|
||||||
@@ -1137,7 +1176,7 @@ bool wxThreadModule::OnInit()
|
|||||||
// words, this should never happen
|
// words, this should never happen
|
||||||
wxLogSysError(_("Thread module initialization failed: impossible to allocate index in thread local storage"));
|
wxLogSysError(_("Thread module initialization failed: impossible to allocate index in thread local storage"));
|
||||||
|
|
||||||
return FALSE;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// main thread doesn't have associated wxThread object, so store 0 in the
|
// main thread doesn't have associated wxThread object, so store 0 in the
|
||||||
@@ -1149,7 +1188,7 @@ bool wxThreadModule::OnInit()
|
|||||||
|
|
||||||
wxLogSysError(_("Thread module initialization failed: can not store value in thread local storage"));
|
wxLogSysError(_("Thread module initialization failed: can not store value in thread local storage"));
|
||||||
|
|
||||||
return FALSE;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
gs_critsectWaitingForGui = new wxCriticalSection();
|
gs_critsectWaitingForGui = new wxCriticalSection();
|
||||||
@@ -1157,10 +1196,12 @@ bool wxThreadModule::OnInit()
|
|||||||
gs_critsectGui = new wxCriticalSection();
|
gs_critsectGui = new wxCriticalSection();
|
||||||
gs_critsectGui->Enter();
|
gs_critsectGui->Enter();
|
||||||
|
|
||||||
|
gs_critsectThreadDelete = new wxCriticalSection;
|
||||||
|
|
||||||
// no error return for GetCurrentThreadId()
|
// no error return for GetCurrentThreadId()
|
||||||
gs_idMainThread = ::GetCurrentThreadId();
|
gs_idMainThread = ::GetCurrentThreadId();
|
||||||
|
|
||||||
return TRUE;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void wxThreadModule::OnExit()
|
void wxThreadModule::OnExit()
|
||||||
@@ -1170,6 +1211,9 @@ void wxThreadModule::OnExit()
|
|||||||
wxLogLastError(wxT("TlsFree failed."));
|
wxLogLastError(wxT("TlsFree failed."));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
delete gs_critsectThreadDelete;
|
||||||
|
gs_critsectThreadDelete = NULL;
|
||||||
|
|
||||||
if ( gs_critsectGui )
|
if ( gs_critsectGui )
|
||||||
{
|
{
|
||||||
gs_critsectGui->Leave();
|
gs_critsectGui->Leave();
|
||||||
@@ -1214,7 +1258,7 @@ void WXDLLIMPEXP_BASE wxMutexGuiLeave()
|
|||||||
|
|
||||||
if ( wxThread::IsMain() )
|
if ( wxThread::IsMain() )
|
||||||
{
|
{
|
||||||
gs_bGuiOwnedByMainThread = FALSE;
|
gs_bGuiOwnedByMainThread = false;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -1245,7 +1289,7 @@ void WXDLLIMPEXP_BASE wxMutexGuiLeaveOrEnter()
|
|||||||
{
|
{
|
||||||
gs_critsectGui->Enter();
|
gs_critsectGui->Enter();
|
||||||
|
|
||||||
gs_bGuiOwnedByMainThread = TRUE;
|
gs_bGuiOwnedByMainThread = true;
|
||||||
}
|
}
|
||||||
//else: already have it, nothing to do
|
//else: already have it, nothing to do
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user