4

I'm facing a strange bug in my Qt 5.7 (on Windows 10) application and the usual culprits for this kind of behaviour are nowhere to be found:

  • Object that is moved has a parent - most certainly not the case
  • Attempting to pull object to thread instead of pushing it - this is the reason for the error however I have no idea where it's coming from

The full error message is

QObject::moveToThread: Current thread (0x2afcca68) is not the object's thread (0x34f4acc8). Cannot move to target thread (0x34f4adc8)

QObject::setParent: Cannot set parent, new parent is in a different thread

and here is also my code:

main.cpp

#include <QApplication>
#include <QQuickItem>
#include "CustomQuickWidget.h"

int main(int argc, char *argv[])
{
    QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
    QApplication app(argc, argv);

    const QUrl source = QUrl(QLatin1String("qrc:/main"));
    CustomQuickWidget widget(source);

    return app.exec();
}

main (alias for main.qml):

// You can put any random QML content in this case really as long as it doesn't create a window since the CustomQuickWidget does that.
Rectangle {
    id: window
    visible: true
    width: 600
    height: 480
}

CustomQuickWidget.cpp

#include "CustomQuickWidget.h"
#include <QQuickItem>

CustomQuickWidget::CustomQuickWidget(const QUrl &source, QWidget *parent) : QQuickWidget(source, parent) {
    // Setup the recognizer
    this->airWheelRecognizer = new QAirWheelGestureRecognizer();
    this->airWheelType = QGestureRecognizer::registerRecognizer(airWheelRecognizer);
    // and turn on grabbing for all the supported gestures
    grabGesture(airWheelType);
    grabGesture(Qt::SwipeGesture);
    grabGesture(Qt::TapGesture);

    // Create thread and device worker
    this->deviceThread = new QThread(this);
    this->deviceWorker = new DeviceMapper(this, Q_NULLPTR); // NOTE: this here is NOT for parent. The constructor's signature for this class is: DeviceMapper(QObject* receiver, QList<Qt::GestureType>* gestureIDs, QObject* parent = Q_NULLPTR)
    this->deviceWorker->init();

    // Create timer that will trigger the data retrieval slot upon timeout
    this->timer = new QTimer();
    this->timer->setTimerType(Qt::PreciseTimer);
    this->timer->setInterval(5);

    // Move timer and device mapper to other thread
    this->timer->moveToThread(this->deviceThread);
    this->deviceWorker->moveToThread(this->deviceThread); // FIXME For unknown reason: QObject::moveToThread: Current thread (...) is not the object's thread. Cannot move to target thread

    // Connect widget, timer and device mapper
    createConnections();

    // Run thread
    this->deviceThread->start();

    // Connect device and start data retrieval
    QTimer::singleShot(0, this->deviceWorker, &(this->deviceWorker->slotToggleConnection));
    QTimer::singleShot(0, this->deviceWorker, &(this->deviceWorker->slotToggleRun));

    this->show();
}

CustomQuickWidget::~CustomQuickWidget()
{
    if (this->deviceThread) {
        this->deviceThread->quit();
        this->deviceThread->wait();
    }
}

void CustomQuickWidget::createConnections()
{
    connect(this->timer, SIGNAL(timeout()),
            this->deviceWorker, SLOT(slotRetrieveData()));

    connect(this->deviceThread, SIGNAL(started()),
            this->timer, SLOT(start()));
    connect(this->deviceThread, SIGNAL(finished()),
            this->deviceWorker, SLOT(deleteLater()));
    connect(this->deviceThread, SIGNAL(finished()),
            this->deviceThread, SLOT(deleteLater()));
}

bool CustomQuickWidget::event(QEvent* event) {
    if (event->type() == QEvent::Gesture) { 
        bool res = gestureEvent(static_cast<QGestureEvent*>(event)); // Not important so not included as code here
        return res;
    }

    return QWidget::event(event);
}

As you can see I have a typical worker-thread-thing going on here. I've made sure that my worker (here DeviceMapper) doesn't have a parent. It is also instantiated inside my widget (where the QThread is also created) but moved to the thread along with a timer.

Now beside the obvious issue here that is in the title I have to mention the following:

  • There is no such error when this->timer->moveToThread(this->deviceThread); is called
  • This very same code works without any issue in another project, which is a subdirs project - one sub-project creates the shared library (which I'm using in this project too) and the other - an application that uses the library.

The only difference between my other application and this one is the usage of QQuickWidget (instead of QWidget) and QML. I'm quite new to QML and this is also my first QQuickWidget so I might be missing some obvious setting that needs to be "activated".

I've also added

cout << this->deviceWorker->thread()->currentThreadId() << endl;
cout << this->thread()->currentThreadId() << endl;

right before this->deviceWorker->moveToThread(this->deviceThread); and I got

0x18b0
0x18b0

which means that before the moveToThread(...) my object belongs to the same thread where the QThread is instantiated. Printing the thread ID after the moveToThread(...) returns the same result but this is expected due to the failure to properly move the object to the other thread.


UPDATE:

The error message appears ONLY when building in release mode however no matter the type of build I have the bug is still present.

rbaleksandar
  • 8,713
  • 7
  • 76
  • 161
  • 1
    `QThread::currentThreadId()` is a static function that returns the thread of the currently executing code, not the thread where the object lives in. Just debug `QObject::thread()` to find out in which thread the object lives in. – thuga Aug 09 '16 at 12:05
  • Ah, okay. Will do that right away. – rbaleksandar Aug 09 '16 at 12:09
  • 1
    Install a custom message handler via `qInstallMessageHandler` and set a breakpoint in there, then wait until you get that warning from Qt. Then look up in the stack to check which QObject is actually emitting that. It might be some sub-object of `deviceWorker`. – peppe Aug 09 '16 at 12:22
  • On @thug's suggestion: I've checked and the `threadId` remains 0. As for @peppe's way - the problem is that the message appears only in `release` mode hence I can't debug it. I've read somewhere that I can remove the `-O3` optimization level flag from the generated `Makefile` to prevent stripping the binary of all the vital debug info but I don't know if this actually works. – rbaleksandar Aug 09 '16 at 12:36
  • @rbaleksandar No, you should leave the same optimisation level so that you're debugging the same program... but add `-g` to include debug symbols. The fact that the behaviour differs between optimisation levels indicates that you're invoking UB somewhere, but that debug mode doesn't apply some optimisation that reveals it. – underscore_d Aug 09 '16 at 13:13
  • Thanks. I've added the flag to both the `CFLAGS` and `CXXFLAGS` (just to make sure I didn't miss something so now I have `-pipe -fno-keep-inline-dllexport -ggdb -O2 -std=gnu++11 -frtti -Wall -Wextra -fexceptions -mthreads $(DEFINES)`. However I'm still unable to debug. Am I putting the flag at the wrong location. I've added this to the `Makefile.Release` file inside my `build` library since this is the one that is called from the top-level `Makefile` when doing a release build. – rbaleksandar Aug 09 '16 at 13:29
  • 1
    @rbaleksandar My `makefile` includes it in the link step too, though I'm not totally sure whether that's required. You also need to make sure the binary isn't being `strip`ped of symbols, which would undo all your effort! – underscore_d Aug 09 '16 at 13:31
  • 2
    Btw, I know much of Qt's outdated docs use it, which is very unfortunate, but all the manual `new`/`delete` everywhere usually aren't needed, open you up to accidents, and aren't good/modern C++ style... which nor was Qt, but it's finally making a proper effort recently, if only all the outdated docs and tutorials out there would catch up. I suggest changing any object that you don't require to outlive the scope in which it's declared, to have automatic/by-value storage duration. That may or may not affect this - but is just good style and will probably prevent various other possible headaches – underscore_d Aug 09 '16 at 13:37
  • 1
    @underscore_d Well spotted. I have `-s -Wl` inside the `LFLAGS` which does exactly that - it strips the binary of any debug symbols. I removed it and now the debugger kicks in when in release mode. Thanks! As for the `new`/`delete` stuff - I try to avoid it but when it but here I have no choice (I did convert the `QThread` member to be on the stack) since both my timer and the `deviceWorker` need to be dynamically allocated. Otherwise once the constructor is done they will go out of scope and that's that. :D – rbaleksandar Aug 09 '16 at 13:47
  • 1
    @rbaleksandar Great - glad I guessed correctly! Let us know how the debugging session goes. – underscore_d Aug 09 '16 at 13:51
  • 2
    @rbaleksandar `both my timer and the deviceWorker need to be dynamically allocated. Otherwise once the constructor is done they will go out of scope and that's that.` But this isn't true! They're class members. The only things that can ever go out of scope at the end of the constructor are local variables declared within its body. If you gave these members automatic storage duration, they would live for as long as their containing object does. – underscore_d Aug 09 '16 at 13:57
  • 3
    You're either calling `DeviceWorker`'s non-thread-safe methods from wrong thread(s), or the `DeviceWorker` owns some objects that are not its children. Either way, you *absolutely* must post a complete example, and it should be in one file. It's thoroughly pointless to have separate header files in such a test case. You should also keep removing code until none can be removed without the error going away. Shove everything into a `main.cpp` that ends with `#include "main.moc"`. See e.g. [this answer](http://stackoverflow.com/a/38798100/1329652) for an idea. – Kuba hasn't forgotten Monica Aug 09 '16 at 14:29
  • @underscore_d I'd say `new` is needed as much or little as it ever was with `QObject` and other classes with parent-child semantics, and `delete` is needed as little as it ever was with them. That hasn't really changed at all with modern C++ (except people actually realizing they don't need to put everything in the heap separately, they can use member variables etc). – hyde Aug 16 '16 at 08:39
  • @hyde I guess I was thinking of 2 things: (1) modern _Qt_ style, significant in which (aside from fewer macros) is people realising heap use is far less necessary than they thought - & than/because official documentation indicated! - all along. And (2) modern C++ _style/ethos_, as per the big bloggers, etc. - which is more about use than features, but newer Standards provide many/even easier ways to heed said advice. Constant advice coming from #2 - increased emphasis on not manually managing memory, using smart pointers, esp. value semantics, etc - probably helps people realise they can do #1 – underscore_d Aug 16 '16 at 09:04

1 Answers1

1

I have managed to solve my problem by pinpointing WHEN it's happening.

At the end of last week the application I was writing started working all of a sudden so even though it bothered me why all that happened before that I let it be. I have neither changed the library's code (except for a couple of comments in my code which obviously cannot affect the code itself) nor the C++ code of my QML application. All I have changed was my QML but in a way that didn't actually relate to the C++ code underneath. The only thing I changed was the build type. However I didn't actually notice that last week.

Yesterday I started working on a new project. And right after doing the very first run I got the same issue. It drove me nuts. So I started analyzing my code (@Kuba Ober, sorry mate, but posting the complete code or even a small chunk of the library is not possible otherwise I would have done it (even though it's a couple of hundreds of lines of actual code (excluding stuff such as comments and empty lines)). I checked and double-checked the parent-child relationships but couldn't find anything that could give me even a small hint when and why this is happening. I have also analyzed the stack to the best of my abilities but all in vain.

Then it struck me...I've mentioned above that my previous project started working all of a sudden after changing its build type. And indeed this was the source of all evil in my situation. The way I add my library to my projects (excluding the initial one which along with the library is part of the same subdir project) is by creating a folder in the root directory of my new project called libs and copying the related stuff to it. Now after I finished working on my library and did some testing I obviously decided to switch to release build. However I copied a library build in release mode to a project build in debug mode. So after a couple of rebuilds and copying the library here and there I found out that mixing the build types of the application that uses the library and the library itself lead to this issue.

I know that mixing build types is a bad idea and I don't that normally but this time it just slipped my mind and was a total accident. I don't know what is happening internally when both the application with X build type and the library with Y build type are mixed but the result in my case was the error I have posted in this thread.

Thanks for all the help. I learned a lot through your comments! Even though debugging was not necessary in my case you have my gratitude. :)

rbaleksandar
  • 8,713
  • 7
  • 76
  • 161
  • So, even if you compile application & library w/ build types mismatched - but _opposite_ way around - you got the same results? That might explain, but I worry it may equally be due to different optimisations alternately masking/revealing results of UB. I've seen something _reminiscent of_ what you said, but it was because I'd inadvertently compiled my program with the wrong compiler (lol) & linked it against a library compiled by the right one. That one I understand intuitively. Incompatibile build modes from same compiler, not so much. But I might be wrong... What do our build experts think? – underscore_d Aug 16 '16 at 09:11
  • Yes, the only scenario where things work is when their build types match that is dll (in debug) + app (in debug) and dll (in release) + app (in release) = no issue. As for the exact **why** the issue is occurring I wrote that I don't have the faintest idea. Since I'm a curious person I would love to know about that but given that I can't provide the code for the library I don't see how more can be said about the issue. Btw the original `subdir` project builds both the application and the shared library with the same build type so that's why the issue didn't appear earlier. – rbaleksandar Aug 16 '16 at 09:17