[Interest] Expected event execution order in this multi-thread application

Richard Weickelt richard at weickelt.de
Sat Oct 5 15:13:56 CEST 2019


>> Because I attached a debugger and stopped T1 during
>> QCoreApplication::processEvents(). I can see E3 (the one that the thread is
>> currently processing) in postEventList at index 0 and E2 at index 1. That's
>> it. From there I see the following call chain
>> QEventDispatcherGlib::processEvents() followed by emit awake() because
>> canWait is false. I haven't traced it further. The signal emission doesn't
>> have any consequence in the event loop though.
> 
> Can you check what happens when it calls into 
> QCoreApplicationPrivate::sendPostedEvents()? That function should loop from 
> data->postEventList.startOffset to data->postEventList.size() (size at the 
> time you call into the function, so no new events wil be handed).
> Like you said, it should be handled on the first iteration after it's been
> queued.

The problem is that my explicit calls to QCoreApplication::processEvents()
do not reach QCoreApplicationPrivate::sendPostedEvents(). I found the root
cause:

1. Posted events will increase a counter (serial number) in the glib event
dispatcher

2. Before a call into QCoreApplication::sendPostedEvents, the dispatcher
stores the current counter value (lastSerialNumber). If multiple events are
sitting in the event queue, the counter value will reflect the last posted
event.

3. Explicit periodic calls to QCoreApplication::processEvents() go into
QEventDispatcherGlib::processEvents() which invokes an iteration in the glib
event loop. So far so good.

4. The glib event loop now calls postEventSourcePrepare() which compares the
stored lastSerialNumber to the current serial number. This function acts as
a filter to decide whether the glib event loop should invoke
QCoreApplicationPrivate::sendPostedEvents(). Since it finds lastSerialNumber
to be equal to the current serial number, it doesn't do anything.

Here is a minimal example of what is happening. It doesn't even need
multiple threads:

    #include <QtCore/QAbstractEventDispatcher>
    #include <QtCore/QCoreApplication>
    #include <QtCore/QDebug>

    namespace {
        QAtomicInt done = 0;
    }

    void E1();
    void E2();
    void E3();

    void E1() {
        qDebug() << "E1";
        QMetaObject::invokeMethod(qApp, &E2, Qt::QueuedConnection);
        QMetaObject::invokeMethod(qApp, &E3, Qt::QueuedConnection);
    }

    void E2() {
        qDebug() << "E2";
        while (done == 0) {
            QCoreApplication::processEvents();
        }
        QCoreApplication::exit(0);
    }

    void E3() {
        // We never reach this
        qDebug() << "E3";
        done = 1;
    }

    int main(int argc, char *argv[])
    {
        QCoreApplication app(argc, argv);
        QMetaObject::invokeMethod(qApp, &E1, Qt::QueuedConnection);
        app.exec();
    }


As soon as some other thread posts an event E4 to the current event loop, E3
will be executed, followed by E4 as expected.

>> It is a public API and it is not marked as deprecated. If the function does
>> not behave as documented, then either the documentation is wrong or the
>> implementation has a bug or I am using it the wrong way. I don't think the
>> latter is the case, but maybe I need a minimal test case to prove that.
> 
> It's not broken, that's why it's not deprecated. But that doesn't mean it's a 
> good API. It's not advisable to use nested event loops. processEvents() is 
> just the worst kind of nested event loops.

Sure. Unfortunately, our application relies on QScriptEngine and requires
that the engine's thread stays somewhat responsive. Now that I understand
the problem, I might be able to implement a work-around in our application.

But I would consider this behavior to be a bug in qeventdispatcher_glib. It
can never be wrong to fix bugs, even in bad APIs ;-)

https://bugreports.qt.io/browse/QTBUG-79020

Richard


More information about the Interest mailing list