diff --git a/CHANGELOG.md b/CHANGELOG.md index 4d12c0c4..f6fba375 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,13 @@ CHANGELOG for FlatCAM Evo beta ================================================= +10.09.2022 + +- hided the main UI on application quit to create a user experience of a shutdown without lag +- added a way to terminate QThreads safely by waiting; should be much safer +- made sure that the ArgsThread class receive the signal to stop +- made sure that on application shutdown, all workers will quit before the actual exit + 1.09.2022 - added a new feature for Geometry export-as-SVG, the ability to export only the paths (outlines); the new feature is controlled from a new parameter in Preferences -> Geometry -> Export diff --git a/appEditors/AppGerberEditor.py b/appEditors/AppGerberEditor.py index 08553e9a..60e0eeef 100644 --- a/appEditors/AppGerberEditor.py +++ b/appEditors/AppGerberEditor.py @@ -3994,7 +3994,7 @@ class AppGerberEditor(QtCore.QObject): self.plot_thread = None # a QThread for the edit process - self.thread = QtCore.QThread() + # self.thread = QtCore.QThread() # def entry2option(option, entry): # self.editor_options[option] = float(entry.text()) @@ -5044,7 +5044,11 @@ class AppGerberEditor(QtCore.QObject): pass def clear(self): - self.thread.quit() + # try: + # self.thread.quit() + # self.thread.wait() + # except Exception as erp: + # self.app.log.error("AppGerberEditor.clear() -> %s" % str(erp)) self.active_tool = None self.selected = [] diff --git a/appMain.py b/appMain.py index db9296ef..4c60206f 100644 --- a/appMain.py +++ b/appMain.py @@ -24,7 +24,6 @@ from io import StringIO import gc - from multiprocessing.connection import Listener, Client from multiprocessing import Pool import socket @@ -2701,7 +2700,7 @@ class App(QtCore.QObject): """ Informs the user. Normally on the status bar, optionally also on the shell. - :param msg: Text to write. Composed from a first part between brackets which is the level and the rest + :param msg: Text to write. Composed of a first part between brackets which is the level and the rest which is the message. The level part will control the text color and the used icon :type msg: str :param shell_echo: Control if to display the message msg in the Shell @@ -2710,7 +2709,7 @@ class App(QtCore.QObject): """ # Type of message in brackets at the beginning of the message. - match = re.search(r"^\[(.*?)\](.*)", msg) + match = re.search(r"^\[(.*?)](.*)", msg) if match: level = match.group(1) msg_ = match.group(2) @@ -3768,6 +3767,11 @@ class App(QtCore.QObject): :return: None """ + # hide the UI so the user experiments a faster shutdown + self.ui.hide() + + self.new_launch.stop.emit() # noqa + # close editors before quiting the app, if they are open if self.call_source == 'geo_editor': self.geo_editor.deactivate() @@ -3815,6 +3819,7 @@ class App(QtCore.QObject): # TODO in the future we need to make a difference between settings that need to be persistent all the time self.defaults.update(self.options) self.preferencesUiManager.save_defaults(silent=True) + if silent is False: self.log.debug("App.quit_application() --> App Defaults saved.") @@ -3852,21 +3857,12 @@ class App(QtCore.QObject): if silent is False: self.log.debug("App.quit_application() --> App UI state saved.") - # try to quit the Socket opened by ArgsThread class - # try: - # # self.new_launch.thread_exit = True - # # self.new_launch.listener.close() - # if sys.platform == 'win32' or sys.platform == 'linux': - # self.new_launch.close_listener() - # # self.new_launch.stop.emit() - # except Exception as err: - # self.log.error("App.quit_application() --> %s" % str(err)) - # try to quit the QThread that run ArgsThread class try: # del self.new_launch if sys.platform == 'win32': self.listen_th.quit() + self.listen_th.wait(1000) except Exception as e: if silent is False: self.log.error("App.quit_application() --> %s" % str(e)) @@ -3875,6 +3871,8 @@ class App(QtCore.QObject): # self.workers.__del__() self.clear_pool() + self.workers.quit() + # quit app by signalling for self.kill_app() method # self.close_app_signal.emit() # sys.exit(0) @@ -8906,7 +8904,7 @@ class ArgsThread(QtCore.QObject): self.thread_exit = False self.start.connect(self.run) # noqa - self.stop.connect(self.close_listener) # noqa + self.stop.connect(self.close_listener, type=Qt.ConnectionType.QueuedConnection) # noqa def my_loop(self, address): try: @@ -8941,6 +8939,7 @@ class ArgsThread(QtCore.QObject): def serve(self, conn): while self.thread_exit is False: + QtCore.QCoreApplication.processEvents() msg = conn.recv() if msg == 'close': break diff --git a/appPlugins/ToolPDF.py b/appPlugins/ToolPDF.py index 1d16c3b6..4049ed40 100644 --- a/appPlugins/ToolPDF.py +++ b/appPlugins/ToolPDF.py @@ -378,7 +378,7 @@ class ToolPDF(AppTool): pass self.check_thread.timeout.connect(self.periodic_check_handler) - self.check_thread.start(QtCore.QThread.Priority.HighPriority) + self.check_thread.start(QtCore.QThread.Priority.HighPriority) # noqa def periodic_check_handler(self): """ diff --git a/appPlugins/ToolSub.py b/appPlugins/ToolSub.py index 79c8ef09..b71e795a 100644 --- a/appPlugins/ToolSub.py +++ b/appPlugins/ToolSub.py @@ -704,7 +704,7 @@ class ToolSub(AppTool): pass self.check_thread.timeout.connect(self.periodic_check_handler) - self.check_thread.start(QtCore.QThread.Priority.HighPriority) + self.check_thread.start(QtCore.QThread.Priority.HighPriority) # noqa def periodic_check_handler(self): """ diff --git a/appTranslation.py b/appTranslation.py index b6ec9c09..7024752f 100644 --- a/appTranslation.py +++ b/appTranslation.py @@ -208,6 +208,7 @@ def restart_program(app, ask=None): # try to quit the QThread that run ArgsThread class try: app.listen_th.quit() + app.listen_th.wait(1000) except Exception as err: log.error("FlatCAMTranslation.restart_program() --> %s" % str(err)) diff --git a/appWorkerStack.py b/appWorkerStack.py index cc9d348f..44fc2023 100644 --- a/appWorkerStack.py +++ b/appWorkerStack.py @@ -41,3 +41,8 @@ class WorkerStack(QtCore.QObject): def on_task_completed(self, worker_name): self.load[str(worker_name)] -= 1 + + def quit(self): + for thread in self.threads: + thread.quit() + thread.wait() diff --git a/tclCommands/TclCommand.py b/tclCommands/TclCommand.py index 4a2656b0..e016d783 100644 --- a/tclCommands/TclCommand.py +++ b/tclCommands/TclCommand.py @@ -53,8 +53,8 @@ class TclCommand(object): if self.app is None: raise TypeError('Expected app to be appMain instance.') - if not isinstance(self.app, appMain.App): - raise TypeError('Expected appMain, got %s.' % type(app)) + # if not isinstance(self.app, appMain.App): + # raise TypeError('Expected appMain, got %s.' % type(app)) self.log = self.app.log self.error_info = None