[Interest] TSAN findings in a simple QApplication/QMetaObject::invokeMethod example

Thiago Macieira thiago.macieira at intel.com
Sat Nov 5 17:26:51 CET 2022


On Saturday, 5 November 2022 04:41:55 PDT Dennis Luehring wrote:
> but that even raised my TSAN warning amount :(
> 
> 
> Warnings from my example: https://pastebin.com/XnN6nzUT

But they're of higher quality now, with much richer backtraces.

The first one in that link I'm not sure about. The second one is interesting 
because it's claiming that free is racing with malloc. That's... surprising. I 
don't know if TSan is simply broken in your case or if it is missing a mutex 
somewhere that was supposed to show that there's no data race between the 
malloc() caller and the free() caller.

You must have compiled in release mode, because there seem to be some frames 
missing here:

    #1 g_malloc <null> (libglib-2.0.so.0+0x5e718)
    #2 QGtk3ThemePlugin::create(QString const&, QList<QString> const&) /home/
linux/dev/3rdparty-linux-gcc/qt6_dev/qt6/qtbase/src/plugins/platformthemes/
gtk3/main.cpp:22 (libqgtk3.so+0xb55a)

gtk3/main.cpp:22 is a simple new QGtk3Theme line, so the constructor must have 
been inlined. I was going to say that something tail-called into g_malloc... 
but that doesn't make sense, because one usually uses the result of g_malloc, 
and a tail call implies it is leaking. And we know it isn't leaking, because 
the pointer was (likely) used.

Anyway, libglib-2.0.so remains a black box for us. 

> also using
> 
> QT_NO_GLIB=1
> 
> does not reduce the warning amount

It won't help if the Gtk3 theme plugin is in use. Since it's just a plugin and 
you've built your own Qt, the simplest is to just delete the file from 
plugins/.

Skipping all those glib warnings, we get to a race that seems to be strictly 
in Qt. It was even helpful enough to print:

  Location is global 'QCoreApplication::self' of size 8 at 0x7f622f44e2c0 
(libQt6Core.so.6+0x000000b792c0)

The code it is noting is expected: the QCoreApplication destructor resets that 
pointer; other threads are expected to read it. However, it's not an atomic. 
Moreover, the last use from an aux thread came from:

 bool selfRequired = QCoreApplicationPrivate::threadRequiresCoreApplication();
 if (!self && selfRequired)

This is some code I wrote a while ago *for* QtDBus and selfRequired should 
have been false for this thread -- QDBusConnectionManager is in the stack 
trace and QDBusConnectionManager is a QDaemonThread. So why was the boolean 
true? One thing the backtrace doesn't tell us is the timing: it's possible 
that the event being handled here is *exactly* the event that will set that 
boolean to false.

As I said, I added QDaemonThread *for* QtDBus and it looks like I didn't fix 
everything. I also distinctly remember writing this changelog that appeared in 
5.6.0:

 - In Qt 6, QCoreApplication::notify() will not be called for events being
   delivered to objects outside the main thread. The reason for that is
   that the main application object may begin destruction while those
   threads are still delivering events, which is undefined behavior.
   Applications that currently override notify() and use that function
   outside the main thread are advised to find other solutions in the mean
   time.

But I don't remember changing the mechanism in Qt 6. Looks like I failed to 
live up to the promise/threat. And this is exactly the race that your code is 
showing.

-- 
Thiago Macieira - thiago.macieira (AT) intel.com
  Cloud Software Architect - Intel DCAI Cloud Engineering





More information about the Interest mailing list