mirror of
https://github.com/wxWidgets/wxWidgets.git
synced 2026-10-06 07:00:16 +08:00
Wait for the threads to terminate before finishing the test and exiting
as they could be still running when it exits otherwise, resulting in
crashes in this test case, as happened in a recent CI run:
--------------------------------- >8 --------------------------------------
test 812
Start 812: MiscThread::ThreadConditions
812: Test command: /home/runner/work/wxWidgets/wxWidgets/build_cmake/lib/test_base "MiscThread::ThreadConditions"
812: Working Directory: /home/runner/work/wxWidgets/wxWidgets/build_cmake/lib/
812: Test timeout computed to be: 10000000
812: Test program for wxWidgets non-GUI features
812: build: 3.3.4 (wchar_t,compiler with C++ ABI compatible with gcc 4,STL containers,compatible with 3.2)
812: compiled using gcc 11.4
812: running under Ubuntu 22.04.5 LTS (Jammy Jellyfish), based on debian (jammy), Linux 6.8.0-1064-azure x86_64 as runner
812: Filters: MiscThread::ThreadConditions
812: ===============================================================================
812: All tests passed (32 assertions in 1 test case)
812:
812: malloc_consolidate(): unaligned fastbin chunk detected
812/871 Test #812: MiscThread::ThreadConditions .........................Subprocess aborted***Exception: 0.84 sec
--------------------------------- >8 --------------------------------------
This crash was difficult to reproduce and only became visible in the CI
after the recent changes resulting in running all the tests in parallel
as this starved this test from having enough cores to mask it. To
reproduce it locally "taskset -c 0 ./test MiscThread::ThreadConditions"
had to be used, this allows to reproduce it under TSAN after just a few
iterations while without taskset it passed 100 times out of 100.
394 lines
9.7 KiB
C++
394 lines
9.7 KiB
C++
///////////////////////////////////////////////////////////////////////////////
|
|
// Name: tests/thread/misc.cpp
|
|
// Purpose: Miscellaneous wxThread test cases
|
|
// Author: Francesco Montorsi (extracted from console sample)
|
|
// Created: 2010-05-10
|
|
// Copyright: (c) 2010 wxWidgets team
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
// ----------------------------------------------------------------------------
|
|
// headers
|
|
// ----------------------------------------------------------------------------
|
|
|
|
#include "testprec.h"
|
|
|
|
#ifndef WX_PRECOMP
|
|
#endif // WX_PRECOMP
|
|
|
|
#include "wx/thread.h"
|
|
#include "wx/utils.h"
|
|
|
|
#include <memory>
|
|
#include <vector>
|
|
|
|
// ----------------------------------------------------------------------------
|
|
// globals
|
|
// ----------------------------------------------------------------------------
|
|
|
|
static size_t gs_counter = (size_t)-1;
|
|
static wxCriticalSection gs_critsect;
|
|
static wxSemaphore gs_cond;
|
|
|
|
class MyJoinableThread : public wxThread
|
|
{
|
|
public:
|
|
MyJoinableThread(size_t n) : wxThread(wxTHREAD_JOINABLE)
|
|
{ m_n = n; Create(); }
|
|
|
|
// thread execution starts here
|
|
virtual ExitCode Entry() override;
|
|
|
|
private:
|
|
size_t m_n;
|
|
};
|
|
|
|
wxThread::ExitCode MyJoinableThread::Entry()
|
|
{
|
|
wxUIntPtr res = 1;
|
|
for ( size_t n = 1; n < m_n; n++ )
|
|
{
|
|
res *= n;
|
|
|
|
// it's a loooong calculation :-)
|
|
wxMilliSleep(100);
|
|
}
|
|
|
|
return (ExitCode)res;
|
|
}
|
|
|
|
class MyDetachedThread : public wxThread
|
|
{
|
|
public:
|
|
// If n == 0, thread must be cancelled to exit.
|
|
MyDetachedThread(size_t n, wxChar ch)
|
|
{
|
|
m_n = n;
|
|
m_ch = ch;
|
|
m_cancelled = false;
|
|
|
|
Create();
|
|
}
|
|
|
|
// thread execution starts here
|
|
virtual ExitCode Entry() override;
|
|
|
|
// and stops here
|
|
virtual void OnExit() override;
|
|
|
|
private:
|
|
size_t m_n; // number of characters to write
|
|
wxChar m_ch; // character to write
|
|
|
|
bool m_cancelled; // false if we exit normally
|
|
};
|
|
|
|
wxThread::ExitCode MyDetachedThread::Entry()
|
|
{
|
|
{
|
|
wxCriticalSectionLocker lock(gs_critsect);
|
|
if ( gs_counter == (size_t)-1 )
|
|
gs_counter = 1;
|
|
else
|
|
gs_counter++;
|
|
}
|
|
|
|
for ( size_t n = 0;; n++ )
|
|
{
|
|
if ( m_n && n == m_n )
|
|
break;
|
|
|
|
if ( TestDestroy() )
|
|
{
|
|
m_cancelled = true;
|
|
|
|
break;
|
|
}
|
|
|
|
//wxPutchar(m_ch);
|
|
//fflush(stdout);
|
|
|
|
wxMilliSleep(100);
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
void MyDetachedThread::OnExit()
|
|
{
|
|
//wxLogTrace(wxT("thread"), wxT("Thread %ld is in OnExit"), GetId());
|
|
|
|
wxCriticalSectionLocker lock(gs_critsect);
|
|
if ( !--gs_counter && !m_cancelled )
|
|
gs_cond.Post();
|
|
}
|
|
|
|
class MyWaitingThread : public wxThread
|
|
{
|
|
public:
|
|
MyWaitingThread( wxMutex *mutex, wxCondition *condition )
|
|
: wxThread(wxTHREAD_JOINABLE)
|
|
{
|
|
m_mutex = mutex;
|
|
m_condition = condition;
|
|
|
|
Create();
|
|
}
|
|
|
|
virtual ExitCode Entry() override
|
|
{
|
|
//wxPrintf(wxT("Thread %lu has started running.\n"), GetId());
|
|
gs_cond.Post();
|
|
|
|
//wxPrintf(wxT("Thread %lu starts to wait...\n"), GetId());
|
|
|
|
m_mutex->Lock();
|
|
m_condition->Wait();
|
|
m_mutex->Unlock();
|
|
|
|
//wxPrintf(wxT("Thread %lu finished to wait, exiting.\n"), GetId());
|
|
gs_cond.Post();
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
private:
|
|
wxMutex *m_mutex;
|
|
wxCondition *m_condition;
|
|
};
|
|
|
|
// semaphore tests
|
|
#include "wx/datetime.h"
|
|
|
|
class MySemaphoreThread : public wxThread
|
|
{
|
|
public:
|
|
MySemaphoreThread(int i, wxSemaphore *sem)
|
|
: wxThread(wxTHREAD_JOINABLE),
|
|
m_sem(sem),
|
|
m_i(i)
|
|
{
|
|
Create();
|
|
}
|
|
|
|
virtual ExitCode Entry() override
|
|
{
|
|
wxUnusedVar(m_i);
|
|
|
|
//wxPrintf(wxT("%s: Thread #%d (%ld) starting to wait for semaphore...\n"),
|
|
// wxDateTime::Now().FormatTime().c_str(), m_i, (long)GetId());
|
|
|
|
m_sem->Wait();
|
|
|
|
//wxPrintf(wxT("%s: Thread #%d (%ld) acquired the semaphore.\n"),
|
|
// wxDateTime::Now().FormatTime().c_str(), m_i, (long)GetId());
|
|
|
|
Sleep(1000);
|
|
|
|
//wxPrintf(wxT("%s: Thread #%d (%ld) releasing the semaphore.\n"),
|
|
// wxDateTime::Now().FormatTime().c_str(), m_i, (long)GetId());
|
|
|
|
m_sem->Post();
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
private:
|
|
wxSemaphore *m_sem;
|
|
int m_i;
|
|
};
|
|
|
|
// ----------------------------------------------------------------------------
|
|
// tests
|
|
// ----------------------------------------------------------------------------
|
|
|
|
// Use all the available CPUs for the threads created by the tests below.
|
|
static const struct SetConcurrency
|
|
{
|
|
SetConcurrency()
|
|
{
|
|
int nCPUs = wxThread::GetCPUCount();
|
|
if ( nCPUs != -1 )
|
|
wxThread::SetConcurrency(nCPUs);
|
|
}
|
|
} gs_setConcurrency;
|
|
|
|
TEST_CASE("MiscThread::Joinable", "[thread]")
|
|
{
|
|
// calc 10! in the background
|
|
MyJoinableThread thread(10);
|
|
CHECK( thread.Run() == wxTHREAD_NO_ERROR );
|
|
CHECK( (wxUIntPtr)thread.Wait() == 362880 );
|
|
}
|
|
|
|
TEST_CASE("MiscThread::Detached", "[thread]")
|
|
{
|
|
static const size_t nThreads = 3;
|
|
MyDetachedThread *threads[nThreads];
|
|
|
|
size_t n;
|
|
for ( n = 0; n < nThreads; n++ )
|
|
{
|
|
threads[n] = new MyDetachedThread(10, 'A' + n);
|
|
}
|
|
|
|
threads[0]->SetPriority(wxPRIORITY_MIN);
|
|
threads[1]->SetPriority(wxPRIORITY_MAX);
|
|
|
|
for ( n = 0; n < nThreads; n++ )
|
|
{
|
|
CHECK( threads[n]->Run() == wxTHREAD_NO_ERROR );
|
|
}
|
|
|
|
// wait until all threads terminate
|
|
CHECK( gs_cond.Wait() == wxSEMA_NO_ERROR );
|
|
}
|
|
|
|
TEST_CASE("MiscThread::Semaphore", "[thread]")
|
|
{
|
|
static const int SEM_LIMIT = 3;
|
|
|
|
wxSemaphore sem(SEM_LIMIT, SEM_LIMIT);
|
|
std::vector<std::unique_ptr<MySemaphoreThread>> threads;
|
|
|
|
for ( int i = 0; i < 3*SEM_LIMIT; i++ )
|
|
{
|
|
auto t = make_unique<MySemaphoreThread>(i, &sem);
|
|
CHECK( t->Run() == wxTHREAD_NO_ERROR );
|
|
|
|
threads.push_back(std::move(t));
|
|
}
|
|
|
|
for ( auto& t : threads )
|
|
{
|
|
CHECK( (wxUIntPtr)t->Wait() == 0 );
|
|
}
|
|
}
|
|
|
|
TEST_CASE("MiscThread::ThreadSuspend", "[thread]")
|
|
{
|
|
MyDetachedThread *thread = new MyDetachedThread(15, 'X');
|
|
|
|
CHECK( thread->Run() == wxTHREAD_NO_ERROR );
|
|
|
|
// this is for this demo only, in a real life program we'd use another
|
|
// condition variable which would be signaled from wxThread::Entry() to
|
|
// tell us that the thread really started running - but here just wait a
|
|
// bit and hope that it will be enough (the problem is, of course, that
|
|
// the thread might still not run when we call Pause() which will result
|
|
// in an error)
|
|
wxMilliSleep(300);
|
|
|
|
for ( size_t n = 0; n < 3; n++ )
|
|
{
|
|
thread->Pause();
|
|
|
|
if ( n > 0 )
|
|
{
|
|
// don't sleep but resume immediately the first time
|
|
wxMilliSleep(300);
|
|
}
|
|
|
|
CHECK( thread->Resume() == wxTHREAD_NO_ERROR );
|
|
}
|
|
|
|
// wait until the thread terminates
|
|
CHECK( gs_cond.Wait() == wxSEMA_NO_ERROR );
|
|
}
|
|
|
|
TEST_CASE("MiscThread::ThreadDelete", "[thread]")
|
|
{
|
|
// Check that deleting a thread which didn't start to run yet returns an
|
|
// error.
|
|
MyDetachedThread *thread0 = new MyDetachedThread(0, 'W');
|
|
CHECK( thread0->Delete() == wxTHREAD_MISC_ERROR );
|
|
|
|
// Check that deleting a running thread works.
|
|
MyDetachedThread *thread1 = new MyDetachedThread(0, 'X');
|
|
CHECK( thread1->Run() == wxTHREAD_NO_ERROR );
|
|
CHECK( thread1->Delete() == wxTHREAD_NO_ERROR );
|
|
|
|
// Create another thread and pause it before deleting.
|
|
MyDetachedThread *thread2 = new MyDetachedThread(0, 'Z');
|
|
CHECK( thread2->Run() == wxTHREAD_NO_ERROR );
|
|
CHECK( thread2->Pause() == wxTHREAD_NO_ERROR );
|
|
CHECK( thread2->Delete() == wxTHREAD_NO_ERROR );
|
|
|
|
// Delete a running joinable thread.
|
|
MyJoinableThread thread3(20);
|
|
CHECK( thread3.Run() == wxTHREAD_NO_ERROR );
|
|
CHECK( thread3.Delete() == wxTHREAD_NO_ERROR );
|
|
|
|
// Delete a joinable thread which already terminated.
|
|
MyJoinableThread thread4(2);
|
|
CHECK( thread4.Run() == wxTHREAD_NO_ERROR );
|
|
thread4.Wait();
|
|
CHECK( thread4.Delete() == wxTHREAD_NO_ERROR );
|
|
}
|
|
|
|
TEST_CASE("MiscThread::ThreadRun", "[thread]")
|
|
{
|
|
MyJoinableThread thread1(2);
|
|
CHECK( thread1.Run() == wxTHREAD_NO_ERROR );
|
|
thread1.Wait(); // wait until the thread ends
|
|
|
|
// verify that running twice the same thread fails
|
|
WX_ASSERT_FAILS_WITH_ASSERT( thread1.Run() );
|
|
}
|
|
|
|
TEST_CASE("MiscThread::ThreadConditions", "[thread]")
|
|
{
|
|
wxMutex mutex;
|
|
wxCondition condition(mutex);
|
|
|
|
// otherwise its difficult to understand which log messages pertain to
|
|
// which condition
|
|
//wxLogTrace(wxT("thread"), wxT("Local condition var is %08x, gs_cond = %08x"),
|
|
// condition.GetId(), gs_cond.GetId());
|
|
|
|
// create and launch threads
|
|
static const size_t NUM_THREADS = 10;
|
|
|
|
std::vector<std::unique_ptr<MyWaitingThread>> threads;
|
|
for ( size_t n = 0; n < NUM_THREADS; n++ )
|
|
{
|
|
threads.push_back(make_unique<MyWaitingThread>(&mutex, &condition));
|
|
}
|
|
|
|
for ( auto& t : threads )
|
|
{
|
|
CHECK( t->Run() == wxTHREAD_NO_ERROR );
|
|
}
|
|
|
|
// wait until all threads run
|
|
size_t nRunning = 0;
|
|
while ( nRunning < NUM_THREADS )
|
|
{
|
|
CHECK( gs_cond.Wait() == wxSEMA_NO_ERROR );
|
|
|
|
nRunning++;
|
|
}
|
|
|
|
wxMilliSleep(500);
|
|
|
|
// now wake one of them up
|
|
CHECK( condition.Signal() == wxCOND_NO_ERROR );
|
|
|
|
CHECK( gs_cond.Wait() == wxSEMA_NO_ERROR );
|
|
size_t nFinished = 1;
|
|
|
|
// wake all the (remaining) threads up, so that they can exit
|
|
CHECK( condition.Broadcast() == wxCOND_NO_ERROR );
|
|
|
|
while ( nFinished < NUM_THREADS )
|
|
{
|
|
CHECK( gs_cond.Wait() == wxSEMA_NO_ERROR );
|
|
|
|
nFinished++;
|
|
}
|
|
|
|
for ( auto& t : threads )
|
|
{
|
|
t->Wait();
|
|
}
|
|
}
|