tst_qthread: use QSemaphore to fix a race and a harmless TSAN warning

The current code with two QWaitConditions, a mutex and a bool lead
to a TSAN warning about lock order inversion: the first for loop locks
mutexes M0, M1, M2 etc. and then the second for loop calls QWaitCondition::wait(M0)
which unlocks+relocks M0, while M1 is locked.

It was also racy, in my opinion: with QWaitCondition there's the risk of
calling wakeOne before the other thread calls wait, and then the
notification is missed.

Ported to QSemaphore which doesn't have either problem.

Change-Id: I41574e944a2631cf42a81d3923abb658c862c25d
Reviewed-by: Thiago Macieira <thiago.macieira@intel.com>
This commit is contained in:
David Faure 2025-03-05 17:45:50 +01:00
parent 10affadd12
commit d1462ec5a3

View File

@ -716,19 +716,18 @@ void noop(void*) { }
class NativeThreadWrapper
{
public:
NativeThreadWrapper() : qthread(nullptr), waitForStop(false) {}
NativeThreadWrapper() : qthread(nullptr), stopSemaphore(1) {}
void start(FunctionPointer functionPointer = noop, void *data = nullptr);
void startAndWait(FunctionPointer functionPointer = noop, void *data = nullptr);
void join();
void setWaitForStop() { waitForStop = true; }
void setWaitForStop() { stopSemaphore.acquire(); }
void stop();
ThreadHandle nativeThreadHandle;
QThread *qthread;
QWaitCondition startCondition;
QMutex mutex;
bool waitForStop;
QWaitCondition stopCondition;
QSemaphore startSemaphore;
QSemaphore stopSemaphore;
protected:
static void *runUnix(void *data);
static unsigned WIN_FIX_STDCALL runWin(void *data);
@ -752,9 +751,8 @@ void NativeThreadWrapper::start(FunctionPointer functionPointer, void *data)
void NativeThreadWrapper::startAndWait(FunctionPointer functionPointer, void *data)
{
QMutexLocker locker(&mutex);
start(functionPointer, data);
startCondition.wait(locker.mutex());
startSemaphore.acquire();
}
void NativeThreadWrapper::join()
@ -775,20 +773,13 @@ void *NativeThreadWrapper::runUnix(void *that)
nativeThreadWrapper->qthread = QThread::currentThread();
// Release main thread.
{
QMutexLocker lock(&nativeThreadWrapper->mutex);
nativeThreadWrapper->startCondition.wakeOne();
}
nativeThreadWrapper->startSemaphore.release();
// Run function.
nativeThreadWrapper->functionPointer(nativeThreadWrapper->data);
// Wait for stop.
{
QMutexLocker lock(&nativeThreadWrapper->mutex);
if (nativeThreadWrapper->waitForStop)
nativeThreadWrapper->stopCondition.wait(lock.mutex());
}
nativeThreadWrapper->stopSemaphore.acquire();
return nullptr;
}
@ -801,9 +792,7 @@ unsigned WIN_FIX_STDCALL NativeThreadWrapper::runWin(void *data)
void NativeThreadWrapper::stop()
{
QMutexLocker lock(&mutex);
waitForStop = false;
stopCondition.wakeOne();
stopSemaphore.release();
}
static bool threadAdoptedOk = false;
@ -993,13 +982,11 @@ void tst_QThread::adoptMultipleThreadsOverlap()
for (int i = 0; i < numThreads; ++i) {
nativeThreads.append(new NativeThreadWrapper());
nativeThreads.at(i)->setWaitForStop();
nativeThreads.at(i)->mutex.lock();
nativeThreads.at(i)->start();
}
for (int i = 0; i < numThreads; ++i) {
nativeThreads.at(i)->startCondition.wait(&nativeThreads.at(i)->mutex);
nativeThreads.at(i)->startSemaphore.acquire();
QObject::connect(nativeThreads.at(i)->qthread, SIGNAL(finished()), &recorder, SLOT(slot()));
nativeThreads.at(i)->mutex.unlock();
}
QObject::connect(nativeThreads.at(numThreads - 1)->qthread, SIGNAL(finished()), &QTestEventLoop::instance(), SLOT(exitLoop()));