2

I am developing a multithread application using the library pyQt5. There are many threads activated in order to plot in the GUI some data extracted from many devices (one thread <-> one device). If the application runs for over 30 minutes, the application closes without any error (maybe I was not able to log it).

The problem is that I am expecting the application will run without quitting unexpectedly after 30 minutes. I create an object inside the code in order to record the logs, but I didn't log any unexpected error. I run my program on Pycharm many times in a debugging mode in run mode:

  1. Debugging: the program does not work properly due to the many threads that are launched, so after 2 minutes I see a window error;

  2. Run: it works properly over an hour without incurring in any python or windows error.

Do you have any idea, like memory limits on python or on windows 11 with 64 bit, that can crash my software?

This happens using the executable file generated using pyinstaller.

I increased the RAM memory in order to monitor it during the execution of the program, but the RAM does not increase in a significant way.

The only error that I catch is -1073741571 (0xC00000FD), a stack overflow. But I do not have any recursive function. Could I increase the size? How can I face it?

Complete error:

Windows fatal exception: stack overflow

Thread 0x00003220 (most recent call first):
  File "C:\honeywell\dev\git\python\fire-chamber\fire_chamber\lib\common\threads\worker.py", line 24 in run
  File "C:\Program Files\JetBrains\PyCharm 2024.3.1.1\plugins\python-ce\helpers\pydev\_pydev_bundle\pydev_monkey_qt.py", line 232 in _exec_run

Thread 0x000084cc (most recent call first):
  File "C:\honeywell\dev\git\python\fire-chamber\fire_chamber\lib\common\threads\worker.py", line 24 in run
  File "C:\Program Files\JetBrains\PyCharm 2024.3.1.1\plugins\python-ce\helpers\pydev\_pydev_bundle\pydev_monkey_qt.py", line 232 in _exec_run

Thread 0x00003714 (most recent call first):
  File "C:\Program Files\JetBrains\PyCharm 2024.3.1.1\plugins\python-ce\helpers\pydev\_pydev_bundle\pydev_monkey_qt.py", line 232 in _exec_run

Thread 0x00003548 (most recent call first):
  File "C:\honeywell\dev\git\python\fire-chamber\fire_chamber\lib\common\threads\ram_reader.py", line 30 in run
  File "C:\Program Files\JetBrains\PyCharm 2024.3.1.1\plugins\python-ce\helpers\pydev\_pydev_bundle\pydev_monkey_qt.py", line 237 in _new_run

Thread 0x000080e4 (most recent call first):
  File "C:\Users\H544210\AppData\Local\Programs\Python\Python312\Lib\threading.py", line 359 in wait
  File "C:\Users\H544210\AppData\Local\Programs\Python\Python312\Lib\queue.py", line 180 in get
  File "C:\honeywell\dev\git\python\fire-chamber\fire_chamber\lib\thread_manager\threads\thread_event_driven_base.py", line 28 in run
  File "C:\Program Files\JetBrains\PyCharm 2024.3.1.1\plugins\python-ce\helpers\pydev\_pydev_bundle\pydev_monkey_qt.py", line 237 in _new_run

Thread 0x0000846c (most recent call first):
  File "C:\honeywell\dev\git\python\fire-chamber\fire_chamber\lib\data_storage\data_storage.py", line 141 in _remove_last_rows
  File "C:\honeywell\dev\git\python\fire-chamber\fire_chamber\lib\data_storage\data_storage.py", line 122 in _creating_raw_csv
  File "C:\honeywell\dev\git\python\fire-chamber\fire_chamber\lib\data_storage\data_storage.py", line 99 in save_raw_data
  File "C:\honeywell\dev\git\python\fire-chamber\fire_chamber\lib\thread_manager\threads\thread_event_driven_base.py", line 30 in run
  File "C:\Program Files\JetBrains\PyCharm 2024.3.1.1\plugins\python-ce\helpers\pydev\_pydev_bundle\pydev_monkey_qt.py", line 237 in _new_run

Thread 0x0000698c (most recent call first):
  File "C:\Users\H544210\AppData\Local\Programs\Python\Python312\Lib\threading.py", line 359 in wait
  File "C:\Users\H544210\AppData\Local\Programs\Python\Python312\Lib\threading.py", line 655 in wait
  File "C:\Program Files\JetBrains\PyCharm 2024.3.1.1\plugins\python-ce\helpers\pydev\pydevd.py", line 159 in _on_run
  File "C:\Program Files\JetBrains\PyCharm 2024.3.1.1\plugins\python-ce\helpers\pydev\_pydevd_bundle\pydevd_comm.py", line 219 in run
  File "C:\Users\H544210\AppData\Local\Programs\Python\Python312\Lib\threading.py", line 1075 in _bootstrap_inner
  File "C:\Users\H544210\AppData\Local\Programs\Python\Python312\Lib\threading.py", line 1032 in _bootstrap

Thread 0x000080e0 (most recent call first):
  File "C:\Program Files\JetBrains\PyCharm 2024.3.1.1\plugins\python-ce\helpers\pydev\_pydevd_bundle\pydevd_comm.py", line 291 in _on_run
  File "C:\Program Files\JetBrains\PyCharm 2024.3.1.1\plugins\python-ce\helpers\pydev\_pydevd_bundle\pydevd_comm.py", line 219 in run
  File "C:\Users\H544210\AppData\Local\Programs\Python\Python312\Lib\threading.py", line 1075 in _bootstrap_inner
  File "C:\Users\H544210\AppData\Local\Programs\Python\Python312\Lib\threading.py", line 1032 in _bootstrap

Thread 0x000050bc (most recent call first):
  File "C:\Users\H544210\AppData\Local\Programs\Python\Python312\Lib\threading.py", line 359 in wait
  File "C:\Users\H544210\AppData\Local\Programs\Python\Python312\Lib\queue.py", line 180 in get
  File "C:\Program Files\JetBrains\PyCharm 2024.3.1.1\plugins\python-ce\helpers\pydev\_pydevd_bundle\pydevd_comm.py", line 367 in _on_run
  File "C:\Program Files\JetBrains\PyCharm 2024.3.1.1\plugins\python-ce\helpers\pydev\_pydevd_bundle\pydevd_comm.py", line 219 in run
  File "C:\Users\H544210\AppData\Local\Programs\Python\Python312\Lib\threading.py", line 1075 in _bootstrap_inner
  File "C:\Users\H544210\AppData\Local\Programs\Python\Python312\Lib\threading.py", line 1032 in _bootstrap

Current thread 0x00004c04 (most recent call first):
  File "C:\honeywell\dev\git\python\fire-chamber\venv\Lib\site-packages\matplotlib\transforms.py", line 2436 in get_affine
  File "C:\honeywell\dev\git\python\fire-chamber\venv\Lib\site-packages\matplotlib\transforms.py", line 2410 in transform_affine
  File "C:\honeywell\dev\git\python\fire-chamber\venv\Lib\site-packages\matplotlib\transforms.py", line 1495 in transform
  File "C:\honeywell\dev\git\python\fire-chamber\venv\Lib\site-packages\matplotlib\transforms.py", line 465 in transformed
  File "C:\honeywell\dev\git\python\fire-chamber\venv\Lib\site-packages\matplotlib\axis.py", line 2581 in get_tick_space
  File "C:\honeywell\dev\git\python\fire-chamber\venv\Lib\site-packages\matplotlib\ticker.py", line 2168 in _raw_ticks
  File "C:\honeywell\dev\git\python\fire-chamber\venv\Lib\site-packages\matplotlib\ticker.py", line 2238 in tick_values
  File "C:\honeywell\dev\git\python\fire-chamber\venv\Lib\site-packages\matplotlib\ticker.py", line 2230 in __call__
  File "C:\honeywell\dev\git\python\fire-chamber\venv\Lib\site-packages\matplotlib\axis.py", line 1525 in get_majorticklocs
  File "C:\honeywell\dev\git\python\fire-chamber\venv\Lib\site-packages\matplotlib\axis.py", line 1281 in _update_ticks
  File "C:\honeywell\dev\git\python\fire-chamber\venv\Lib\site-packages\matplotlib\axis.py", line 1351 in get_tightbbox
  File "C:\honeywell\dev\git\python\fire-chamber\venv\Lib\site-packages\matplotlib\artist.py", line 1402 in _get_tightbbox_for_layout_only
  File "C:\honeywell\dev\git\python\fire-chamber\venv\Lib\site-packages\matplotlib\axes\_base.py", line 4554 in get_tightbbox
  File "C:\honeywell\dev\git\python\fire-chamber\venv\Lib\site-packages\matplotlib\artist.py", line 1402 in _get_tightbbox_for_layout_only
  File "C:\honeywell\dev\git\python\fire-chamber\venv\Lib\site-packages\matplotlib\_constrained_layout.py", line 641 in get_pos_and_bbox
  File "C:\honeywell\dev\git\python\fire-chamber\venv\Lib\site-packages\matplotlib\_constrained_layout.py", line 388 in make_layout_margins
  File "C:\honeywell\dev\git\python\fire-chamber\venv\Lib\site-packages\matplotlib\_constrained_layout.py", line 116 in do_constrained_layout
  File "C:\honeywell\dev\git\python\fire-chamber\venv\Lib\site-packages\matplotlib\layout_engine.py", line 278 in execute
  File "C:\honeywell\dev\git\python\fire-chamber\venv\Lib\site-packages\matplotlib\figure.py", line 3251 in draw
  File "C:\honeywell\dev\git\python\fire-chamber\venv\Lib\site-packages\matplotlib\artist.py", line 71 in draw_wrapper
  File "C:\honeywell\dev\git\python\fire-chamber\venv\Lib\site-packages\matplotlib\artist.py", line 94 in draw_wrapper
  File "C:\honeywell\dev\git\python\fire-chamber\venv\Lib\site-packages\matplotlib\backends\backend_agg.py", line 382 in draw
  File "C:\honeywell\dev\git\python\fire-chamber\fire_chamber\lib\interface\widgets\ram_monitor.py", line 104 in _update_generic_plot
  File "C:\honeywell\dev\git\python\fire-chamber\fire_chamber\lib\interface\widgets\ram_monitor.py", line 112 in _update_plot
  File "C:\honeywell\dev\git\python\fire-chamber\venv\Lib\site-packages\matplotlib\backends\backend_qt.py", line 451 in flush_events
  File "C:\honeywell\dev\git\python\fire-chamber\fire_chamber\lib\interface\widgets\mpl_canvas.py", line 180 in update_curve
  File "C:\honeywell\dev\git\python\fire-chamber\fire_chamber\lib\interface\interface.py", line 888 in _smoke_plot_update
  File "C:\honeywell\dev\git\python\fire-chamber\fire_chamber\lib\interface\interface.py", line 869 in _clib_update
  File "C:\honeywell\dev\git\python\fire-chamber\fire_chamber\lib\interface\interface.py", line 961 in _update_data
  File "C:\honeywell\dev\git\python\fire-chamber\venv\Lib\site-packages\matplotlib\backends\backend_qt.py", line 451 in flush_events
  File "C:\honeywell\dev\git\python\fire-chamber\fire_chamber\lib\interface\widgets\ram_monitor.py", line 105 in _update_generic_plot
  File "C:\honeywell\dev\git\python\fire-chamber\fire_chamber\lib\interface\widgets\ram_monitor.py", line 112 in _update_plot
  File "C:\honeywell\dev\git\python\fire-chamber\venv\Lib\site-packages\matplotlib\backends\backend_qt.py", line 451 in flush_events
  File "C:\honeywell\dev\git\python\fire-chamber\fire_chamber\lib\interface\widgets\mpl_canvas.py", line 180 in update_curve
  File "C:\honeywell\dev\git\python\fire-chamber\fire_chamber\lib\interface\interface.py", line 817 in _analog_data_update
  File "C:\honeywell\dev\git\python\fire-chamber\fire_chamber\lib\interface\interface.py", line 961 in _update_data
  File "C:\honeywell\dev\git\python\fire-chamber\venv\Lib\site-packages\matplotlib\backends\backend_qt.py", line 451 in flush_events
  File "C:\honeywell\dev\git\python\fire-chamber\fire_chamber\lib\interface\widgets\ram_monitor.py", line 105 in _update_generic_plot
  File "C:\honeywell\dev\git\python\fire-chamber\fire_chamber\lib\interface\widgets\ram_monitor.py", line 112 in _update_plot
  File "C:\honeywell\dev\git\python\fire-chamber\venv\Lib\site-packages\matplotlib\backends\backend_qt.py", line 451 in flush_events
  File "C:\honeywell\dev\git\python\fire-chamber\fire_chamber\lib\interface\widgets\ram_monitor.py", line 105 in _update_generic_plot
  File "C:\honeywell\dev\git\python\fire-chamber\fire_chamber\lib\interface\widgets\ram_monitor.py", line 112 in _update_plot
  File "C:\honeywell\dev\git\python\fire-chamber\venv\Lib\site-packages\matplotlib\backends\backend_qt.py", line 451 in flush_events
  File "C:\honeywell\dev\git\python\fire-chamber\fire_chamber\lib\interface\widgets\ram_monitor.py", line 105 in _update_generic_plot
  File "C:\honeywell\dev\git\python\fire-chamber\fire_chamber\lib\interface\widgets\ram_monitor.py", line 112 in _update_plot
  File "C:\honeywell\dev\git\python\fire-chamber\venv\Lib\site-packages\matplotlib\backends\backend_qt.py", line 451 in flush_events
  File "C:\honeywell\dev\git\python\fire-chamber\fire_chamber\lib\interface\widgets\mpl_canvas.py", line 180 in update_curve
  File "C:\honeywell\dev\git\python\fire-chamber\fire_chamber\lib\interface\interface.py", line 888 in _smoke_plot_update
  File "C:\honeywell\dev\git\python\fire-chamber\fire_chamber\lib\interface\interface.py", line 869 in _clib_update
  File "C:\honeywell\dev\git\python\fire-chamber\fire_chamber\lib\interface\interface.py", line 961 in _update_data
  File "C:\honeywell\dev\git\python\fire-chamber\venv\Lib\site-packages\matplotlib\backends\backend_qt.py", line 451 in flush_events
  File "C:\honeywell\dev\git\python\fire-chamber\fire_chamber\lib\interface\widgets\mpl_canvas.py", line 180 in update_curve
  File "C:\honeywell\dev\git\python\fire-chamber\fire_chamber\lib\interface\interface.py", line 817 in _analog_data_update
  File "C:\honeywell\dev\git\python\fire-chamber\fire_chamber\lib\interface\interface.py", line 961 in _update_data
  File "C:\honeywell\dev\git\python\fire-chamber\venv\Lib\site-packages\matplotlib\backends\backend_qt.py", line 451 in flush_events
  File "C:\honeywell\dev\git\python\fire-chamber\fire_chamber\lib\interface\widgets\ram_monitor.py", line 105 in _update_generic_plot
  File "C:\honeywell\dev\git\python\fire-chamber\fire_chamber\lib\interface\widgets\ram_monitor.py", line 112 in _update_plot
  File "C:\honeywell\dev\git\python\fire-chamber\venv\Lib\site-packages\matplotlib\backends\backend_qt.py", line 451 in flush_events
  File "C:\honeywell\dev\git\python\fire-chamber\fire_chamber\lib\interface\widgets\ram_monitor.py", line 105 in _update_generic_plot
  File "C:\honeywell\dev\git\python\fire-chamber\fire_chamber\lib\interface\widgets\ram_monitor.py", line 114 in _update_plot
  File "C:\honeywell\dev\git\python\fire-chamber\venv\Lib\site-packages\matplotlib\backends\backend_qt.py", line 451 in flush_events
  File "C:\honeywell\dev\git\python\fire-chamber\fire_chamber\lib\interface\widgets\ram_monitor.py", line 105 in _update_generic_plot
  File "C:\honeywell\dev\git\python\fire-chamber\fire_chamber\lib\interface\widgets\ram_monitor.py", line 112 in _update_plot
  File "C:\honeywell\dev\git\python\fire-chamber\venv\Lib\site-packages\matplotlib\backends\backend_qt.py", line 451 in flush_events
  File "C:\honeywell\dev\git\python\fire-chamber\fire_chamber\lib\interface\widgets\mpl_canvas.py", line 180 in update_curve
  File "C:\honeywell\dev\git\python\fire-chamber\fire_chamber\lib\interface\interface.py", line 888 in _smoke_plot_update
  File "C:\honeywell\dev\git\python\fire-chamber\fire_chamber\lib\interface\interface.py", line 869 in _clib_update
  File "C:\honeywell\dev\git\python\fire-chamber\fire_chamber\lib\interface\interface.py", line 961 in _update_data
  File "C:\honeywell\dev\git\python\fire-chamber\venv\Lib\site-packages\matplotlib\backends\backend_qt.py", line 451 in flush_events
  File "C:\honeywell\dev\git\python\fire-chamber\fire_chamber\lib\interface\widgets\ram_monitor.py", line 105 in _update_generic_plot
  File "C:\honeywell\dev\git\python\fire-chamber\fire_chamber\lib\interface\widgets\ram_monitor.py", line 114 in _update_plot
  File "C:\honeywell\dev\git\python\fire-chamber\venv\Lib\site-packages\matplotlib\backends\backend_qt.py", line 451 in flush_events
  File "C:\honeywell\dev\git\python\fire-chamber\fire_chamber\lib\interface\widgets\mpl_canvas.py", line 180 in update_curve
  File "C:\honeywell\dev\git\python\fire-chamber\fire_chamber\lib\interface\interface.py", line 817 in _analog_data_update
  File "C:\honeywell\dev\git\python\fire-chamber\fire_chamber\lib\interface\interface.py", line 961 in _update_data
  File "C:\honeywell\dev\git\python\fire-chamber\venv\Lib\site-packages\matplotlib\backends\backend_qt.py", line 451 in flush_events
  File "C:\honeywell\dev\git\python\fire-chamber\fire_chamber\lib\interface\widgets\ram_monitor.py", line 105 in _update_generic_plot
  File "C:\honeywell\dev\git\python\fire-chamber\fire_chamber\lib\interface\widgets\ram_monitor.py", line 112 in _update_plot
  File "C:\honeywell\dev\git\python\fire-chamber\venv\Lib\site-packages\matplotlib\backends\backend_qt.py", line 451 in flush_events
  File "C:\honeywell\dev\git\python\fire-chamber\fire_chamber\lib\interface\widgets\ram_monitor.py", line 105 in _update_generic_plot
  File "C:\honeywell\dev\git\python\fire-chamber\fire_chamber\lib\interface\widgets\ram_monitor.py", line 114 in _update_plot
  File "C:\honeywell\dev\git\python\fire-chamber\venv\Lib\site-packages\matplotlib\backends\backend_qt.py", line 451 in flush_events
  File "C:\honeywell\dev\git\python\fire-chamber\fire_chamber\lib\interface\widgets\ram_monitor.py", line 105 in _update_generic_plot
  File "C:\honeywell\dev\git\python\fire-chamber\fire_chamber\lib\interface\widgets\ram_monitor.py", line 112 in _update_plot
  File "C:\honeywell\dev\git\python\fire-chamber\venv\Lib\site-packages\matplotlib\backends\backend_qt.py", line 451 in flush_events
  File "C:\honeywell\dev\git\python\fire-chamber\fire_chamber\lib\interface\widgets\mpl_canvas.py", line 180 in update_curve
  File "C:\honeywell\dev\git\python\fire-chamber\fire_chamber\lib\interface\interface.py", line 888 in _smoke_plot_update
  File "C:\honeywell\dev\git\python\fire-chamber\fire_chamber\lib\interface\interface.py", line 869 in _clib_update
  File "C:\honeywell\dev\git\python\fire-chamber\fire_chamber\lib\interface\interface.py", line 961 in _update_data
  File "C:\honeywell\dev\git\python\fire-chamber\venv\Lib\site-packages\matplotlib\backends\backend_qt.py", line 451 in flush_events
  File "C:\honeywell\dev\git\python\fire-chamber\fire_chamber\lib\interface\widgets\ram_monitor.py", line 105 in _update_generic_plot
  File "C:\honeywell\dev\git\python\fire-chamber\fire_chamber\lib\interface\widgets\ram_monitor.py", line 114 in _update_plot
  File "C:\honeywell\dev\git\python\fire-chamber\venv\Lib\site-packages\matplotlib\backends\backend_qt.py", line 451 in flush_events
  File "C:\honeywell\dev\git\python\fire-chamber\fire_chamber\lib\interface\widgets\mpl_canvas.py", line 180 in update_curve
  File "C:\honeywell\dev\git\python\fire-chamber\fire_chamber\lib\interface\interface.py", line 817 in _analog_data_update
  File "C:\honeywell\dev\git\python\fire-chamber\fire_chamber\lib\interface\interface.py", line 961 in _update_data
  File "C:\honeywell\dev\git\python\fire-chamber\venv\Lib\site-packages\matplotlib\backends\backend_qt.py", line 451 in flush_events
  File "C:\honeywell\dev\git\python\fire-chamber\fire_chamber\lib\interface\widgets\ram_monitor.py", line 105 in _update_generic_plot
  File "C:\honeywell\dev\git\python\fire-chamber\fire_chamber\lib\interface\widgets\ram_monitor.py", line 112 in _update_plot
  File "C:\honeywell\dev\git\python\fire-chamber\venv\Lib\site-packages\matplotlib\backends\backend_qt.py", line 451 in flush_events
  ...

Process finished with exit code -1073741571 (0xC00000FD)

Minimal example to give an idea how the project works:

import sys
import faulthandler
import time
import random as rd
from copy import deepcopy
from dataclasses import dataclass

import numpy as np
import matplotlib.pyplot as plt
from PyQt5.QtCore import QObject, pyqtSignal, QThread
from PyQt5.QtWidgets import QMainWindow, QWidget, QGridLayout, QApplication, QTabWidget, QGroupBox
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas


SIG1: str = "sig_1"
SIG2: str = "sig_2"
SIG3: str = "sig_3"
SIG4: str = "sig_4"
SIGNAL_TO_PLOT: tuple = (SIG1, SIG2, SIG3, SIG4)



def set_padding(inf_value: float, sup_value: float, perc: float = 0.05) -> tuple[float, float]:
    dim_range: float = np.abs(sup_value - inf_value)
    padding: float = perc * dim_range
    return inf_value - padding, sup_value + padding


class GlobalCommunication(QObject):
    device_data_signal: pyqtSignal = pyqtSignal(object)


global_communication: GlobalCommunication = GlobalCommunication()


@dataclass
class Packet:
    name: str
    value: float


class Instrument:
    def __init__(self, name: str):
        self.id: str = name

    def read(self) -> Packet:
        return Packet(self.id, rd.uniform(0, 1))
    
    
class ThreadWrapper:
    SAMPLING_TIME: float = 1.0  # [s]

    def __init__(self, device: Instrument):
        self._device: Instrument = device
        self._thread: QThread = QThread()
        self._worker: Worker = Worker(self._device, self.SAMPLING_TIME)
        self._worker.moveToThread(self._thread)

        self._thread.started.connect(self._worker.run)
        self._worker.finished.connect(self._thread.quit)
        self._worker.data_ready.connect(global_communication.device_data_signal.emit)

    def run(self):
        self._thread.start()

    def stop(self):
        self._worker.stop()


class Worker(QObject):
    finished: pyqtSignal = pyqtSignal()
    data_ready: pyqtSignal = pyqtSignal(object)

    def __init__(self, device: Instrument, sampling_time: float):
        super().__init__()
        self._device: Instrument = device
        self._sampling_time: float = sampling_time
        self._running: bool = True

    def run(self):
        self._running = True
        while self._running:
            device_data_packet: Packet = self._device.read()
            print(f"{device_data_packet=}")
            self.data_ready.emit(device_data_packet)
            time.sleep(self._sampling_time)
        self.finished.emit()

    def stop(self):
        self._running = False
    
    
class ThreadManagerWrapper:
    def __init__(self):
        self._device_list: list = []
        self._thread_list: list = []
        self.set_configuration()

    def set_configuration(self):
        ############ Objects creation ############
        self.set_devices()
        ############ Thread creation ############
        self.set_threads()

    def set_devices(self):
        # Creating the devices
        self._sig_1 = Instrument(SIG1)
        self._sig_2 = Instrument(SIG2)
        self._sig_3 = Instrument(SIG3)
        self._sig_4 = Instrument(SIG4)

        # Populating the device list
        self._device_list = [
            self._sig_1,
            self._sig_2,
            self._sig_3,
            self._sig_4,
        ]

    def set_threads(self):
        # Creating the analog daq thread
        _sig_1_thread: ThreadWrapper = ThreadWrapper(self._sig_1)
        _sig_2_thread: ThreadWrapper = ThreadWrapper(self._sig_2)
        _sig_3_thread: ThreadWrapper = ThreadWrapper(self._sig_3)
        _sig_4_thread: ThreadWrapper = ThreadWrapper(self._sig_4)

        # Populating the thread list
        self._thread_list =[
            _sig_1_thread,
            _sig_2_thread,
            _sig_3_thread,
            _sig_4_thread,
        ]

    def run_processes(self):
        # Start threads
        self._run_threads()

    def _run_threads(self):
        try:
            for thread_element in self._thread_list:
                thread_element.run()
        except Exception as e:
            print(f"[Error thread] {str(e)}")

    def stop_process(self):
        for thread_element in self._thread_list:
            thread_element.stop()
            

class ConfigurationThread(QObject):
    error: pyqtSignal = pyqtSignal(str)
    ready: pyqtSignal = pyqtSignal()
    finished: pyqtSignal = pyqtSignal()

    def __init__(self):
        super().__init__()
        self.configured: bool = False
        self._thread: QThread = QThread()
        self._worker: ConfigurationWorker = ConfigurationWorker()
        self._worker.moveToThread(self._thread)

        self._thread.started.connect(self._worker.run)
        self._worker.configured.connect(self._on_configured)
        self._worker.error.connect(self._error_manager)
        self._worker.finished.connect(self._on_worker_finished)

        self._thread.start()

    def run(self):
        self._thread.start()

    def _on_configured(self, thread_manager: ThreadManagerWrapper):
        self._thread_manager = thread_manager
        self.configured = True
        self.ready.emit()

    def _on_worker_finished(self):
        '''Do nothing to take alive the thread'''
        pass

    def run_processes(self):
        if self.configured and self._thread_manager:
            self._thread_manager.run_processes()
            self.finished.emit()

    def stop_process(self):
        if self.configured and self._thread_manager:
            self._thread_manager.stop_process()
            self.finished.emit()

    def _error_manager(self, msg: str):
        self.configured = False
        self.error.emit(msg)

    def shutdown(self):
        if self._thread.isRunning():
            self._thread.quit()
            self._thread.wait()


class ConfigurationWorker(QObject):
    finished: pyqtSignal = pyqtSignal()
    error: pyqtSignal = pyqtSignal(str)
    configured: pyqtSignal = pyqtSignal(object)

    def __init__(self):
        super().__init__()
        self._thread_manager: None | ThreadManagerWrapper = None

    def run(self):
        try:
            if not self._thread_manager:
                self._thread_manager = ThreadManagerWrapper()
            self.configured.emit(self._thread_manager)
        except Exception as e:
            print(str(e))
        finally:
            self.finished.emit()
            

class MplCanvas(FigureCanvas):
    def __init__(self, title: str):
        fig, self.ax = plt.subplots(constrained_layout=True)
        super().__init__(fig)
        self.curve, = self.ax.plot([], [], "b-")
        # Show grid
        self.ax.grid(linestyle="--")
        # Set the title
        self.ax.set_title(title)
        # Set label
        self.ax.set_xlabel("Time [s]")
        fig.subplots_adjust(top=0.85)

    @staticmethod
    def _update_x_lim(x_list: list) -> tuple:
        # To not modify the original list I create a deep copy
        x_total_list: list = deepcopy(x_list)
        if not x_total_list:
            # The figure is empty
            return -1, 1
        x_total_list.sort()
        x_inf, x_sup = set_padding(x_total_list[0], x_total_list[-1])
        return x_inf, x_sup

    @staticmethod
    def _update_y_lim(y_list: list) -> tuple:
        if not y_list:
            # The figure is empty
            return -1, 1
        y_inf, y_sup = set_padding(np.min(y_list), np.max(y_list))
        return y_inf, y_sup

    def update_curve(self, x_list: list, y_list: list):
        x_inf, x_sup = self._update_x_lim(x_list)
        self.ax.set_xlim(x_inf, x_sup)
        y_inf, y_sup = self._update_y_lim(y_list)
        self.ax.set_ylim(y_inf, y_sup)
        self.curve.set_data(x_list, y_list)
        self.draw()
        self.flush_events()
    
    
class Interface(QMainWindow):
    def __init__(self):
        super().__init__()
        # I create a list in order to clean all the figure during some events (now it's not used)
        self._figure_dict: dict = {}
        self._data: dict = {
            signal_name: []
            for signal_name in SIGNAL_TO_PLOT
        }
        self._set_figures()
        # Connecting to the update data function
        global_communication.device_data_signal.connect(self._update_data)
        self._set_threads()

    def _set_figures(self):
        self.group_box: QGroupBox = QGroupBox("Signals")
        self.group_box_layout: QGridLayout = QGridLayout(self.group_box)
        for i, signal_name in enumerate(SIGNAL_TO_PLOT):
            fig: MplCanvas = MplCanvas(signal_name)
            self._figure_dict[signal_name] = fig
            row: int = i % 2
            column: int = int(i / 2)
            self.group_box_layout.addWidget(fig, row, column)

        central_widget = QWidget()
        layout = QGridLayout()
        layout.addWidget(self.group_box)
        central_widget.setLayout(layout)
        self.setCentralWidget(central_widget)

    def _set_threads(self):
        self._configuration_thread: ConfigurationThread = ConfigurationThread()
        self._configuration_thread.error.connect(self.run_error_popup)
        self._configuration_thread.ready.connect(self._on_thread_ready)
        self._configuration_thread.finished.connect(self._set_something)

    def _update_data(self, data_packet: Packet):
        signal_name: str = data_packet.name
        # Store the data
        self._data[signal_name].append(data_packet.value)
        x_time: list = list(range(len(self._data[signal_name])))
        self._figure_dict[signal_name].update_curve(x_time, self._data[signal_name])

    def _on_thread_ready(self):
        # Run the threads
        self._configuration_thread.run_processes()

    def _set_something(self):
        print("Is finished [Used in the main application to detect an event in the main application]")
        
    def run_error_popup(self, error_msg: str):
        print(f"The error is: {error_msg}")


def run_ui():
    # Launching the app
    app: QApplication = QApplication([])
    interface: Interface = Interface()
    interface.show()
    sys.exit(app.exec())


if __name__ == '__main__':
    faulthandler.enable()
    print(sys.getrecursionlimit())
    run_ui()

1
  • I have created a minimal example that works. You can emulate it in your enviroment. I use python3.12 Commented Oct 13 at 10:26

0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.