tweak signal handling

This commit is contained in:
Kamil Sopko
2016-03-25 00:59:02 +01:00
parent cac2f74be2
commit 2082446ab0
4 changed files with 70 additions and 23 deletions

View File

@@ -108,6 +108,10 @@ class App(QtCore.QObject):
# Emmited when shell command is finished(one command only) # Emmited when shell command is finished(one command only)
shell_command_finished = QtCore.pyqtSignal(object) shell_command_finished = QtCore.pyqtSignal(object)
# Emitted when an unhandled exception happens
# in the worker task.
thread_exception = QtCore.pyqtSignal(object)
message = QtCore.pyqtSignal(str, str, str) message = QtCore.pyqtSignal(str, str, str)
def __init__(self, user_defaults=True, post_gui=None): def __init__(self, user_defaults=True, post_gui=None):

View File

@@ -48,12 +48,24 @@ class Worker(QtCore.QObject):
# 'worker_name' property of task allows to target # 'worker_name' property of task allows to target
# specific worker. # specific worker.
if 'worker_name' in task and task['worker_name'] == self.name: #if 'worker_name' in task and task['worker_name'] == self.name:
task['fcn'](*task['params']) # task['fcn'](*task['params'])
return # return
#if 'worker_name' not in task and self.name is None:
# 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 return
# FlatCAMApp.App.log.debug("Task ignored.") # FlatCAMApp.App.log.debug("Task ignored.")

View File

@@ -258,15 +258,17 @@ class TclCommandSignaled(TclCommand):
it handles all neccessary stuff about blocking and passing exeptions it handles all neccessary stuff about blocking and passing exeptions
""" """
# default timeout for operation is 300 sec, but it can be much more # default timeout for operation is 10 sec, but it can be much more
default_timeout = 300000 default_timeout = 10000
output = None output = None
def execute_call(self, args, unnamed_args): def execute_call(self, args, unnamed_args):
self.output = self.execute(args, unnamed_args) try:
self.app.shell_command_finished.emit(self) self.output = self.execute(args, unnamed_args)
finally:
self.app.shell_command_finished.emit(self)
def execute_wrapper(self, *args): def execute_wrapper(self, *args):
""" """
@@ -279,11 +281,16 @@ class TclCommandSignaled(TclCommand):
""" """
@contextmanager @contextmanager
def wait_signal(signal, timeout=300000): def wait_signal(signal, timeout=10000):
"""Block loop until signal emitted, or timeout (ms) elapses.""" """Block loop until signal emitted, or timeout (ms) elapses."""
loop = QtCore.QEventLoop() loop = QtCore.QEventLoop()
# Normal termination
signal.connect(loop.quit) signal.connect(loop.quit)
# Termination by exception in thread
self.app.thread_exception.connect(loop.quit)
status = {'timed_out': False} status = {'timed_out': False}
def report_quit(): def report_quit():
@@ -292,18 +299,23 @@ class TclCommandSignaled(TclCommand):
yield yield
# Temporarily change how exceptions are managed.
oeh = sys.excepthook oeh = sys.excepthook
ex = [] 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: if timeout is not None:
QtCore.QTimer.singleShot(timeout, report_quit) QtCore.QTimer.singleShot(timeout, report_quit)
# Block
loop.exec_() loop.exec_()
# Restore exception management
sys.excepthook = oeh sys.excepthook = oeh
if ex: if ex:
self.raise_tcl_error(str(ex[0])) self.raise_tcl_error(str(ex[0]))
@@ -324,12 +336,23 @@ class TclCommandSignaled(TclCommand):
# set detail for processing, it will be there until next open or close # set detail for processing, it will be there until next open or close
self.app.shell.open_proccessing(self.get_current_command()) self.app.shell.open_proccessing(self.get_current_command())
self.output = None
def handle_finished(obj):
self.app.shell_command_finished.disconnect(handle_finished)
# TODO: handle output
pass
self.app.shell_command_finished.connect(handle_finished)
with wait_signal(self.app.shell_command_finished, passed_timeout): with wait_signal(self.app.shell_command_finished, passed_timeout):
# every TclCommandNewObject ancestor support timeout as parameter, # every TclCommandNewObject ancestor support timeout as parameter,
# but it does not mean anything for child itself # but it does not mean anything for child itself
# when operation will be really long is good to set it higher then defqault 30s # when operation will be really long is good to set it higher then defqault 30s
self.app.worker_task.emit({'fcn': self.execute_call, 'params': [args, unnamed_args]}) self.app.worker_task.emit({'fcn': self.execute_call, 'params': [args, unnamed_args]})
return self.output
except Exception as unknown: except Exception as unknown:
self.log.error("TCL command '%s' failed." % str(self)) self.log.error("TCL command '%s' failed." % str(self))
self.app.raise_tcl_unknown_error(unknown) self.app.raise_tcl_unknown_error(unknown)

View File

@@ -1,6 +1,8 @@
import sys import sys
import unittest import unittest
from PyQt4 import QtGui from PyQt4 import QtGui
from PyQt4.QtCore import QThread
from FlatCAMApp import App from FlatCAMApp import App
from FlatCAMObj import FlatCAMGerber, FlatCAMGeometry, FlatCAMCNCjob, FlatCAMExcellon from FlatCAMObj import FlatCAMGerber, FlatCAMGeometry, FlatCAMCNCjob, FlatCAMExcellon
from ObjectUI import GerberObjectUI, GeometryObjectUI from ObjectUI import GerberObjectUI, GeometryObjectUI
@@ -10,6 +12,8 @@ import tempfile
class TclShellTest(unittest.TestCase): class TclShellTest(unittest.TestCase):
setup = False
gerber_files = 'tests/gerber_files' gerber_files = 'tests/gerber_files'
copper_bottom_filename = 'detector_copper_bottom.gbr' copper_bottom_filename = 'detector_copper_bottom.gbr'
copper_top_filename = 'detector_copper_top.gbr' copper_top_filename = 'detector_copper_top.gbr'
@@ -24,20 +28,23 @@ class TclShellTest(unittest.TestCase):
drill_diameter = 0.8 drill_diameter = 0.8
def setUp(self): def setUp(self):
self.app = QtGui.QApplication(sys.argv)
# Create App, keep app defaults (do not load if not self.setup:
# user-defined defaults). self.setup=True
self.fc = App(user_defaults=False) self.app = QtGui.QApplication(sys.argv)
self.fc.shell.show() # Create App, keep app defaults (do not load
# user-defined defaults).
self.fc = App(user_defaults=False)
self.fc.shell.show()
pass pass
def tearDown(self): def tearDown(self):
self.app.closeAllWindows() #self.fc.tcl=None
#self.app.closeAllWindows()
del self.fc #del self.fc
del self.app #del self.app
pass pass
def test_set_get_units(self): def test_set_get_units(self):
@@ -60,6 +67,7 @@ class TclShellTest(unittest.TestCase):
# open gerber files top, bottom and cutout # open gerber files top, bottom and cutout
self.fc.exec_command_test('set_sys units MM') self.fc.exec_command_test('set_sys units MM')
self.fc.exec_command_test('new') self.fc.exec_command_test('new')