Procházet zdrojové kódy

tweak signal handling

Kamil Sopko před 9 roky
rodič
revize
2082446ab0
4 změnil soubory, kde provedl 69 přidání a 22 odebrání
  1. 4 0
      FlatCAMApp.py
  2. 17 5
      FlatCAMWorker.py
  3. 31 8
      tclCommands/TclCommand.py
  4. 17 9
      tests/test_tcl_shell.py

+ 4 - 0
FlatCAMApp.py

@@ -108,6 +108,10 @@ class App(QtCore.QObject):
     # Emmited when shell command is finished(one command only)
     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)
 
     def __init__(self, user_defaults=True, post_gui=None):

+ 17 - 5
FlatCAMWorker.py

@@ -48,12 +48,24 @@ class Worker(QtCore.QObject):
 
         # '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:
+        #    task['fcn'](*task['params'])
+        #    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
 
         # FlatCAMApp.App.log.debug("Task ignored.")

+ 31 - 8
tclCommands/TclCommand.py

@@ -258,15 +258,17 @@ class TclCommandSignaled(TclCommand):
         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 = 300000
+    # default  timeout for operation is  10 sec, but it can be much more
+    default_timeout = 10000
 
     output = None
 
     def execute_call(self, args, unnamed_args):
 
-        self.output = self.execute(args, unnamed_args)
-        self.app.shell_command_finished.emit(self)
+        try:
+            self.output = self.execute(args, unnamed_args)
+        finally:
+            self.app.shell_command_finished.emit(self)
 
     def execute_wrapper(self, *args):
         """
@@ -279,11 +281,16 @@ class TclCommandSignaled(TclCommand):
         """
 
         @contextmanager
-        def wait_signal(signal, timeout=300000):
+        def wait_signal(signal, timeout=10000):
             """Block loop until signal emitted, or timeout (ms) elapses."""
             loop = QtCore.QEventLoop()
+
+            # Normal termination
             signal.connect(loop.quit)
 
+            # Termination by exception in thread
+            self.app.thread_exception.connect(loop.quit)
+
             status = {'timed_out': False}
 
             def report_quit():
@@ -292,18 +299,23 @@ class TclCommandSignaled(TclCommand):
 
             yield
 
+            # Temporarily change how exceptions are managed.
             oeh = sys.excepthook
             ex = []
-            def exceptHook(type_, value, traceback):
+
+            def except_hook(type_, value, traceback_):
                 ex.append(value)
-                oeh(type_, value, traceback)
-            sys.excepthook = exceptHook
+                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.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
             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):
                 # every TclCommandNewObject ancestor  support  timeout as parameter,
                 # but it does not mean anything for child itself
                 # 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]})
 
+            return self.output
+
         except Exception as unknown:
             self.log.error("TCL command '%s' failed." % str(self))
             self.app.raise_tcl_unknown_error(unknown)

+ 17 - 9
tests/test_tcl_shell.py

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