diff --git a/FlatCAMApp.py b/FlatCAMApp.py index 538bd141..59335235 100644 --- a/FlatCAMApp.py +++ b/FlatCAMApp.py @@ -11,6 +11,7 @@ import Tkinter from PyQt4 import QtCore import time # Just used for debugging. Double check before removing. from xml.dom.minidom import parseString as parse_xml_string +from contextlib import contextmanager ######################################## ## Imports part of FlatCAM ## @@ -106,6 +107,10 @@ class App(QtCore.QObject): message = QtCore.pyqtSignal(str, str, str) + # Emitted when an unhandled exception happens + # in the worker task. + thread_exception = QtCore.pyqtSignal(object) + def __init__(self, user_defaults=True, post_gui=None): """ Starts the application. @@ -2138,13 +2143,22 @@ class App(QtCore.QObject): return a, kwa - from contextlib import contextmanager @contextmanager def wait_signal(signal, timeout=10000): - """Block loop until signal emitted, or timeout (ms) elapses.""" + """ + Block loop until signal emitted, timeout (ms) elapses + or unhandled exception happens in a thread. + + :param signal: Signal to wait for. + """ loop = QtCore.QEventLoop() + + # Normal termination signal.connect(loop.quit) + # Termination by exception in thread + self.thread_exception.connect(loop.quit) + status = {'timed_out': False} def report_quit(): @@ -2153,17 +2167,23 @@ class App(QtCore.QObject): yield + # Temporarily change how exceptions are managed. oeh = sys.excepthook ex = [] - def exceptHook(type_, value, traceback): - ex.append(value) - oeh(type_, value, traceback) - sys.excepthook = exceptHook + def except_hook(type_, value, traceback_): + ex.append(value) + oeh(type_, value, traceback_) + sys.excepthook = except_hook + + # Terminate on timeout if timeout is not None: QtCore.QTimer.singleShot(timeout, report_quit) + #### Block #### loop.exec_() + + # Restore exception management sys.excepthook = oeh if ex: self.raiseTclError(str(ex[0])) diff --git a/FlatCAMWorker.py b/FlatCAMWorker.py index 528171b6..e97d9d11 100644 --- a/FlatCAMWorker.py +++ b/FlatCAMWorker.py @@ -14,24 +14,27 @@ class Worker(QtCore.QObject): self.name = name def run(self): - # FlatCAMApp.App.log.debug("Worker Started!") + self.app.log.debug("Worker Started!") # Tasks are queued in the event listener. self.app.worker_task.connect(self.do_worker_task) def do_worker_task(self, task): - # FlatCAMApp.App.log.debug("Running task: %s" % str(task)) + self.app.log.debug("Running task: %s" % str(task)) # 'worker_name' property of task allows to target # specific worker. - if 'worker_name' in task and task['worker_name'] == self.name: - task['fcn'](*task['params']) - return + if ('worker_name' in task and task['worker_name'] == self.name) or \ + ('worker_name' not in task and self.name is None): + + try: + task['fcn'](*task['params']) + except Exception as e: + self.app.thread_exception.emit(e) + raise e - if 'worker_name' not in task and self.name is None: - task['fcn'](*task['params']) return # FlatCAMApp.App.log.debug("Task ignored.")