Uploaded image for project: 'Qt'
  1. Qt
  2. QTBUG-69856

Model updates in a loop crashes QML ListView

    XMLWordPrintable

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
    • All
    • 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.

      Attachments

        No reviews matched the request. Check your Options in the drop-down menu of this sections header.

        Activity

          People

            laknoll Lars Knoll
            morni morni
            Votes:
            1 Vote for this issue
            Watchers:
            5 Start watching this issue

            Dates

              Created:
              Updated:
              Resolved:

              Gerrit Reviews

                There are no open Gerrit changes