From f4fb91b56e61934c6d1c7da52a2ea7111e7c0f18 Mon Sep 17 00:00:00 2001 From: Vadim Zeitlin Date: Tue, 10 May 2022 23:26:45 +0200 Subject: [PATCH 1/8] Don't keep critical section locked during wxEntryStart() call Use gs_initData.csInit only to ensure that we call wxEntryStart() once even if there are multiple calls to wxInitialize() from multiple threads, but don't keep it locked for the duration of that function as this is unnecessary and results in -- probably benign in practice, but still annoying -- warnings from the thread sanitizer about lock order inversions due to locking csInit first before locking gs_mutexGui in wxThreadModule::OnInit() and then acquiring csInit again while gs_mutexGui is still locked in wxUninitialize(). This shouldn't result in any observable changes in behaviour. --- src/common/init.cpp | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/src/common/init.cpp b/src/common/init.cpp index 6b2cc19710..6b5956956a 100644 --- a/src/common/init.cpp +++ b/src/common/init.cpp @@ -139,7 +139,8 @@ static struct InitData #endif // wxUSE_UNICODE } - // critical section protecting this struct + // critical section protecting the counter and allowing to call + // wxInitialize() concurrently from different threads wxCRIT_SECT_DECLARE_MEMBER(csInit); // number of times wxInitialize() was called minus the number of times @@ -530,6 +531,7 @@ bool wxInitialize() bool wxInitialize(int& argc, wxChar **argv) { + { wxCRIT_SECT_LOCKER(lockInit, gs_initData.csInit); if ( gs_initData.nInitCount++ ) @@ -537,6 +539,9 @@ bool wxInitialize(int& argc, wxChar **argv) // already initialized return true; } + } // Do not keep the critical section locked, this could potentially result + // in deadlocks and is unnecessary anyhow as wxEntryStart() will be + // called exactly once and so doesn't need to be protected. return wxEntryStart(argc, argv); } @@ -544,6 +549,7 @@ bool wxInitialize(int& argc, wxChar **argv) #if wxUSE_UNICODE bool wxInitialize(int& argc, char **argv) { + { wxCRIT_SECT_LOCKER(lockInit, gs_initData.csInit); if ( gs_initData.nInitCount++ ) @@ -551,6 +557,7 @@ bool wxInitialize(int& argc, char **argv) // already initialized return true; } + } // See the comment in the overload above. return wxEntryStart(argc, argv); } @@ -558,10 +565,12 @@ bool wxInitialize(int& argc, char **argv) void wxUninitialize() { + { wxCRIT_SECT_LOCKER(lockInit, gs_initData.csInit); - if ( --gs_initData.nInitCount == 0 ) - { - wxEntryCleanup(); - } + if ( --gs_initData.nInitCount != 0 ) + return; + } // Don't keep the critical section locked during cleanup call neither. + + wxEntryCleanup(); } From 44131b044daf3a7084424b4551d1c01cfe07b5cc Mon Sep 17 00:00:00 2001 From: Vadim Zeitlin Date: Tue, 10 May 2022 23:54:21 +0200 Subject: [PATCH 2/8] Use the same number of iterations in atomic unit test Use reasonably, but not too, big number, so that the test doesn't run too long, even when TSAN is used. This also simplifies the code. --- tests/thread/atomic.cpp | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/tests/thread/atomic.cpp b/tests/thread/atomic.cpp index 2d8ce31a97..0ec7362f3d 100644 --- a/tests/thread/atomic.cpp +++ b/tests/thread/atomic.cpp @@ -27,13 +27,7 @@ WX_DEFINE_ARRAY_PTR(wxThread *, wxArrayThread); // constants // ---------------------------------------------------------------------------- -// number of times to run the loops: the code takes too long to run if we use -// the bigger value with generic atomic operations implementation -#ifdef wxHAS_ATOMIC_OPS - static const wxInt32 ITERATIONS_NUM = 10000000; -#else - static const wxInt32 ITERATIONS_NUM = 1000; -#endif +static const wxInt32 ITERATIONS_NUM = 10000; // ---------------------------------------------------------------------------- // test class From a2e68f43eac1c508f44e46bab057f26e4449a5e9 Mon Sep 17 00:00:00 2001 From: Vadim Zeitlin Date: Tue, 10 May 2022 23:55:25 +0200 Subject: [PATCH 3/8] Avoid direct access to an atomic variable in the test This (correctly) results in a warning about data race from TSAN, so don't do it and use the return value of wxAtomicDec() instead. --- tests/thread/atomic.cpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/tests/thread/atomic.cpp b/tests/thread/atomic.cpp index 0ec7362f3d..1e554935bf 100644 --- a/tests/thread/atomic.cpp +++ b/tests/thread/atomic.cpp @@ -171,9 +171,7 @@ void *AtomicTestCase::MyThread::Entry() { case AtomicTestCase::IncAndDecMixed: wxAtomicInc(m_operateOn); - wxAtomicDec(m_operateOn); - - if (m_operateOn < 0) + if ( wxAtomicDec(m_operateOn) < 0 ) ++negativeValuesSeen; break; From 50bc3ceb04c8d0d809d72af44ddb5ad201dfc816 Mon Sep 17 00:00:00 2001 From: Vadim Zeitlin Date: Wed, 11 May 2022 00:06:44 +0200 Subject: [PATCH 4/8] Get rid of CppUnit boilerplate in atomic unit test Also use a single test function with different sections instead of using a helper function with 4 wrappers calling it for simplicity. --- tests/thread/atomic.cpp | 73 ++++++++++++++++------------------------- 1 file changed, 28 insertions(+), 45 deletions(-) diff --git a/tests/thread/atomic.cpp b/tests/thread/atomic.cpp index 1e554935bf..bb8849a80f 100644 --- a/tests/thread/atomic.cpp +++ b/tests/thread/atomic.cpp @@ -30,14 +30,11 @@ WX_DEFINE_ARRAY_PTR(wxThread *, wxArrayThread); static const wxInt32 ITERATIONS_NUM = 10000; // ---------------------------------------------------------------------------- -// test class +// test helper thread // ---------------------------------------------------------------------------- -class AtomicTestCase : public CppUnit::TestCase +namespace { -public: - AtomicTestCase() { } - enum ETestType { IncAndDecMixed, @@ -45,7 +42,6 @@ public: DecOnly }; -private: class MyThread : public wxThread { public: @@ -59,34 +55,13 @@ private: wxAtomicInt &m_operateOn; ETestType m_testType; }; +} // anonymous namespace - CPPUNIT_TEST_SUITE( AtomicTestCase ); - CPPUNIT_TEST( TestNoThread ); - CPPUNIT_TEST( TestDecReturn ); - CPPUNIT_TEST( TestTwoThreadsMix ); - CPPUNIT_TEST( TestTenThreadsMix ); - CPPUNIT_TEST( TestTwoThreadsSeparate ); - CPPUNIT_TEST( TestTenThreadsSeparate ); - CPPUNIT_TEST_SUITE_END(); +// ---------------------------------------------------------------------------- +// the tests themselves +// ---------------------------------------------------------------------------- - void TestNoThread(); - void TestDecReturn(); - void TestTenThreadsMix() { TestWithThreads(10, IncAndDecMixed); } - void TestTwoThreadsMix() { TestWithThreads(2, IncAndDecMixed); } - void TestTenThreadsSeparate() { TestWithThreads(10, IncOnly); } - void TestTwoThreadsSeparate() { TestWithThreads(2, IncOnly); } - void TestWithThreads(int count, ETestType testtype); - - wxDECLARE_NO_COPY_CLASS(AtomicTestCase); -}; - -// register in the unnamed registry so that these tests are run by default -CPPUNIT_TEST_SUITE_REGISTRATION( AtomicTestCase ); - -// also include in its own registry so that these tests can be run alone -CPPUNIT_TEST_SUITE_NAMED_REGISTRATION( AtomicTestCase, "AtomicTestCase" ); - -void AtomicTestCase::TestNoThread() +TEST_CASE("Atomic::NoThread", "[atomic]") { wxAtomicInt int1 = 0, int2 = 0; @@ -97,23 +72,31 @@ void AtomicTestCase::TestNoThread() wxAtomicDec(int2); } - CPPUNIT_ASSERT( int1 == ITERATIONS_NUM ); - CPPUNIT_ASSERT( int2 == -ITERATIONS_NUM ); + CHECK( int1 == ITERATIONS_NUM ); + CHECK( int2 == -ITERATIONS_NUM ); } -void AtomicTestCase::TestDecReturn() +TEST_CASE("Atomic::DecReturn", "[atomic]") { wxAtomicInt i(0); wxAtomicInc(i); wxAtomicInc(i); - CPPUNIT_ASSERT( i == 2 ); + CHECK( i == 2 ); - CPPUNIT_ASSERT( wxAtomicDec(i) > 0 ); - CPPUNIT_ASSERT( wxAtomicDec(i) == 0 ); + CHECK( wxAtomicDec(i) > 0 ); + CHECK( wxAtomicDec(i) == 0 ); } -void AtomicTestCase::TestWithThreads(int count, ETestType testType) +TEST_CASE("Atomic::WithThreads", "[atomic]") { + int count; + ETestType testType; + + SECTION( "2 threads using inc and dec") { count = 2; testType = IncAndDecMixed; } + SECTION("10 threads using inc and dec") { count = 10; testType = IncAndDecMixed; } + SECTION( "2 threads using inc or dec" ) { count = 2; testType = IncOnly; } + SECTION("10 threads using inc or dec" ) { count = 10; testType = IncOnly; } + wxAtomicInt int1=0; wxArrayThread threads; @@ -152,16 +135,16 @@ void AtomicTestCase::TestWithThreads(int count, ETestType testType) for ( i = 0; i < count; ++i ) { // each thread should return 0, else it detected some problem - CPPUNIT_ASSERT (threads[i]->Wait() == (wxThread::ExitCode)0); + CHECK (threads[i]->Wait() == (wxThread::ExitCode)0); delete threads[i]; } - CPPUNIT_ASSERT( int1 == 0 ); + CHECK( int1 == 0 ); } // ---------------------------------------------------------------------------- -void *AtomicTestCase::MyThread::Entry() +void *MyThread::Entry() { wxInt32 negativeValuesSeen = 0; @@ -169,17 +152,17 @@ void *AtomicTestCase::MyThread::Entry() { switch ( m_testType ) { - case AtomicTestCase::IncAndDecMixed: + case IncAndDecMixed: wxAtomicInc(m_operateOn); if ( wxAtomicDec(m_operateOn) < 0 ) ++negativeValuesSeen; break; - case AtomicTestCase::IncOnly: + case IncOnly: wxAtomicInc(m_operateOn); break; - case AtomicTestCase::DecOnly: + case DecOnly: wxAtomicDec(m_operateOn); break; } From e82290f0daa440c6a9784bd3f9daa09a407dacf7 Mon Sep 17 00:00:00 2001 From: Vadim Zeitlin Date: Wed, 11 May 2022 00:08:52 +0200 Subject: [PATCH 5/8] Use wxVector instead of array macro in atomic unit test No real changes. --- tests/thread/atomic.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/tests/thread/atomic.cpp b/tests/thread/atomic.cpp index bb8849a80f..2d28ed475e 100644 --- a/tests/thread/atomic.cpp +++ b/tests/thread/atomic.cpp @@ -20,8 +20,9 @@ #include "wx/thread.h" #include "wx/dynarray.h" #include "wx/log.h" +#include "wx/vector.h" -WX_DEFINE_ARRAY_PTR(wxThread *, wxArrayThread); +typedef wxVector wxArrayThread; // ---------------------------------------------------------------------------- // constants @@ -123,7 +124,7 @@ TEST_CASE("Atomic::WithThreads", "[atomic]") delete thread; } else - threads.Add(thread); + threads.push_back(thread); } for ( i = 0; i < count; ++i ) From cf66ce5f9406cb2a516b27d8957148ced7bf87a9 Mon Sep 17 00:00:00 2001 From: Vadim Zeitlin Date: Wed, 11 May 2022 00:12:43 +0200 Subject: [PATCH 6/8] Return the new value from wxAtomicInc() too This is easy to do and can be useful when using an atomic initialization counter. The only platform where not returning the new value might be more efficient than returning it is Solaris which is not really relevant any more and on all the other platforms the functions we were already using provided this value already (or almost, in case of using gcc builtins, when we just have to use a different one). --- build/aclocal/atomic_builtins.m4 | 7 ++----- configure | 9 +++------ include/wx/atomic.h | 29 +++++++++++++---------------- interface/wx/atomic.h | 9 ++++++--- tests/thread/atomic.cpp | 11 +++++------ 5 files changed, 29 insertions(+), 36 deletions(-) diff --git a/build/aclocal/atomic_builtins.m4 b/build/aclocal/atomic_builtins.m4 index eac5d3336a..6ebc2279af 100644 --- a/build/aclocal/atomic_builtins.m4 +++ b/build/aclocal/atomic_builtins.m4 @@ -6,16 +6,13 @@ AC_DEFUN([WX_ATOMIC_BUILTINS], [ AC_REQUIRE([AC_PROG_CC]) if test -n "$GCC"; then - AC_MSG_CHECKING([for __sync_fetch_and_add and __sync_sub_and_fetch builtins]) + AC_MSG_CHECKING([for __sync_xxx_and_fetch builtins]) AC_CACHE_VAL(wx_cv_cc_gcc_atomic_builtins, [ AC_TRY_LINK( [], [ unsigned int value=0; - /* wxAtomicInc doesn't use return value here */ - __sync_fetch_and_add(&value, 2); - __sync_sub_and_fetch(&value, 1); - /* but wxAtomicDec does, so mimic that: */ + volatile unsigned int r1 = __sync_add_and_fetch(&value, 2); volatile unsigned int r2 = __sync_sub_and_fetch(&value, 1); ], wx_cv_cc_gcc_atomic_builtins=yes, diff --git a/configure b/configure index 1f78571587..e3ba68438d 100755 --- a/configure +++ b/configure @@ -25145,8 +25145,8 @@ fi if test -n "$GCC"; then - { $as_echo "$as_me:${as_lineno-$LINENO}: checking for __sync_fetch_and_add and __sync_sub_and_fetch builtins" >&5 -$as_echo_n "checking for __sync_fetch_and_add and __sync_sub_and_fetch builtins... " >&6; } + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for __sync_xxx_and_fetch builtins" >&5 +$as_echo_n "checking for __sync_xxx_and_fetch builtins... " >&6; } if ${wx_cv_cc_gcc_atomic_builtins+:} false; then : $as_echo_n "(cached) " >&6 else @@ -25159,10 +25159,7 @@ main () { unsigned int value=0; - /* wxAtomicInc doesn't use return value here */ - __sync_fetch_and_add(&value, 2); - __sync_sub_and_fetch(&value, 1); - /* but wxAtomicDec does, so mimic that: */ + volatile unsigned int r1 = __sync_add_and_fetch(&value, 2); volatile unsigned int r2 = __sync_sub_and_fetch(&value, 1); ; diff --git a/include/wx/atomic.h b/include/wx/atomic.h index 25bd049834..5112623515 100644 --- a/include/wx/atomic.h +++ b/include/wx/atomic.h @@ -17,10 +17,7 @@ // get the value of wxUSE_THREADS configuration flag #include "wx/defs.h" -// constraints on the various functions: -// - wxAtomicDec must return a zero value if the value is zero once -// decremented else it must return any non-zero value (the true value is OK -// but not necessary). +// these functions return the new value, after the operation #if wxUSE_THREADS @@ -31,9 +28,9 @@ // http://bugs.mysql.com/bug.php?id=28456 // http://golubenco.org/blog/atomic-operations/ -inline void wxAtomicInc (wxUint32 &value) +inline wxUint32 wxAtomicInc (wxUint32 &value) { - __sync_fetch_and_add(&value, 1); + return __sync_add_and_fetch(&value, 1); } inline wxUint32 wxAtomicDec (wxUint32 &value) @@ -47,9 +44,9 @@ inline wxUint32 wxAtomicDec (wxUint32 &value) // include standard Windows headers #include "wx/msw/wrapwin.h" -inline void wxAtomicInc (wxUint32 &value) +inline wxUint32 wxAtomicInc (wxUint32 &value) { - InterlockedIncrement ((LONG*)&value); + return InterlockedIncrement ((LONG*)&value); } inline wxUint32 wxAtomicDec (wxUint32 &value) @@ -60,9 +57,9 @@ inline wxUint32 wxAtomicDec (wxUint32 &value) #elif defined(__DARWIN__) #include "libkern/OSAtomic.h" -inline void wxAtomicInc (wxUint32 &value) +inline wxUint32 wxAtomicInc (wxUint32 &value) { - OSAtomicIncrement32 ((int32_t*)&value); + return OSAtomicIncrement32 ((int32_t*)&value); } inline wxUint32 wxAtomicDec (wxUint32 &value) @@ -76,7 +73,7 @@ inline wxUint32 wxAtomicDec (wxUint32 &value) inline void wxAtomicInc (wxUint32 &value) { - atomic_add_32 ((uint32_t*)&value, 1); + return atomic_add_32_nv ((uint32_t*)&value, 1); } inline wxUint32 wxAtomicDec (wxUint32 &value) @@ -94,7 +91,7 @@ inline wxUint32 wxAtomicDec (wxUint32 &value) #else // else of wxUSE_THREADS // if no threads are used we can safely use simple ++/-- -inline void wxAtomicInc (wxUint32 &value) { ++value; } +inline wxUint32 wxAtomicInc (wxUint32 &value) { return ++value; } inline wxUint32 wxAtomicDec (wxUint32 &value) { return --value; } #endif // !wxUSE_THREADS @@ -120,10 +117,10 @@ public: wxAtomicInt32& operator=(wxInt32 v) { m_value = v; return *this; } - void Inc() + wxInt32 Inc() { wxCriticalSectionLocker lock(m_locker); - ++m_value; + return ++m_value; } wxInt32 Dec() @@ -137,14 +134,14 @@ private: wxCriticalSection m_locker; }; -inline void wxAtomicInc(wxAtomicInt32 &value) { value.Inc(); } +inline wxInt32 wxAtomicInc(wxAtomicInt32 &value) { return value.Inc(); } inline wxInt32 wxAtomicDec(wxAtomicInt32 &value) { return value.Dec(); } #else // !wxNEEDS_GENERIC_ATOMIC_OPS #define wxHAS_ATOMIC_OPS -inline void wxAtomicInc(wxInt32 &value) { wxAtomicInc((wxUint32&)value); } +inline wxInt32 wxAtomicInc(wxInt32 &value) { return wxAtomicInc((wxUint32&)value); } inline wxInt32 wxAtomicDec(wxInt32 &value) { return wxAtomicDec((wxUint32&)value); } typedef wxInt32 wxAtomicInt32; diff --git a/interface/wx/atomic.h b/interface/wx/atomic.h index 441e6eb610..0eefd2d424 100644 --- a/interface/wx/atomic.h +++ b/interface/wx/atomic.h @@ -22,15 +22,18 @@ but is implemented in a generic way using a critical section which can be prohibitively expensive for use in performance-sensitive code. + Returns the new value after the increment (the return value is only + available since wxWidgets 3.1.7, this function doesn't return anything in + previous versions of the library). + @header{wx/atomic.h} */ -void wxAtomicInc(wxAtomicInt& value); +wxInt32 wxAtomicInc(wxAtomicInt& value); /** This function decrements value in an atomic manner. - Returns 0 if value is 0 after decrement or any non-zero value (not - necessarily equal to the value of the variable) otherwise. + Returns the new value after decrementing it. @see wxAtomicInc diff --git a/tests/thread/atomic.cpp b/tests/thread/atomic.cpp index 2d28ed475e..81c0672d7d 100644 --- a/tests/thread/atomic.cpp +++ b/tests/thread/atomic.cpp @@ -77,15 +77,14 @@ TEST_CASE("Atomic::NoThread", "[atomic]") CHECK( int2 == -ITERATIONS_NUM ); } -TEST_CASE("Atomic::DecReturn", "[atomic]") +TEST_CASE("Atomic::ReturnValue", "[atomic]") { wxAtomicInt i(0); - wxAtomicInc(i); - wxAtomicInc(i); - CHECK( i == 2 ); + REQUIRE( wxAtomicInc(i) == 1 ); + REQUIRE( wxAtomicInc(i) == 2 ); - CHECK( wxAtomicDec(i) > 0 ); - CHECK( wxAtomicDec(i) == 0 ); + REQUIRE( wxAtomicDec(i) == 1 ); + REQUIRE( wxAtomicDec(i) == 0 ); } TEST_CASE("Atomic::WithThreads", "[atomic]") From c036bdf3e5bc92df3dc16138f0120b531f82b517 Mon Sep 17 00:00:00 2001 From: Vadim Zeitlin Date: Wed, 11 May 2022 00:15:22 +0200 Subject: [PATCH 7/8] Recommend using std::atomic instead of our functions The standard class is much more flexible and type-safe. --- interface/wx/atomic.h | 3 +++ 1 file changed, 3 insertions(+) diff --git a/interface/wx/atomic.h b/interface/wx/atomic.h index 0eefd2d424..bf0b22d255 100644 --- a/interface/wx/atomic.h +++ b/interface/wx/atomic.h @@ -16,6 +16,9 @@ /** This function increments @a value in an atomic manner. + @note It is recommended to use @c std::atomic available in C++11 and later + instead of this function in any new code. + Whenever possible wxWidgets provides an efficient, CPU-specific, implementation of this function. If such implementation is available, the symbol wxHAS_ATOMIC_OPS is defined. Otherwise this function still exists From fbeccb3954e6f45851b2ae6dd888932b3ffff8bc Mon Sep 17 00:00:00 2001 From: Vadim Zeitlin Date: Wed, 11 May 2022 00:15:46 +0200 Subject: [PATCH 8/8] Use wxAtomicInt for the global initialization counter This is slightly more efficient and simpler than using a separate critical section and can easily be done now that wxAtomicInc() returns the new value. No real changes. --- src/common/init.cpp | 34 +++++++++------------------------- 1 file changed, 9 insertions(+), 25 deletions(-) diff --git a/src/common/init.cpp b/src/common/init.cpp index 6b5956956a..e6d33d088d 100644 --- a/src/common/init.cpp +++ b/src/common/init.cpp @@ -28,7 +28,7 @@ #endif #include "wx/init.h" -#include "wx/thread.h" +#include "wx/atomic.h" #include "wx/scopedptr.h" #include "wx/except.h" @@ -130,22 +130,20 @@ static inline void Use(void *) { } static struct InitData { InitData() + : nInitCount(0) { - nInitCount = 0; - #if wxUSE_UNICODE argc = argcOrig = 0; // argv = argvOrig = NULL; -- not even really needed #endif // wxUSE_UNICODE } - // critical section protecting the counter and allowing to call - // wxInitialize() concurrently from different threads - wxCRIT_SECT_DECLARE_MEMBER(csInit); - // number of times wxInitialize() was called minus the number of times // wxUninitialize() was - size_t nInitCount; + // + // it is atomic to allow more than one thread to call wxInitialize() but + // only one of them to actually initialize the library + wxAtomicInt nInitCount; #if wxUSE_UNICODE int argc; @@ -531,17 +529,11 @@ bool wxInitialize() bool wxInitialize(int& argc, wxChar **argv) { - { - wxCRIT_SECT_LOCKER(lockInit, gs_initData.csInit); - - if ( gs_initData.nInitCount++ ) + if ( wxAtomicInc(gs_initData.nInitCount) != 1 ) { // already initialized return true; } - } // Do not keep the critical section locked, this could potentially result - // in deadlocks and is unnecessary anyhow as wxEntryStart() will be - // called exactly once and so doesn't need to be protected. return wxEntryStart(argc, argv); } @@ -549,15 +541,11 @@ bool wxInitialize(int& argc, wxChar **argv) #if wxUSE_UNICODE bool wxInitialize(int& argc, char **argv) { - { - wxCRIT_SECT_LOCKER(lockInit, gs_initData.csInit); - - if ( gs_initData.nInitCount++ ) + if ( wxAtomicInc(gs_initData.nInitCount) != 1 ) { // already initialized return true; } - } // See the comment in the overload above. return wxEntryStart(argc, argv); } @@ -565,12 +553,8 @@ bool wxInitialize(int& argc, char **argv) void wxUninitialize() { - { - wxCRIT_SECT_LOCKER(lockInit, gs_initData.csInit); - - if ( --gs_initData.nInitCount != 0 ) + if ( wxAtomicDec(gs_initData.nInitCount) != 0 ) return; - } // Don't keep the critical section locked during cleanup call neither. wxEntryCleanup(); }