From 0c71d36f223a233b437c2d0546f610662f52921f Mon Sep 17 00:00:00 2001 From: Konstantin Leontev Date: Wed, 2 Oct 2024 15:12:15 +0100 Subject: [PATCH] [bos #38044][EDF] (2023-T3) Support for automatic reparation. Initial commit for the process implementation. --- src/RepairGUIAdv/CMakeLists.txt | 1 + src/RepairGUIAdv/geomrepairadv_worker.py | 105 +++++++++++----- .../geomrepairadv_worker_messages.py | 119 ++++++++++++++++++ 3 files changed, 196 insertions(+), 29 deletions(-) create mode 100644 src/RepairGUIAdv/geomrepairadv_worker_messages.py diff --git a/src/RepairGUIAdv/CMakeLists.txt b/src/RepairGUIAdv/CMakeLists.txt index fd1f024c4..bed7efa46 100644 --- a/src/RepairGUIAdv/CMakeLists.txt +++ b/src/RepairGUIAdv/CMakeLists.txt @@ -35,6 +35,7 @@ IF(SALOME_BUILD_GUI) geomrepairadv_logger.py geomrepairadv_progress.py geomrepairadv_worker.py + geomrepairadv_worker_messages.py locate_subshapes.py locate_subshapes_algo.py locate_subshapes_limits.py diff --git a/src/RepairGUIAdv/geomrepairadv_worker.py b/src/RepairGUIAdv/geomrepairadv_worker.py index 553b06650..bfb4caf98 100644 --- a/src/RepairGUIAdv/geomrepairadv_worker.py +++ b/src/RepairGUIAdv/geomrepairadv_worker.py @@ -22,30 +22,39 @@ import inspect from traceback import format_exc -from qtsalome import QApplication, pyqtSignal, QThread, Qt, QTimer +from qtsalome import QApplication, Qt, QTimer from .geomrepairadv_logger import logger +from .geomrepairadv_worker_messages import MessageHandlerFactory +from multiprocessing import Process, Pipe + +import CORBA -class Worker(QThread): +class Worker(Process): """ - Creates a tread to run a given target function with a progress dialog as a parent. + Creates a process to run a given target function with a progress dialog as a parent. """ - progress_update = pyqtSignal(int) - thread_failed = pyqtSignal() - work_completed = pyqtSignal() - def __init__(self, parent=None, target=None, args=None): - super().__init__(parent) + super().__init__() - # Set target function and it's arguments + # Set target function and its arguments self.target = target self.args = args - # Update a progress bar each time we receive an update signal - self.progress_update.connect(parent.setValue) - self.thread_failed.connect(parent.on_failed) - self.work_completed.connect(parent.on_completed) + # Create a pipe for communication + self.parent_conn, self.child_conn = Pipe() + + # Set a timer to handle signals non-blocking way + self.signals_timer = QTimer() + self.signals_timer.timeout.connect(self.handle_signals) + self.signals_timer.start(1000) + + # Create a message handler factory + self.message_handler_factory = MessageHandlerFactory() + + # Store parent for signal handling + self.parent = parent # Calculate total amount of lines in executed function source_lines = inspect.getsourcelines(target) @@ -53,7 +62,7 @@ class Worker(QThread): first_line = source_lines[1] # Set a progress emitter to update the progress from the target - self.progress_emitter = ProgressEmitter(self.progress_update, total_lines, first_line) + self.progress_emitter = ProgressEmitter(self.child_conn, total_lines, first_line) # Set a variable for result self.result = None @@ -70,23 +79,17 @@ class Worker(QThread): """ try: - # Wait mode cursor - QApplication.setOverrideCursor(Qt.WaitCursor) - self.result = self.target(self.args, self.progress_emitter) # Reset the progress when finished - self.progress_update.emit(100) + self.child_conn.send(('progress', 100)) if not self.is_canceled(): - self.work_completed.emit() + self.child_conn.send(('completed', self.result)) except Exception: logger.error(format_exc()) - self.thread_failed.emit() - - finally: - QApplication.restoreOverrideCursor() + self.child_conn.send(('failed',)) def is_canceled(self): @@ -106,9 +109,6 @@ class Worker(QThread): # can return from the running function gracefully. self.progress_emitter.stop() - # Termination doesn't call a final block inside run() - QApplication.restoreOverrideCursor() - # Set a timer for a case when the running function # doesn't use emitter or does so heavy job that it would take # a lot of time for waiting and we need to terminate it. @@ -121,10 +121,26 @@ class Worker(QThread): """ # Check if the job is still running and need to be terminated - if self.isRunning(): - logger.warning('Thread will be terminated!') + if self.is_alive(): + logger.warning('Process will be terminated!') + super().terminate() + self.on_finished() + + + def on_finished(self): + """ + Called when the execution is finished. + """ + + logger.debug('on_finished called') + + QApplication.restoreOverrideCursor() + self.signals_timer.stop() + + self.join() + def get_result(self): """ @@ -134,6 +150,37 @@ class Worker(QThread): return self.result + def start(self): + """ + Starts the process and sets up the communication with the parent. + """ + super().start() + self.handle_signals() + + QApplication.setOverrideCursor(Qt.WaitCursor) + + + def handle_signals(self): + """ + Handles signals from the child process. + """ + + logger.debug('handle_signals called') + + if not self.parent_conn.poll(): + return + + msg = self.parent_conn.recv() + logger.debug('msg: %s', msg) + + # Get a handler for the message type + handler = self.message_handler_factory.get_handler(msg[0]) + if handler: + handler.handle(msg, self) + else: + logger.error('No handler for message type: %s', msg[0]) + + class ProgressEmitter(): """ Helper class to reduce code repetition while update progress @@ -173,6 +220,6 @@ class ProgressEmitter(): progress_value = (line - self.first_line) / self.progress_percent logger.debug('progress_value: %d', progress_value) - self.progress_update.emit(int(progress_value)) + self.progress_update.send(['progress', int(progress_value)]) return self.is_running diff --git a/src/RepairGUIAdv/geomrepairadv_worker_messages.py b/src/RepairGUIAdv/geomrepairadv_worker_messages.py new file mode 100644 index 000000000..2f5bbc2c5 --- /dev/null +++ b/src/RepairGUIAdv/geomrepairadv_worker_messages.py @@ -0,0 +1,119 @@ +# -*- coding: utf-8 -*- +# Copyright (C) 2014-2024 EDF +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# See https://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +# +# Author : Konstantin Leontev (OpenCascade S.A.S) + +class MessageHandler: + """ + MessageHandler is a base class for handling messages + between child and worker processes in the progress bar Worker class. + """ + + def handle(self, msg, worker): + """ + Handle the given message. + + This method should be implemented by subclasses to define how to process + the given message. + + Args: + msg: The message to be handled. + worker: The worker context or object related to the message. + + Raises: + NotImplementedError: If the method is not implemented by a subclass. + """ + + raise NotImplementedError("Subclasses should implement this method") + + +class ProgressHandler(MessageHandler): + """ + ProgressHandler updates progress bar with a value from a message. + """ + + def handle(self, msg, worker): + """ + Sets a new value for a worker progress bar. + + Args: + msg (tuple): A tuple where the second element is the value to be set. + worker (object): The worker object that has a setValue method. + """ + + worker.parent.setValue(msg[1]) + + +class CompletedHandler(MessageHandler): + """ + CompletedHandler handles the completion of the worker process. + """ + + def handle(self, msg, worker): + """ + Handles the given message by performing actions on the worker object. + + Args: + msg: The message to handle. + worker: The worker object that contains the methods to be called. + """ + + worker.result = msg[1] + + worker.parent.on_completed() + worker.on_finished() + + +class FailedHandler(MessageHandler): + """ + FailedHandler handles the failure of the worker process. + """ + + def handle(self, msg, worker): + """ + Handles the given message by invoking the worker's failure handler, + stopping the timer, and joining the worker thread. + + Args: + msg: The message to handle. + worker: The worker object that contains the failure handler, timer, and worker thread. + """ + + worker.parent.on_failed() + worker.on_finished() + + +class MessageHandlerFactory: + """ + MessageHandlerFactory creates a handler for a given message type. + """ + + def __init__(self): + self.handlers = { + 'progress': ProgressHandler(), + 'completed': CompletedHandler(), + 'failed': FailedHandler() + } + + def get_handler(self, msg_type): + """ + Returns a handler for the given message type. + """ + + return self.handlers.get(msg_type, None)