I am modifying a multithreaded Qt application and have faced an unforeseen behaviour.
I have a class WorkerThread inherited from QThread with method run()
in it doing some work. A slot handleSuccess()
of an object of WorkerThread is connected to a signal from another — main thread, the signal is emitted in response to asynchronous ingoing connections from a server. As I understand, while run()
is running an flow control is not in exec()
of an event loop, the slot cannot be called. Instead, I see in logs of app that the thread is doing its work in the middle point of run()
and then instantly enters the slot without doing any preparations.
class WorkerThread : public QThread {
Q_OBJECT
public:
WorkerThread() { moveToThread(this); /* further initialization */ }
void run();
void foo();
public slots:
void handleSuccess(const QByteArray &);
void shutdown();
private:
QMutex mutex;
MyResource resource;
volatile bool mShutdown;
/* ... other declarations */
};
void WorkerThread::foo() {
QMutexLocker locker(&mutex);
/* Working with the guarded resource */
}
void WorkerThread::run() {
QEventLoop signalWaiterEventLoop;
connect(this, SIGNAL(terminated()), &signalWaiterEventLoop, SLOT(quit()), Qt::QueuedConnection);
connect(this, SIGNAL(shutdownThread()), &signalWaiterEventLoop, SLOT(quit()), Qt::QueuedConnection);
connect(this, SIGNAL(dbChanged()), &signalWaiterEventLoop, SLOT(quit()), Qt::QueuedConnection);
while (!mShutdown) {
signalWaiterEventLoop.exec();
if (mShutdown) break;
/* ... useful payload */
foo();
/* ... another payload */
}
}
void WorkerThread::handleSuccess(const QByteArray & data) {
log(Q_FUNC_INFO, __LINE__, __FILE__);
QMutexLocker locker(&mutex);
/* processing data */
}
void WorkerThread::shutdown()
{
mShutdown = true;
emit shutdownThread();
}
The object is created and initialized in the main thread:
void SomeClass::init() {
/* ... */
mWorker = new WorkerThread();
connect(connect(mProtocol, SIGNAL(dataReceived(QByteArray)), mWorker, SLOT(processSuccess(QByteArray)),
Qt::QueuedConnection));
mWorker->start();
/* ... */
}
The error occurs when foo()
gets called from inside run()
, locks the mutex, and then the flow control is passed to handleSuccess()
without any reason visible to me, and, at last, handleSuccess()
tries to lock the mutex resulting in a deadlock. I would stress that all this happens in a single thread, I've logged that. The fact is, the error is stable, arises every time in the same place. It's clear that I do not take something into account, but what exactly?
Update
I have rewritten the WorkerThread
into Worker: public QObject
and the former run()
became public slot handleNewArrivals()
, which is called in the thread's event loop in response to signals. Still, the problem remains: another slot is executed right in the middle of handleNewArrivals()
. The place of interruption is stable, here it is (seek evaluateTo()
):
bool hasContent = false;
QByteArray outData;
QBuffer inputBuffer(&data), outputBuffer(&outData);
inputBuffer.open(QIODevice::ReadOnly);
outputBuffer.open(QIODevice::WriteOnly|QIODevice::Append);
QXmlQuery xmlQuery;
xmlQuery.bindVariable("inputDocument", &inputBuffer);
xmlQuery.setQuery("doc($inputDocument)/commands//*");
QXmlSerializer serializer(xmlQuery, &outputBuffer);
log(Q_FUNC_INFO, __LINE__, __FILE__);
// the log line above appears, then after 1 ms the line
// from handleSuccess() appears successively.
xmlQuery.evaluateTo(&serializer);
// this line is never shown
log(Q_FUNC_INFO, __LINE__, __FILE__);
QXmlStreamReader xmlReader(outData);
while (!xmlReader.atEnd()) {
xmlReader.readNext();
if (xmlReader.tokenType() == QXmlStreamReader::Invalid) {
ufo::logT(this) << tr("Invalid token: %1").arg(xmlReader.errorString());
continue;
}
if (!xmlReader.isStartDocument() && !xmlReader.isEndDocument()) {
xmlWriterDb.writeCurrentToken(xmlReader);
hasContent = true;
}
}
ufo::logT(this) << tr("Selected data from payment, hasContent = %1").arg(hasContent?"true":"false");
inputBuffer.close();
outputBuffer.close();
What can be with QXmlQuery::evaluateTo and QXmlSerializer to cause such jump? It seems like there is an exception or unix-like signal (the software runs under Windows, mingw32) or something, though wrapping evaluateto()
or the whole body of slot in try-catch gives nothing.
I imagine you're being bitten by this bit of behavior documented for QXmlQuery
:
Event Handling
When QXmlQuery accesses resources (e.g., calling fn:doc() to load a file, or accessing a device via a bound variable), the event loop is used, which means events will be processed. To avoid processing events when QXmlQuery accesses resources, create your QXmlQuery instance in a separate thread.
If a dataReceived()
signal has been queued for the handleSuccess()
slot, when QXmlQuery
uses the event loop the event will be processed.