diff --git a/examples/example3/ChildProcess3.py b/examples/example3/ChildProcess3.py index 3cf5a2ed39c32c6901ebd7fa9e8cdb5c9ee04060..97c500b12b59d665ab767b3a599f189054a98279 100644 --- a/examples/example3/ChildProcess3.py +++ b/examples/example3/ChildProcess3.py @@ -28,3 +28,7 @@ class ChildProcess3(cmp.CProcess): @test_call2.setter(emit_to='bar') def test_call2(self, value: int): self.my_value = value + + @cmp.CProcess.register_signal() + def exception_call(self, value: int): + return value/0 \ No newline at end of file diff --git a/examples/example3/ChildProcessControl3.py b/examples/example3/ChildProcessControl3.py index 143158630781a4e4c669d8d29d3700d8d52f83f4..5e61cc90d748ceae0aa65736fff11da0feb61da2 100644 --- a/examples/example3/ChildProcessControl3.py +++ b/examples/example3/ChildProcessControl3.py @@ -17,3 +17,6 @@ class ChildProcessControl3(cmp.CProcessControl): print(a) #print(f"{os.getpid()} -> call_without_mp with {a}, {b}, {c}!") + @cmp.CProcessControl.register_function() + def exception_call(self, a): + pass diff --git a/examples/example3/example3.py b/examples/example3/example3.py index f3829bd6b77ad43dacf2c5a45430802803ccd33a..8e1b454c3a64fb78e9e0c515e53b21b685a7cb14 100644 --- a/examples/example3/example3.py +++ b/examples/example3/example3.py @@ -39,7 +39,7 @@ class Form(QDialog): self.lineedit.setFocus() self.setWindowTitle('Upper') # self.lineedit.returnPressed.connect(lambda: child_con.call_without_mp(1, 2, c=3)) - self.lineedit.returnPressed.connect(lambda: child_con.test_call(1)) + self.lineedit.returnPressed.connect(lambda: child_con.exception_call(1)) def updateUI(self, text): diff --git a/src/cmp/CException.py b/src/cmp/CException.py new file mode 100644 index 0000000000000000000000000000000000000000..cfca20e511f0165f5101c0c87584a41317d87e45 --- /dev/null +++ b/src/cmp/CException.py @@ -0,0 +1,23 @@ +import traceback + + +class CException: + + def __init__(self, parent_name: str, function_name: str, exception: Exception): + self.parent_name = parent_name + self.function_name: str = function_name + self.exception = exception + self.traceback_list: list[str] = traceback.format_exception( + type(self.exception), + value=self.exception, + tb=self.exception.__traceback__) + self.additional_info: str = "" + + def traceback_short(self) -> str: + return "".join(self.traceback_list[-2:len(self.traceback_list)]) + + def traceback(self) -> str: + return "".join(self.traceback_list) + + def set_additional_info(self, additional_info: str): + self.additional_info = additional_info diff --git a/src/cmp/CProcess.py b/src/cmp/CProcess.py index a219d8d6c9f27d83a837f11b1137f5bee85e2802..87cf6ec074ff4aec45f32b59446f3af547677d27 100644 --- a/src/cmp/CProcess.py +++ b/src/cmp/CProcess.py @@ -118,6 +118,15 @@ class CProcess(CBase, Process): result = cmp.CResultRecord(func_name, signal_name, res) self.state_queue.put(result) + def _put_exception_to_queue(self, func_name, exc): + self._internal_logger.debug(f"Error executing {func_name}.") + tb_str = traceback.format_exception(type(exc), value=exc, tb=exc.__traceback__) + tb_join = "".join(tb_str[-2:len(tb_str)]) + result = cmp.CException(self.name, func_name, exc, ) + result.set_additional_info(tb_join) + self.state_queue.put(result) + + @staticmethod def register_signal(postfix=None, signal_name: str = None): _postfix = postfix.strip() if postfix is not None else None @@ -138,9 +147,15 @@ class CProcess(CBase, Process): self._internal_logger.debug(f"Constructing signal name for function '{func.__name__}': {sign}") else: sign = None - res = func(self, *args, **kwargs) - self._put_result_to_queue(func_name, sign, res) - return res + try: + res = func(self, *args, **kwargs) + self._put_result_to_queue(func_name, sign, res) + return res + except Exception as e: + self._internal_logger.error(f"Error in function {func_name}: {e} ({type(e)})") + self._put_exception_to_queue(func.__name__, e) + return None + return get_signature diff --git a/src/cmp/CProcessControl.py b/src/cmp/CProcessControl.py index ca6df801f3e7b4e329e886bbaf123214137ebe5b..b2da7c231fe9ec7a2527e4ac9cbc034de27cb48c 100644 --- a/src/cmp/CProcessControl.py +++ b/src/cmp/CProcessControl.py @@ -5,20 +5,23 @@ import os import re import signal import time +import traceback from multiprocessing import Queue, Process, Value from typing import Type from PySide6.QtCore import QObject, QThreadPool, Signal from PySide6.QtGui import QWindow -from PySide6.QtWidgets import QWidget +from PySide6.QtWidgets import QWidget, QMessageBox from rich.logging import RichHandler import cmp +from cmp import CException from cmp.CBase import CBase class CProcessControl(CBase, QObject): + on_exception_raised = Signal(object, name='on_exception_raised') def __init__(self, parent: QObject = None, signal_class: QObject = None, internal_log: bool = False, @@ -60,6 +63,9 @@ class CProcessControl(CBase, QObject): self.internal_log_level = internal_log_level self.logger, self.logger_handler = self.create_new_logger(f"{self.__class__.__name__}({os.getpid()})") + + self.on_exception_raised.connect(self.display_exception) + self.msg_box = QMessageBox() # ================================================================================================================== # # ================================================================================================================== @@ -107,6 +113,12 @@ class CProcessControl(CBase, QObject): res.emit_signal(self._signal_class) except Exception as e: self._internal_logger.error(f"Error while emitting {res} in {self.__class__.__name__}: {e}") + elif isinstance(res, cmp.CException): + self._internal_logger.error(f"Received exception: {res}") + try: + self.on_exception_raised.emit(res) + except Exception as e: + self._internal_logger.error(f"Error while emitting exception: {e}") else: self._internal_logger.error(f"Received unknown result {res}!") @@ -118,6 +130,23 @@ class CProcessControl(CBase, QObject): self.state_queue.close() self.cmd_queue.close() + def display_exception(self, e: cmp.CException): + # Create a message box + try: + self.msg_box = QMessageBox() + self.msg_box.setIcon(QMessageBox.Critical) + self.msg_box.setText(f"Error executing {e.function_name} in {e.parent_name}") + self.msg_box.setInformativeText(f"Error: {e.exception}") + self.msg_box.setWindowTitle("Error") + self.msg_box.setDetailedText(e.traceback_short()) + self.msg_box.setStandardButtons(QMessageBox.Ok) + self.msg_box.show() + self._internal_logger.error(f"Error executing {e.function_name} in {e.parent_name}: {e.exception}\n" + f"{e.traceback()}") + except Exception as e: + self._internal_logger.error(f"Error while displaying exception: {e}") + + def execute_function(self, func: callable, signal: Signal = None): self.register_function(signal)(func)(self) diff --git a/src/cmp/__init__.py b/src/cmp/__init__.py index cb22ac4863086c7a6e4446958d351976b0f3d898..9195dd6eff14f87dd37e094db908a6059a636a05 100644 --- a/src/cmp/__init__.py +++ b/src/cmp/__init__.py @@ -6,6 +6,7 @@ from rich.logging import RichHandler from .CCommandRecord import CCommandRecord from .CResultRecord import CResultRecord +from .CException import CException from .CProcess import CProcess from .CProcessControl import CProcessControl