Fixes to make crashing less likely on PySide

Merge branch 'clear_cycles' into develop
This commit is contained in:
Luke Campagnola 2014-03-24 15:52:08 -04:00
commit 6c6beed735
6 changed files with 111 additions and 14 deletions

View File

@ -32,6 +32,23 @@ else:
if USE_PYSIDE:
from PySide import QtGui, QtCore, QtOpenGL, QtSvg
import PySide
try:
from PySide import shiboken
isQObjectAlive = shiboken.isValid
except ImportError:
def isQObjectAlive(obj):
try:
if hasattr(obj, 'parent'):
obj.parent()
elif hasattr(obj, 'parentItem'):
obj.parentItem()
else:
raise Exception("Cannot determine whether Qt object %s is still alive." % obj)
except RuntimeError:
return False
else:
return True
VERSION_INFO = 'PySide ' + PySide.__version__
# Make a loadUiType function like PyQt has
@ -78,6 +95,9 @@ else:
pass
import sip
def isQObjectAlive(obj):
return not sip.isdeleted(obj)
loadUiType = uic.loadUiType
QtCore.Signal = QtCore.pyqtSignal

View File

@ -56,6 +56,7 @@ CONFIG_OPTIONS = {
'weaveDebug': False, ## Print full error message if weave compile fails
'exitCleanup': True, ## Attempt to work around some exit crash bugs in PyQt and PySide
'enableExperimental': False, ## Enable experimental features (the curious can search for this key in the code)
'crashWarning': False, # If True, print warnings about situations that may result in a crash
}
@ -256,6 +257,7 @@ from .graphicsWindows import *
from .SignalProxy import *
from .colormap import *
from .ptime import time
from pyqtgraph.Qt import isQObjectAlive
##############################################################
@ -284,7 +286,12 @@ def cleanup():
s = QtGui.QGraphicsScene()
for o in gc.get_objects():
try:
if isinstance(o, QtGui.QGraphicsItem) and o.scene() is None:
if isinstance(o, QtGui.QGraphicsItem) and isQObjectAlive(o) and o.scene() is None:
if getConfigOption('crashWarning'):
sys.stderr.write('Error: graphics item without scene. '
'Make sure ViewBox.close() and GraphicsView.close() '
'are properly called before app shutdown (%s)\n' % (o,))
s.addItem(o)
except RuntimeError: ## occurs if a python wrapper no longer has its underlying C++ object
continue

View File

@ -18,6 +18,7 @@ This class is very heavily featured:
"""
from ...Qt import QtGui, QtCore, QtSvg, USE_PYSIDE
from ... import pixmaps
import sys
if USE_PYSIDE:
from .plotConfigTemplate_pyside import *
@ -193,14 +194,6 @@ class PlotItem(GraphicsWidget):
self.layout.setColumnStretchFactor(1, 100)
## Wrap a few methods from viewBox
for m in [
'setXRange', 'setYRange', 'setXLink', 'setYLink', 'setAutoPan', 'setAutoVisible',
'setRange', 'autoRange', 'viewRect', 'viewRange', 'setMouseEnabled', 'setLimits',
'enableAutoRange', 'disableAutoRange', 'setAspectLocked', 'invertY',
'register', 'unregister']: ## NOTE: If you update this list, please update the class docstring as well.
setattr(self, m, getattr(self.vb, m))
self.items = []
self.curves = []
self.itemMeta = weakref.WeakKeyDictionary()
@ -298,7 +291,24 @@ class PlotItem(GraphicsWidget):
"""Return the :class:`ViewBox <pyqtgraph.ViewBox>` contained within."""
return self.vb
## Wrap a few methods from viewBox.
#Important: don't use a settattr(m, getattr(self.vb, m)) as we'd be leaving the viebox alive
#because we had a reference to an instance method (creating wrapper methods at runtime instead).
for m in [
'setXRange', 'setYRange', 'setXLink', 'setYLink', 'setAutoPan', 'setAutoVisible',
'setRange', 'autoRange', 'viewRect', 'viewRange', 'setMouseEnabled', 'setLimits',
'enableAutoRange', 'disableAutoRange', 'setAspectLocked', 'invertY',
'register', 'unregister']: ## NOTE: If you update this list, please update the class docstring as well.
def _create_method(name):
def method(self, *args, **kwargs):
return getattr(self.vb, name)(*args, **kwargs)
method.__name__ = name
return method
locals()[m] = _create_method(m)
del _create_method
def setLogMode(self, x=None, y=None):
"""
@ -356,10 +366,8 @@ class PlotItem(GraphicsWidget):
self.ctrlMenu.setParent(None)
self.ctrlMenu = None
#self.ctrlBtn.setParent(None)
#self.ctrlBtn = None
#self.autoBtn.setParent(None)
#self.autoBtn = None
self.autoBtn.setParent(None)
self.autoBtn = None
for k in self.axes:
i = self.axes[k]['item']

View File

@ -5,11 +5,12 @@ from ...Point import Point
from ... import functions as fn
from .. ItemGroup import ItemGroup
from .. GraphicsWidget import GraphicsWidget
from ...GraphicsScene import GraphicsScene
import weakref
from copy import deepcopy
from ... import debug as debug
from ... import getConfigOption
import sys
from pyqtgraph.Qt import isQObjectAlive
__all__ = ['ViewBox']
@ -240,6 +241,7 @@ class ViewBox(GraphicsWidget):
del ViewBox.NamedViews[self.name]
def close(self):
self.clear()
self.unregister()
def implements(self, interface):
@ -1653,6 +1655,9 @@ class ViewBox(GraphicsWidget):
## called when the application is about to exit.
## this disables all callbacks, which might otherwise generate errors if invoked during exit.
for k in ViewBox.AllViews:
if isQObjectAlive(k) and getConfigOption('crashWarning'):
sys.stderr.write('Warning: ViewBox should be closed before application exit.\n')
try:
k.destroyed.disconnect()
except RuntimeError: ## signal is already disconnected.

View File

@ -0,0 +1,47 @@
import gc
import weakref
try:
import faulthandler
faulthandler.enable()
except ImportError:
pass
import pyqtgraph as pg
pg.mkQApp()
def test_getViewWidget():
view = pg.PlotWidget()
vref = weakref.ref(view)
item = pg.InfiniteLine()
view.addItem(item)
assert item.getViewWidget() is view
del view
gc.collect()
assert vref() is None
assert item.getViewWidget() is None
def test_getViewWidget_deleted():
view = pg.PlotWidget()
item = pg.InfiniteLine()
view.addItem(item)
assert item.getViewWidget() is view
# Arrange to have Qt automatically delete the view widget
obj = pg.QtGui.QWidget()
view.setParent(obj)
del obj
gc.collect()
assert not pg.Qt.isQObjectAlive(view)
assert item.getViewWidget() is None
#if __name__ == '__main__':
#view = pg.PlotItem()
#vref = weakref.ref(view)
#item = pg.InfiniteLine()
#view.addItem(item)
#del view
#gc.collect()

View File

@ -0,0 +1,10 @@
import pyqtgraph as pg
import gc
def test_isQObjectAlive():
o1 = pg.QtCore.QObject()
o2 = pg.QtCore.QObject()
o2.setParent(o1)
del o1
gc.collect()
assert not pg.Qt.isQObjectAlive(o2)