Make console handle exceptions from non-gui threads a little more carefully

This commit is contained in:
Luke Campagnola 2018-07-03 13:43:28 -07:00
parent 3d3d0a2459
commit f3583ed338

View File

@ -5,6 +5,7 @@ from ..Qt import QtCore, QtGui, QT_LIB
from ..python2_3 import basestring
from .. import exceptionHandling as exceptionHandling
from .. import getConfigOption
from ..functions import SignalBlock
if QT_LIB == 'PySide':
from . import template_pyside as template
elif QT_LIB == 'PySide2':
@ -33,6 +34,7 @@ class ConsoleWidget(QtGui.QWidget):
- ability to add extra features like exception stack introspection
- ability to have multiple interactive prompts, including for spawned sub-processes
"""
_threadException = QtCore.Signal(object)
def __init__(self, parent=None, namespace=None, historyFile=None, text=None, editor=None):
"""
@ -89,6 +91,9 @@ class ConsoleWidget(QtGui.QWidget):
self.ui.onlyUncaughtCheck.toggled.connect(self.updateSysTrace)
self.currentTraceback = None
# send exceptions raised in non-gui threads back to the main thread by signal.
self._threadException.connect(self._threadExceptionHandler)
def loadHistory(self):
"""Return the list of previously-invoked command strings (or None)."""
@ -260,9 +265,12 @@ class ConsoleWidget(QtGui.QWidget):
If True, the console will catch all unhandled exceptions and display the stack
trace. Each exception caught clears the last.
"""
self.ui.catchAllExceptionsBtn.setChecked(catch)
with SignalBlock(self.ui.catchAllExceptionsBtn.toggled, self.catchAllExceptions):
self.ui.catchAllExceptionsBtn.setChecked(catch)
if catch:
self.ui.catchNextExceptionBtn.setChecked(False)
with SignalBlock(self.ui.catchNextExceptionBtn.toggled, self.catchNextException):
self.ui.catchNextExceptionBtn.setChecked(False)
self.enableExceptionHandling()
self.ui.exceptionBtn.setChecked(True)
else:
@ -273,9 +281,11 @@ class ConsoleWidget(QtGui.QWidget):
If True, the console will catch the next unhandled exception and display the stack
trace.
"""
self.ui.catchNextExceptionBtn.setChecked(catch)
with SignalBlock(self.ui.catchNextExceptionBtn.toggled, self.catchNextException):
self.ui.catchNextExceptionBtn.setChecked(catch)
if catch:
self.ui.catchAllExceptionsBtn.setChecked(False)
with SignalBlock(self.ui.catchAllExceptionsBtn.toggled, self.catchAllExceptions):
self.ui.catchAllExceptionsBtn.setChecked(False)
self.enableExceptionHandling()
self.ui.exceptionBtn.setChecked(True)
else:
@ -328,7 +338,18 @@ class ConsoleWidget(QtGui.QWidget):
else:
sys.settrace(self.systrace)
def exceptionHandler(self, excType, exc, tb, systrace=False):
def exceptionHandler(self, excType, exc, tb, systrace=False, frame=None):
if frame is None:
frame = sys._getframe()
# exceptions raised in non-gui threads must be handled separately
isGuiThread = QtCore.QThread.currentThread() == QtCore.QCoreApplication.instance().thread()
if not isGuiThread:
# sending a frame from one thread to another.. probably not safe, but better than just
# dropping the exception?
self._threadException.emit((excType, exc, tb, systrace, frame.f_back))
return
if self.ui.catchNextExceptionBtn.isChecked():
self.ui.catchNextExceptionBtn.setChecked(False)
elif not self.ui.catchAllExceptionsBtn.isChecked():
@ -342,10 +363,13 @@ class ConsoleWidget(QtGui.QWidget):
if systrace:
# exceptions caught using systrace don't need the usual
# call stack + traceback handling
self.setStack(sys._getframe().f_back.f_back)
self.setStack(frame.f_back.f_back)
else:
self.setStack(frame=sys._getframe().f_back, tb=tb)
self.setStack(frame=frame.f_back, tb=tb)
def _threadExceptionHandler(self, args):
self.exceptionHandler(*args)
def setStack(self, frame=None, tb=None):
"""Display a call stack and exception traceback.