[bos #38044][EDF] (2023-T3) Support for automatic reparation. Initial commit for the process implementation.

This commit is contained in:
Konstantin Leontev 2024-10-02 15:12:15 +01:00
parent 4e90f531cf
commit 0c71d36f22
3 changed files with 196 additions and 29 deletions

View File

@ -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

View File

@ -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

View File

@ -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)