How to "debug" an application stuck in the main event loop
-
Hi,
I need your help because several users of my software are facing the “UI freeze” same issue, when using Qt 5.15 (only on Mac, no issue reported on Windows).
For a reason I ignore, the software is regularly freezing in the main QEventLoop when they click on a tab of a QTabWidget. Despite many tries, I could not reproduce it myself, even when trying on different Mac machines, that’s why I need your help to understand how I could get more info about what’s going wrong, to better understand the issue.
I’ve asked them to generate a “sample process” of the app while it is stuck. It seems to be a built-in Mac utility that regularly takes kind of snapshots of the callstack and reports how much times it passed in the functions. Except the main() of my application, all the code I see there is pure Qt code and I don’t see function where some code would be blocked. I’m just under the impression that there is some never-ending UI refresh ongoing.
I’ve posted a subset of the stack, in case it rings some bell.
I guess that finding the root cause of the issue just looking at this stack is nearly impossible, but if you could give me some advices about what I could do to identify which part of the UI is causing trouble for example, that would already help a lot. More generally, I would appreciate any advice about how to analyze this kind of bug, when not being able to reproduce myself.
Thanks in advance.
Call graph: 1685 Thread_89766 DispatchQueue_1: com.apple.main-thread (serial) + 1685 start (in dyld) + 1942 [0x201f22366] + 1685 main (in myApp) + 6088 [0x100e57118] + 1685 ??? (in myApp) load address 0x100e4b000 + 0xdefc [0x100e58efc] + 1685 QCoreApplication::exec() (in QtCore) + 130 [0x1107b5042] + 1685 QEventLoop::exec(QFlags<QEventLoop::ProcessEventsFlag>) (in QtCore) + 431 [0x1107b0acf] + 1685 ??? (in libqcocoa.dylib) load address 0x10d77f000 + 0x3962f [0x10d7b862f] + 1685 -[NSApplication run] (in AppKit) + 603 [0x7ff818b96005] + 1685 -[NSApplication(NSEventRouting) _nextEventMatchingEventMask:untilDate:inMode:dequeue:] (in AppKit) + 1273 [0x7ff8194b4fe9] + 1685 _DPSNextEvent (in AppKit) + 880 [0x7ff818ba4be5] + 1685 _BlockUntilNextEventMatchingListInModeWithFilter (in HIToolbox) + 66 [0x7ff8204c6381] + 1685 ReceiveNextEventCommon (in HIToolbox) + 201 [0x7ff8204c6466] + 1685 RunCurrentEventLoopInMode (in HIToolbox) + 292 [0x7ff8204c6829] + 1685 CFRunLoopRunSpecific (in CoreFoundation) + 557 [0x7ff8155db2a9] + 1685 __CFRunLoopRun (in CoreFoundation) + 919 [0x7ff8155dbc08] + 1685 __CFRunLoopDoSources0 (in CoreFoundation) + 215 [0x7ff8155dcf98] + 1685 __CFRunLoopDoSource0 (in CoreFoundation) + 157 [0x7ff8155dd1c9] + 1685 __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__ (in CoreFoundation) + 17 [0x7ff8155dd226] + 1685 ??? (in libqcocoa.dylib) load address 0x10d77f000 + 0x3a9c8 [0x10d7b99c8] + 1685 ??? (in libqcocoa.dylib) load address 0x10d77f000 + 0x3a259 [0x10d7b9259] + 1681 QCoreApplicationPrivate::sendPostedEvents(QObject*, int, QThreadData*) (in QtCore) + 809 [0x1107b5d79] + ! 1681 QCoreApplication::notifyInternal2(QObject*, QEvent*) (in QtCore) + 212 [0x1107b4a34] + ! 1679 QApplication::notify(QObject*, QEvent*) (in QtWidgets) + 497 [0x10dd3de11] + ! : 1678 QApplicationPrivate::notify_helper(QObject*, QEvent*) (in QtWidgets) + 266 [0x10dd3c9ea] + ! : | 1678 QAbstractItemView::event(QEvent*) (in QtWidgets) + 546 [0x10dfa86f2] + ! : | 1678 QFrame::event(QEvent*) (in QtWidgets) + 45 [0x10de1f58d] + ! : | 1678 QWidget::event(QEvent*) (in QtWidgets) + 4698 [0x10dd77e9a] + ! : | 1677 QObject::event(QEvent*) (in QtCore) + 943 [0x1107dfb9f] + ! : | + 1677 ??? (in QtWidgets) load address 0x10dd2c000 + 0xf7544 [0x10de23544] + ! : | + 1070 QAbstractScrollAreaPrivate::layoutChildren_helper(bool*, bool*) (in QtWidgets) + 2381 [0x10de211fd] + ! : | + ! 1070 QWidget::setGeometry(QRect const&) (in QtWidgets) + 264 [0x10dd744d8] + ! : | + ! 1064 QWidgetPrivate::setGeometry_sys(int, int, int, int, bool) (in QtWidgets) + 2230 [0x10dd74226] + ! : | + ! : 1064 QCoreApplication::notifyInternal2(QObject*, QEvent*) (in QtCore) + 212 [0x1107b4a34] + ! : | + ! : 1064 QApplication::notify(QObject*, QEvent*) (in QtWidgets) + 497 [0x10dd3de11] + ! : | + ! : 1064 QApplicationPrivate::notify_helper(QObject*, QEvent*) (in QtWidgets) + 245 [0x10dd3c9d5] + ! : | + ! : 1064 QCoreApplicationPrivate::sendThroughObjectEventFilters(QObject*, QEvent*) (in QtCore) + 155 [0x1107b4ceb] + ! : | + ! : 1063 QAbstractItemView::viewportEvent(QEvent*) (in QtWidgets) + 1417 [0x10dfa8ec9] + ! : | + ! : | 1063 QFrame::event(QEvent*) (in QtWidgets) + 45 [0x10de1f58d] + ! : | + ! : | 1063 QWidget::event(QEvent*) (in QtWidgets) + 1135 [0x10dd770af] + ! : | + ! : | 582 QTableView::updateGeometries() (in QtWidgets) + 164 [0x10e000734] + ! : | + ! : | + 582 QHeaderView::sizeHint() const (in QtWidgets) + 159 [0x10dfb7f6f] + ! : | + ! : | + 542 QHeaderView::sectionSizeFromContents(int) const (in QtWidgets) + 912 [0x10dfc5a20] + ! : | + ! : | + ! 534 ??? (in QtWidgets) load address 0x10dd2c000 + 0xbff53 [0x10ddebf53] + ! : | + ! : | + ! : 534 ??? (in libqmacstyle.dylib) load address 0x10ef5b000 + 0x1d619 [0x10ef78619] + ! : | + ! : | + ! : 533 QCommonStyle::sizeFromContents(QStyle::ContentsType, QStyleOption const*, QSize const&, QWidget const*) const (in QtWidgets) + 980 [0x10ddc9054] + ! : | + ! : | + ! : | 532 QFontMetrics::size(int, QString const&, int, int*) const (in QtGui) + 147 [0x10f1d52e3] + ! : | + ! : | + ! : | + 532 ??? (in QtGui) load address 0x10f0d5000 + 0x2bf768 [0x10f394768] + ! : | + ! : | + ! : | + 493 ??? (in QtGui) load address 0x10f0d5000 + 0x2bc7f5 [0x10f3917f5] + ! : | + ! : | + ! : | + ! 456 QTextLine::layout_helper(int) (in QtGui) + 805 [0x10f2020f5] + ! : | + ! : | + ! : | + ! : 423 QTextEngine::shapeText(int) const (in QtGui) + 3283 [0x10f1ea113] + ! : | + ! : | + ! : | + ! : | 386 QTextEngine::shapeTextWithHarfbuzzNG(QScriptItem const&, unsigned short const*, int, QFontEngine*, QVector<unsigned int> const&, bool, bool) const (in QtGui) + 1146 [0x10f1eb9da] + ! : | + ! : | + ! : | + ! : | + 385 ??? (in QtGui) load address 0x10f0d5000 + 0x458ad9 [0x10f52dad9] (and many other lines)
-
@Ekia
First of all, when they get this “UI freeze” do you mean they just get a delay and then the program continues OK or do you mean it never returns, it hangs and they cannot get out of it?I'm not certain whether we know whether the "snapshots" definitely point to where the problem is happening, but let's take what we can from the trace.
You often won't see much in your own code on the trace because (as in this case) the code is simply in the main Qt event loop from your
main()
program's call toQApplication::exec()
. That gets some event --- perhaps something the use does --- and then calls code. That is waht is happening here.The major clue, if this is to be believed, is the
QHeaderView::sectionSizeFromContents()
. You have aQTableView
, that is needing toupdateGeometries()
and it is trying to determine the width of columns to lay itself out correctly. That in turn is looking at font size to measure the width to make a column. I think this is from the contents of (columns in each row of) the table, rather than just any column header text itself.IIRC, this looks at columns in all rows in the table view, not just the ones which are visible but the ones scrolled out of view too. But I may be mistaken about that, it might only measure the ones visible in the viewport.
First thing is to find which table. Perhaps you are lucky and only have one? What is total size of the data it is showing? If it is "large", such as from a database, are you only reading into memory a "page's worth" of rows, or do you make the table view show the whole model? Try to reduce that.
QHeaderView::sectionSizeFromContents()
isvirtual
. If you derive from that for your table's header view you canoverride
it. You could make it do something simpler, like not look through all the rows. Or at least just put in aqDebug()
(or whatever, some logging) at your override's start, call the base method, and anotherqDebug()
at the end. Then when your app "hangs" ask them for the debug log file. Look at the end of it and see if you do indeed find it showing a "starting sectionSizeFromContents()` with no matching "ending sectionSizeFromContents()" you know that is where it hung. -
@JonB said in How to "debug" an application stuck in the main event loop:
@Ekia
First of all, when they get this “UI freeze” do you mean they just get a delay and then the program continues OK or do you mean it never returns, it hangs and they cannot get out of it?Apparently, it never returns.
First thing is to find which table. Perhaps you are lucky and only have one? What is total size of the data it is showing? If it is "large", such as from a database, are you only reading into memory a "page's worth" of rows, or do you make the table view show the whole model? Try to reduce that.
I'm not lucky and have many tables in the application. In the context of the bug, AFAIK the tables are really small (~10 rows at most, with a very small number of columns). I was thinking about calling
installEventFilter()
on the app and trying to somehow log "relevant" updates to try to detect which table might be causing trouble, but I'm not sure about this strategy.QHeaderView::sectionSizeFromContents()
isvirtual
. If you derive from that for your table's header view you canoverride
it. You could make it do something simpler, like not look through all the rows. Or at least just put in aqDebug()
(or whatever, some logging) at your override's start, call the base method, and anotherqDebug()
at the end. Then when your app "hangs" ask them for the debug log file. Look at the end of it and see if you do indeed find it showing a "starting sectionSizeFromContents()` with no matching "ending sectionSizeFromContents()" you know that is where it hung.Thanks for the suggestion, that will probably help once I'll find a way to detect which table is causing trouble (if it is indeed coming from a table).
@SGaist said in How to "debug" an application stuck in the main event loop:
Hi,
In addition to the excellent suggestions of @JonB, you should also add which version of macOS your users that have this issue are using.
Also, did you saw a pattern in the type of machines they have ?Actually, there is no clear pattern, they all have Apple M1 processors and some of the users are facing the issue and some other no. Regarding the OS, most of them have MacOS 14 (Sonoma). On my side, I've tested on this OS version with both M1 and Intel Macs, without reproducing the issue.
Also the users facing the issue reported that it seems to occur on a specific setup, for some of them only with an external monitor and for some others, only without! They also reported that being plugged to a dock might have an impact.
Many thanks to both of you for your help!
-
Just a quick update to tell you that I've been able to identify the widget that was causing trouble, installing a custom event listener on the QApplication, where I was logging all the events of type "Resize" as I had the impression that it was coming from there.
I just wanted to share how I did that, in case someone else needs to investigate something similar in the future.
Regarding the widget itself, I'm not very familiar with Qt widget's size policy strategy, but my incomplete understanding is that the size policy of a QTableView set to
Minimum
was somehow leading to an infinite loop of resize events (maybe when the resize of this widget was causing the resize of another widget ?). Changing the size policy seems to fix the issue.Thanks for everyone for the help!