Search code examples
c++multithreadingqtsignals-slots

A slot gets called in the middle of QThread::run


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.


Solution

  • 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.