aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorJan Arve Sæther <jan-arve.saether@qt.io>2020-05-07 02:55:33 +0200
committerJan Arve Sæther <jan-arve.saether@qt.io>2020-05-26 11:35:37 +0200
commitabaa8cf657111c86f1db72dff65a0c85e158f7b1 (patch)
treebf5248b5a4a1a0e02c732c20e9a5a56f6e127f71 /src
parent5654d77ef667438ea13ed219e39d39080f769d66 (diff)
Improve polish loop detection and diagnostics
The existing warning was pretty much useless since it would only warn after having looped INT_MAX times. In addition, it didn't actually detect if polish() was called from within updatePolish(). Instead, the counting is changed to be strictly more correct: The counter is now only increased when polish() is called within the updatePolish(). It will reset back to 1 if that does not occur. Effectively, the counter will reflect how many consecutive polish loops we have processed. This patch will show diagnostics after having reached 1000 consecutive polish loops. It will only warn for the next 5 items in order to not be too verbose...(most likely they will be the same 5 items). If the counter reaches 100,000, we break out of the loop: This might be important for e.g. CI runs so that the process can actually terminate in order to get some useful diagnostics. Note that the item that calls polish() within updatePolish() doesn't have to be the same item as updatePolish() was called on. We also want to track these since there might be several items working in tandem to create the loop. With this change it will now give the following output: main.qml:10:5: QML Row: possible QQuickItem::polish() loop main.qml:10:5: QML Row: Row called polish() inside updatePolish() of Row (This is when Row called polish() from within its own updatePolish()) Fixes: QTBUG-40220 Task-number: QTBUG-83856 Change-Id: Ib8a7242908082c70d8cf71efbbe1fa148dbfada0 Reviewed-by: Shawn Rutledge <shawn.rutledge@qt.io> (cherry picked from commit 1c8bce285522e0dcfd13fe6c514f4756d6d6438c) Reviewed-by: Jan Arve Sæther <jan-arve.saether@qt.io> Reviewed-by: Volker Hilsheimer <volker.hilsheimer@qt.io>
Diffstat (limited to 'src')
-rw-r--r--src/quick/items/qquickwindow.cpp90
1 files changed, 83 insertions, 7 deletions
diff --git a/src/quick/items/qquickwindow.cpp b/src/quick/items/qquickwindow.cpp
index 2c89ebd380..3c97475e86 100644
--- a/src/quick/items/qquickwindow.cpp
+++ b/src/quick/items/qquickwindow.cpp
@@ -70,6 +70,8 @@
#include <QtCore/QLibraryInfo>
#include <QtCore/QRunnable>
#include <QtQml/qqmlincubator.h>
+#include <QtQml/qqmlinfo.h>
+#include <QtQml/private/qqmlmetatype_p.h>
#include <QtQuick/private/qquickpixmapcache_p.h>
@@ -293,6 +295,79 @@ static bool transformDirtyOnItemOrAncestor(const QQuickItem *item)
}
#endif
+/*!
+ * \internal
+
+ A "polish loop" can occur inside QQuickWindowPrivate::polishItems(). It is when an item calls
+ polish() on an(other?) item from updatePolish(). If this anomaly happens repeatedly and without
+ interruption (of a well-behaved updatePolish() that doesn't call polish()), it is a strong
+ indication that we are heading towards an infinite polish loop. A polish loop is not a bug in
+ Qt Quick - it is a bug caused by ill-behaved items put in the scene.
+
+ We can detect this sequence of polish loops easily, since the
+ QQuickWindowPrivate::itemsToPolish is basically a stack: polish() will push to it, and
+ polishItems() will pop from it.
+ Therefore if updatePolish() calls polish(), the immediate next item polishItems() processes is
+ the item that was polished by the previous call to updatePolish().
+ We therefore just need to count the number of polish loops we detected in _sequence_.
+*/
+struct PolishLoopDetector
+{
+ PolishLoopDetector(const QVector<QQuickItem*> &itemsToPolish)
+ : itemsToPolish(itemsToPolish)
+ {
+ }
+
+ /*
+ * returns true when it detected a likely infinite loop
+ * (suggests it should abort the polish loop)
+ **/
+ bool check(QQuickItem *item, int itemsRemainingBeforeUpdatePolish)
+ {
+ if (itemsToPolish.count() > itemsRemainingBeforeUpdatePolish) {
+ // Detected potential polish loop.
+ ++numPolishLoopsInSequence;
+ if (numPolishLoopsInSequence >= 1000) {
+ // Start to warn about polish loop after 1000 consecutive polish loops
+ if (numPolishLoopsInSequence == 100000) {
+ // We have looped 100,000 times without actually reducing the list of items to
+ // polish, give up for now.
+ // This is not a fix, just a remedy so that the application can be somewhat
+ // responsive.
+ numPolishLoopsInSequence = 0;
+ return true;
+ } else if (numPolishLoopsInSequence < 1005) {
+ // Show the 5 next items involved in the polish loop.
+ // (most likely they will be the same 5 items...)
+ QQuickItem *guiltyItem = itemsToPolish.last();
+ qmlWarning(item) << "possible QQuickItem::polish() loop";
+
+ auto typeAndObjectName = [](QQuickItem *item) {
+ QString typeName = QQmlMetaType::prettyTypeName(item);
+ QString objName = item->objectName();
+ if (!objName.isNull())
+ return QString::fromLatin1("%1(%2)").arg(typeName, objName);
+ return typeName;
+ };
+
+ qmlWarning(guiltyItem) << typeAndObjectName(guiltyItem)
+ << " called polish() inside updatePolish() of " << typeAndObjectName(item);
+
+ if (numPolishLoopsInSequence == 1004)
+ // Enough warnings. Reset counter in order to speed things up and re-detect
+ // more loops
+ numPolishLoopsInSequence = 0;
+ }
+ }
+ } else {
+ numPolishLoopsInSequence = 0;
+ }
+ return false;
+ }
+ const QVector<QQuickItem*> &itemsToPolish; // Just a ref to the one in polishItems()
+ int numPolishLoopsInSequence = 0;
+};
+
void QQuickWindowPrivate::polishItems()
{
// An item can trigger polish on another item, or itself for that matter,
@@ -300,20 +375,21 @@ void QQuickWindowPrivate::polishItems()
// iterate through the set, we must continue pulling items out until it
// is empty.
// In the case where polish is called from updatePolish() either directly
- // or indirectly, we use a recursionSafeguard to print a warning to
- // the user.
- int recursionSafeguard = INT_MAX;
- while (!itemsToPolish.isEmpty() && --recursionSafeguard > 0) {
+ // or indirectly, we use a PolishLoopDetector to determine if a warning should
+ // be printed to the user.
+
+ PolishLoopDetector polishLoopDetector(itemsToPolish);
+ while (!itemsToPolish.isEmpty()) {
QQuickItem *item = itemsToPolish.takeLast();
QQuickItemPrivate *itemPrivate = QQuickItemPrivate::get(item);
itemPrivate->polishScheduled = false;
+ const int itemsRemaining = itemsToPolish.count();
itemPrivate->updatePolish();
item->updatePolish();
+ if (polishLoopDetector.check(item, itemsRemaining) == true)
+ break;
}
- if (recursionSafeguard == 0)
- qWarning("QQuickWindow: possible QQuickItem::polish() loop");
-
#if QT_CONFIG(im)
if (QQuickItem *focusItem = q_func()->activeFocusItem()) {
// If the current focus item, or any of its anchestors, has changed location