11

I know this has been asked many times before. I read all of those threads, and my case seems different. Everybody else who has this trouble has a few straightforward causes that I think I’ve ruled out, such as:

  • Starting a timer with no event loop running
  • Starting/stopping a timer from a thread other than the one that created the timer
  • Failing to set the parent property of a widget, leading to problems with the order of destruction

Below I have a minimal code sample that demonstrates the problem. Notice that I’ve started no threads or timers. I also have set the parent of every widget. If I remove the graph widgets, the problem goes away, so one is tempted to blame pyQtGraph, however, if I include the plot widgets but exclude all the blank tabs (i.e. every tab except tabCatchaTiger), the problem also goes away, and that seems to vindicate pyQtGraph.

Versions:

  • Windows 7
  • Python 2.7.8
  • Wing IDE 5.0.9-1
  • PyQt 4.11.1
  • PyQwt 5.2.1
  • PyQtGraph 0.9.8

Test case:

from PyQt4 import Qt, QtGui, QtCore
import PyQt4.Qwt5 as Qwt
import pyqtgraph as pg

pg.functions.USE_WEAVE = False # Lets pyqtgraph plot without gcc

pg.setConfigOption('background', 'w')
pg.setConfigOption('foreground', 'k')

# GUI for visualizing data from database
class crashyGUI(QtGui.QWidget) :

    def __init__(self) :
        # Make the window
        QtGui.QWidget.__init__(self)
        self.resize(700, QtGui.QDesktopWidget().screenGeometry(self).height()*.85)
        self.setWindowTitle('Data Visualization')

        # Create tab interface
        tabWidget = QtGui.QTabWidget(self)

        # define the tab objects
        self.tabEeny = QtGui.QWidget(tabWidget)
        self.tabMeeny = QtGui.QWidget(tabWidget)
        self.tabMiney = QtGui.QWidget(tabWidget)
        self.tabMoe = QtGui.QWidget(tabWidget)
        self.tabCatchaTiger = QtGui.QWidget(tabWidget)
        self.tabByThe = QtGui.QWidget(tabWidget)
        self.tabToe = QtGui.QWidget(tabWidget)

        # Initialize the tab objects
        self.initTabCatchaTiger()

        ###########################################
        ############### Main Layout ###############
        ###########################################

        tabWidget.addTab(self.tabEeny, 'Eeny')
        tabWidget.addTab(self.tabMeeny, 'Meeny')
        tabWidget.addTab(self.tabMiney, 'Miney')
        tabWidget.addTab(self.tabMoe, 'Moe')
        tabWidget.addTab(self.tabCatchaTiger, 'Catch a Tiger')
        tabWidget.addTab(self.tabByThe, 'By The')
        tabWidget.addTab(self.tabToe, 'Toe')

        self.mainLayout = QtGui.QVBoxLayout(self)
        self.mainLayout.addWidget(tabWidget)

        self.setLayout(self.mainLayout)

    def initTabCatchaTiger(self):
        ###########################################
        ############# ADC Capture Tab #############
        ###########################################
        # define tab layout
        grid = QtGui.QGridLayout(self.tabCatchaTiger)

        # create copy of adc plot and add to row 3 of the grid
        self.catchaTigerPlot1 = pg.PlotWidget(name = 'Catch a Tiger 1', parent = self.tabCatchaTiger)
        self.catchaTigerPlot1.setTitle('Catch a Tiger 1')
        grid.addWidget(self.catchaTigerPlot1, 2, 0, 1, 8)

        self.catchaTigerPlot2 = pg.PlotWidget(name = 'Catch a Tiger 2', parent = self.tabCatchaTiger)
        self.catchaTigerPlot2.setTitle('Catch a Tiger 2')
        grid.addWidget(self.catchaTigerPlot2, 3, 0, 1, 8)

        # set layout for tab
        self.tabCatchaTiger.setLayout(grid)

    def closeEvent(self, event) :
            pass

def main() :
    # open a QApplication and dialog() GUI
    app = QtGui.QApplication([])

    windowCrashy = crashyGUI()
    windowCrashy.show()
    app.exec_()

main()
ekhumoro
  • 115,249
  • 20
  • 229
  • 336
Johnny J
  • 113
  • 1
  • 1
  • 4
  • I don't quite understand your conclusion. The issue only happens if the pyqtgraph tab is present, but not shown. So it looks like there is a glitch in the initialization of the plot widget (I won't say bug, because it's only spewing out warning messages). – ekhumoro Nov 25 '14 at 21:27

5 Answers5

17

There seem to be two closely-related issues in the example.

The first one causes Qt to print the QObject::startTimer: QTimer can only be used with threads started with QThread messages on exit.

The second one (which may not affect all users) causes Qt to print QPixmap: Must construct a QApplication before a QPaintDevice, and then dump core on exit.

Both of these issues are caused by python deleting objects in an unpredicable order when it exits.

In the example, the second issue can be fixed by adding the following line to the __init__ of the top-level window:

    self.setAttribute(QtCore.Qt.WA_DeleteOnClose)

Unless QApplication.setQuitOnLastWindowClosed has been changed to False, this will ensure that the application quits at the right time, and that Qt has a chance to automatically delete all the children of the top-level window before the python garbage-collector gets to work.

However, for this to be completely successful, all the relevant objects must be linked together in a parent-child hierarchy. The example code does this where it can, but there seem to be some critical places in the initialization of the PlotWidget class where it is not done.

In particular, there is nothing to ensure that the central item of the PlotWidget has a parent set when it is created. If the relevant part of the code is changed to this:

class PlotWidget(GraphicsView):
    ...
    def __init__(self, parent=None, background='default', **kargs):
        GraphicsView.__init__(self, parent, background=background)
        ...
        self.plotItem = PlotItem(**kargs)
        # make sure the item gets a parent
        self.plotItem.setParent(self)
        self.setCentralItem(self.plotItem)

then the first issue with the QTimer messages also goes away.

ekhumoro
  • 115,249
  • 20
  • 229
  • 336
  • 3
    Using `WA_DeleteOnClose` has worked in my case. Thanks :) – Green Cell Nov 26 '15 at 04:01
  • I had built one pyqt5 gui application tool where this error doesn't happen. Then in a similar project which I implement exactly what I did in the previous project , this error happens. It's so weird – Ice Bear Dec 22 '21 at 14:04
7

Here's a better answer:

You are allowing the QApplication to be collected before python exits. This causes two different issues:

  1. The QTimer error messages are caused by pyqtgraph trying to track its ViewBoxes after the QApplication has been destroyed.

  2. The crash appears to be intrinsic to Qt / PyQt. The following crashes in the same way:

    from PyQt4 import Qt, QtGui, QtCore
    
    def main() :
        app = QtGui.QApplication([])
        x = QtGui.QGraphicsView()
        s = QtGui.QGraphicsScene()
        x.setScene(s)
        x.show()
        app.exec_()
    
    main()
    

You can fix it by adding global app to your main function, or by creating the QApplication at the module level.

Luke
  • 11,374
  • 2
  • 48
  • 61
  • 1
    In this specific case, it's due to a bug in your example. The scene should have the view set as its parent. – ekhumoro Nov 26 '14 at 18:47
  • 2
    I don't think the `global app` fix is likely to be reliable, either. When I try it with the OP's original example, it still dumps core on exit. A solution that works for me is to add `self.setAttribute(QtCore.Qt.WA_DeleteOnClose)` in the `__init__` of the main window, although I don't know if that is more reliable in general. It should be pointed out, though, that these "fixes" are only needed when the plot widgets are created - otherwise the original example works fine for me. – ekhumoro Nov 26 '14 at 18:53
  • Why would we expect a crash if the scene has no parent? (PySide does not crash, for example) – Luke Nov 26 '14 at 19:14
  • 1
    Perhaps I should have said "potential oversight" rather than "bug". AFAIUI, Python does not guarantee that objects will be deleted in a specific order when it exits. On the other hand, when Qt deletes an object, it always tries to delete all its children as well, which will normally ensure that objects get deleted in the right order. This is especially important when Qt takes ownership of an object, because you could end up in a situation where an attempt is made to delete the object twice (which will result in a crash). – ekhumoro Nov 26 '14 at 19:46
  • As for the differences between PyQt and PySide - I don't really know enough about the implementations of either to comment on that. As per my previous comment, I've almost always found that issues like this can be handled with "good housekeeping". I suppose this may mean I end up working around real bugs in PySide/PyQt sometimes - maybe the current example is one of them! But whatever the case, this is certainly a difficult area to deal with, especially if (like me) you don't have much experience of C++. – ekhumoro Nov 26 '14 at 20:19
1

Try to write this in block of __init__:

self.setAttribute(Qt.WA_DeleteOnClose)
eyllanesc
  • 235,170
  • 19
  • 170
  • 241
Avo Asatryan
  • 404
  • 8
  • 21
0

Personally, I don't put any effort into chasing exit crashes anymore--just use pg.exit() and be done with it.

(but if you do happen to find a bug in pyqtgraph, don't hesitate to open an issue on github)

Luke
  • 11,374
  • 2
  • 48
  • 61
  • 1
    That is a really lame API. If those alleged bugs in PySide/PyQt are real, why aren't they being reported? – ekhumoro Nov 25 '14 at 21:33
  • Some of them have been reported. Others were simply dropped because they require too much effort to track down (for example, distilling a 100k-line application down to the minimal lines necessary to reproduce an exit crash is not remotely worth the effort). – Luke Nov 25 '14 at 21:43
  • Okay, but then what is the justification for blaming PySide/PyQt in the documentation of a public API? Surely the onus is on the authors of pyqtgraph to rule out bugs in their own code before they start blaming others? – ekhumoro Nov 25 '14 at 22:05
  • _Some_ of the bugs were traced back to PyQt/PySide and reported. _Some_ of the bugs were traced back to pyqtgraph and fixed. _Most_ of the bugs were never traced at all (so you're right that pyqtgraph should probably be included in that list). – Luke Nov 26 '14 at 01:52
  • Yes, calling pg.exit() appears to solve the problem. So perhaps the problem IS in pyqtgraph, but if the exit function exists, it seems like pyqtgraph deliberately puts the onus on the application to tell it when to shut down, so is this really a bug? – Johnny J Nov 26 '14 at 16:16
  • It is a bug, we just don't know what causes it or how to fix it. It might be in Qt, PyQt, pyqtgraph, or in your script. However we can work around it by calling `pg.exit()` and denying Python the opportunity to tear down all those Qt objects that lead to a crash (the process is about to close anyway, so there is no need to tear down). – Luke Nov 26 '14 at 17:30
0

I had this happen as well and in my case it was caused by a call to deleteLater() on the aboutToQuit-Signal of the application, like so:

def closeEvent(self, *args, **kwargs):
   self.deleteLater()

if __name__ == "__main__":

   application = QtWidgets.QApplication(sys.argv)

   window = testApplication()
   # Handle application exit
   application.aboutToQuit.connect(window.closeEvent)
   # System exit
   sys.exit(application.exec_())

Getting rid of the deleteLater on the whole window seemed to solve it.

Selim Yildiz
  • 5,254
  • 6
  • 18
  • 28
Lgum
  • 31
  • 6