Skip to content
  • Categories
  • Recent
  • Tags
  • Popular
  • Users
  • Groups
  • Search
  • Get Qt Extensions
  • Unsolved
Collapse
Brand Logo
  1. Home
  2. Qt Development
  3. Qt for Python
  4. segfault from QApplication::setFont() after discarding a temporary widget
Forum Updated to NodeBB v4.3 + New Features

segfault from QApplication::setFont() after discarding a temporary widget

Scheduled Pinned Locked Moved Unsolved Qt for Python
2 Posts 1 Posters 390 Views 1 Watching
  • Oldest to Newest
  • Newest to Oldest
  • Most Votes
Reply
  • Reply as topic
Log in to reply
This topic has been deleted. Only users with topic management privileges can see it.
  • H Offline
    H Offline
    HForest
    wrote on last edited by HForest
    #1

    I have a QTreeView that displays thousands of items, each with variable height due to word-wrapped text. To accomplish this efficiently (i.e. avoid putting thousands of widgets in the event loop) the delegate creates temporary widgets to calculate item size in sizeHint() and draw in paint(). This works nicely.

    For performance when responding to rapid-fire events like mouse movement and window resizing, the most recently used temporary widgets are saved in an LRU cache. Old widgets are evicted from the cache and discarded when new ones are needed.

    The problem:

    When a user changes their system font, Qt crashes the process with a segmentation fault. The same thing happens if the app changes its own font with QApplication.setFont(). The crash is intermittent, but happens frequently on my KDE Plasma system.

    If I disable the cache, so temporary widgets are discarded immediately instead of when new ones are created, the segfault goes away. (Of course, that also hurts event processing performance.) QListView seems immune to the crash, but I need QTreeView since the items are hierarchical.

    The top of the stack when the segfault happens:

    #0  0x00007fc8e92adf44 in QCoreApplication::notifyInternal2(QObject*, QEvent*)
        () from /lib/x86_64-linux-gnu/libQt5Core.so.5
    #1  0x00007fc8e3d65a2e in QApplication::setFont(QFont const&, char const*) ()
       from /lib/x86_64-linux-gnu/libQt5Widgets.so.5
    

    I don't have debug symbols, but gdb shows a 0 in the RDI register, which (if I understand amd64 calling conventions correctly) suggests that Qt called notifyInternal2() with a null pointer as its QObject * argument. If so, that seems like a bug in Qt.

    I wouldn't mind some more experienced eyes on this before I file a report. Perhaps I'm missing something. Anyone know what's going on here?

    I have reproduced the crash with Qt 5 in KDE Plasma, Xfce, and Windows 10, using this program:

    #!/usr/bin/env python3
    
    """
    Reproduce a segfault by changing font size.
    Usage: Press (or hold) the Space bar until it crashes.
    
    The Qt -style command line option might influence the likelihood of a crash.
    On Linux, it seems more common with -style Fusion/Breeze/Windows.
    On Win10, it seems more common with -style fusion/windowsvista.
    """
    
    import functools
    import sys
    
    from PySide2.QtCore import QAbstractListModel
    from PySide2.QtCore import QModelIndex
    from PySide2.QtCore import QPoint
    from PySide2.QtCore import Qt
    from PySide2.QtCore import Slot
    from PySide2.QtGui import QFont
    from PySide2.QtWidgets import QApplication
    from PySide2.QtWidgets import QLabel
    from PySide2.QtWidgets import QStyledItemDelegate
    from PySide2.QtWidgets import QTreeView
    from PySide2.QtWidgets import QVBoxLayout
    from PySide2.QtWidgets import QWidget
    
    
    WIDGET_CACHE_SIZE = 2
    ITEM_COUNT = WIDGET_CACHE_SIZE + 1  #xxx ensure a cache eviction
    
    
    class SimpleListModel(QAbstractListModel):
        "A model for demonstrating variable-size items"
    
        def rowCount(self, _parentidx):
            return ITEM_COUNT
    
        def data(self, _index, role=Qt.DisplayRole):
            if role == Qt.DisplayRole:
                return "An item with wrapping text. Press Space."
    
    
    class VariableSizeItemDelegate(QStyledItemDelegate):
        "Draw variable-size items with help from a widget"
    
        @functools.lru_cache(maxsize=WIDGET_CACHE_SIZE)
        def get_widget(self, index):
            """Get a widget for drawing a model item.
            Cache these, for performance during window resize & mouse operations.
            """
            widget = QLabel(index.data())
            widget.setWordWrap(True)
            return widget
    
        @staticmethod
        def update_widget_size(widget, option):
            "Size a widget according to a QStyleOptionViewItem"
            view = option.styleObject
            width = view.columnWidth(0) - view.indentation()
            widget.setFixedWidth(width)
            widget.adjustSize()
    
        @Slot()
        def invalidate_sizes(self):
            "Tell the view that new sizeHint() calls are needed"
            self.sizeHintChanged.emit(QModelIndex())
    
        def sizeHint(self, option, index):
    
            widget = self.get_widget(index)  #xxx cache eviction sets up a segfault
            self.update_widget_size(widget, option)
            return widget.size()
    
        def paint(self, painter, option, index):
    
            widget = self.get_widget(index)
            self.update_widget_size(widget, option)
    
            painter.save()
            painter.translate(option.rect.topLeft())
            widget.render(painter, QPoint())
            painter.restore()
    
    
    class SimpleTreeView(QTreeView):
        "Simple tree view to help reproduce a segfault"
    
        def __init__(self):
            super().__init__()
    
            delegate = VariableSizeItemDelegate()
            self.setItemDelegate(delegate)
            self.header().sectionResized.connect(delegate.invalidate_sizes)
    
            self.model = SimpleListModel()
            self.setModel(self.model)
    
            self.setWindowTitle("Press Space to Crash")
    
        def keyPressEvent(self, event):
            if event.key() == Qt.Key_Space:
                font = QFont(QApplication.font())
                size = font.pointSize() + 1 if font.pointSize() < 20 else 6
                font.setPointSize(size)
                print('\r setFont() call...', end='')
                QApplication.setFont(font)  #xxx trigger segfault
                print('\r setFont() return.', end='')
    
            return super().keyPressEvent(event)
    
    
    if __name__ == "__main__":
        app = QApplication(sys.argv)
        window = SimpleTreeView()
        window.show()
        sys.exit(app.exec_())
    
    
    1 Reply Last reply
    0
    • H Offline
      H Offline
      HForest
      wrote on last edited by
      #2

      Reported here:
      https://bugreports.qt.io/browse/PYSIDE-2022

      1 Reply Last reply
      0

      • Login

      • Login or register to search.
      • First post
        Last post
      0
      • Categories
      • Recent
      • Tags
      • Popular
      • Users
      • Groups
      • Search
      • Get Qt Extensions
      • Unsolved