Details
-
Bug
-
Resolution: Cannot Reproduce
-
P1: Critical
-
None
-
5.11.1
-
None
-
macOS High Sierra, v10.13.5m, Qt 5.11.1, Qt Creator 4.6.2
-
-
Bug Fixing Week Q2/2020
Description
Summary
When doing multiple model updates in a loop, Qt crashes if that model is attached to a ListView
Detail
I have a list of (price, qty) items sorted by descending price. Each price point is unique and represents a row in the model. Every second I get an update from the web of how the model changed (some rows should be removed, some added, and some have their qty changed). This is for a stock exchange, so most of the action in that second happened around the first price point (which represents the current best bid price for a stock).
I call insertRow(), removeRow or setData on items in a QStandardItemModel inside an update loop when I get new data. This causes the attached ListView to update accordingly with transition animations (these are important to visualize what is going on, otherwise it is just a wall of numbers changing - which is why I do individual updates instead of doing a model reset)
This is my update function (with some custom classes, but it gives the idea):
// Update function auto updateLevels = [&]( const QList< DepthLevel > & levels, QStandardItemModel & model, QList< qint64 > & sorted ) { for ( auto & l : levels ) { // Remove 0 qty entries if ( l.m_Qty == 0 ) { int row = sorted.indexOf( Utils::toFixed8( l.m_Price ) ); if ( row != -1 ) { model.removeRow( row ); sorted.removeAt( row ); } } else { // If we already have a price level entry, update its quantity and total int row = sorted.indexOf( Utils::toFixed8( l.m_Price ) ); if ( row != -1 ) { // Update changed data model.item( row )->setData( l.m_Qty, EQuantity ); model.item( row )->setData( l.m_Qty * l.m_Price, ETotal ); } // Otherwise add a new level to the model else { auto item = new QStandardItem(); item->setData( l.m_Qty, EQuantity ); item->setData( l.m_Price, EPrice ); item->setData( l.m_Qty * l.m_Price, ETotal ); // First add it and sort to get the correct index to insert in the model qint64 key = Utils::toFixed8( l.m_Price ); sorted.append( key ); bool sortAscending = &model == m_Asks; std::sort( sorted.begin(), sorted.end(), [ sortAscending ]( qint64 a, qint64 b) { return sortAscending? a < b : a > b; } ); int row = sorted.indexOf( key ); model.insertRow( row, item ); } } } };
This runs fine and the ListView updates correctly. However, intermittently the app will crash usually inside endInsertRow(). All updates are on the main thread and this crash happens within a few minutes and is always at row 0. If I comment out the ListView in the UI, the model updates fine and runs for hours without any issues. Also, if I wrap the loop in a beginResetModel() & endResetModel() and block signals while in the loop, it runs fine for hours. In that case I can have the ListView, but of course, I don't get the transition animations, which is the whole point of doing the updates on individual rows instead of in a batch. (The animations are not to blame, it still crashes without any animations). Here is a stack trace, although I don't have debug symbols to investigate Qt code.
Thread 0 Crashed:: CrBrowserMain Dispatch queue: com.apple.main-thread 0 org.qt-project.QtCore 0x000000010d582c3f QMetaObject::activate(QObject*, int, int, void**) + 47 1 org.qt-project.QtQml 0x000000010d110578 QQmlDelegateModel::_q_itemsInserted(int, int) + 232 2 org.qt-project.QtQml 0x000000010d11fe3c QQmlDelegateModel::qt_metacall(QMetaObject::Call, int, void**) + 140 3 org.qt-project.QtCore 0x000000010d5834c6 QMetaObject::activate(QObject*, int, int, void**) + 2230 4 org.qt-project.QtCore 0x000000010d50d012 QAbstractItemModel::endInsertRows() + 306
There is nothing special about the ListView:
ListView { id: bids anchors.bottom: parent.bottom anchors.top: parent.verticalCenter anchors.left: parent.left anchors.right: parent.right clip: true interactive: false model: marketDepth.bidsModel() // ModelLevel is just a Rectangle with 3 Text items showing qty, price, total from the model delegate: ModelLevel { priceColor: "yellowgreen" color: G.colorWithAlpha( "green", 0.15 ) } add: Transition { NumberAnimation { property: "scale"; from: 0; to: 1; duration: depthView.animTime; onRunningChanged: complete() } } remove: Transition { NumberAnimation { property: "scale"; to: 0; duration: depthView.animTime / 2; onRunningChanged: complete() } } displaced: Transition { NumberAnimation { properties: "x,y"; duration: depthView.animTime; onRunningChanged: complete() } } }
Initially, I thought it was a model issue. I had a custom QAbstractItemModel which I changed to a QStandardItemModel to rule out model bugs. I also tried just appending new rows and have a proxy model to do the sorting, but it still crashed. The only way to prevent the crash is to comment out the UI showing model changes, or not send signals to the UI during updates.