From bbc68caff995ca12e25ce225c08f9db73ac5a7d2 Mon Sep 17 00:00:00 2001 From: Christoph Wiedemann <62332054+cwiede@users.noreply.github.com> Date: Sun, 19 Mar 2023 11:06:58 +0100 Subject: [PATCH 01/10] remove qt5 support from SConstruct --- workspace/SConstruct | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/workspace/SConstruct b/workspace/SConstruct index 12dd6e7..8999cc0 100644 --- a/workspace/SConstruct +++ b/workspace/SConstruct @@ -59,10 +59,7 @@ else: target_platform="msvc_x86_64", deploy_platform="msvc_x86_64", variant="unknown") - if qtversion[0] == "5": - env["QT5VERSION"] = qtversion - env.Tool("qt5") - elif qtversion[0] == "6": + if qtversion[0] == "6": env["QT6VERSION"] = qtversion env.Tool("qt6") else: @@ -70,10 +67,7 @@ else: # TODO: disable QtWidgets and QtGui after this has been fixed: # https://bugreports.qt.io/browse/PYSIDE-1627 -if qtversion[0] == "5": - env.EnableQt5Modules(['QtCore', "QtWidgets", "QtGui"]) -else: - env.EnableQt6Modules(["QtCore", "QtWidgets", "QtGui"]) +env.EnableQt6Modules(["QtCore", "QtWidgets", "QtGui"]) env.AddMethod(lambda env, args: args, "RegisterSources") env.AddMethod(lambda env, args: None, "RegisterTargets") env.Append(CPPDEFINES=["Py_LIMITED_API"]) From cc4b07a488eb182e51b9d74301341867650e7092 Mon Sep 17 00:00:00 2001 From: Christoph Wiedemann <62332054+cwiede@users.noreply.github.com> Date: Mon, 9 Oct 2023 13:09:32 +0200 Subject: [PATCH 02/10] switch to PySide 6.5.3 --- setup.py | 4 ++-- workspace/requirements.txt | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/setup.py b/setup.py index 446a21e..2469b8e 100644 --- a/setup.py +++ b/setup.py @@ -147,8 +147,8 @@ def is_pure(*args): setup(name='nexxT', install_requires=[ - "PySide6==6.5.1.1", - "shiboken6==6.5.1.1", + "PySide6==6.5.3", + "shiboken6==6.5.3", "jsonschema>=3.2.0", "h5py>=2.10.0", "setuptools>=41.0.0", diff --git a/workspace/requirements.txt b/workspace/requirements.txt index d190054..36a9676 100644 --- a/workspace/requirements.txt +++ b/workspace/requirements.txt @@ -1,7 +1,7 @@ -PySide6==6.5.1.1 +PySide6==6.5.3 scons==4.3.0 -shiboken6==6.5.1.1 -shiboken6-generator==6.5.1.1 +shiboken6==6.5.3 +shiboken6-generator==6.5.3 pytest==7.1.3 pytest-cov==4.0.0 pylint==2.15.4 From 6f00e50b25e245840fde867745ebf06b069cb448 Mon Sep 17 00:00:00 2001 From: Christoph Wiedemann <62332054+cwiede@users.noreply.github.com> Date: Mon, 9 Oct 2023 14:23:59 +0200 Subject: [PATCH 03/10] fix disconnect error by introducing @Slot decorator --- nexxT/services/SrvRecordingControl.py | 1 + 1 file changed, 1 insertion(+) diff --git a/nexxT/services/SrvRecordingControl.py b/nexxT/services/SrvRecordingControl.py index 430c3a9..949556d 100644 --- a/nexxT/services/SrvRecordingControl.py +++ b/nexxT/services/SrvRecordingControl.py @@ -158,6 +158,7 @@ def startRecording(self, directory): Application.activeApplication.stateChanged.connect(self.stateChanged) self._startRecording.emit(directory) + @Slot(int) def stateChanged(self, state): """ Stops the recording when application is stopped. From 5c132ebc822d9a16632dbba51dea56ce2df8884f Mon Sep 17 00:00:00 2001 From: Christoph Wiedemann <62332054+cwiede@users.noreply.github.com> Date: Mon, 9 Oct 2023 15:08:59 +0200 Subject: [PATCH 04/10] fix disconnect error by introducing @Slot decorator --- nexxT/services/SrvRecordingControl.py | 1 + 1 file changed, 1 insertion(+) diff --git a/nexxT/services/SrvRecordingControl.py b/nexxT/services/SrvRecordingControl.py index 949556d..fc5c3fb 100644 --- a/nexxT/services/SrvRecordingControl.py +++ b/nexxT/services/SrvRecordingControl.py @@ -127,6 +127,7 @@ def _supportedFeaturesChanged(self, featureset): :return: """ + @Slot(str, float, "qlonglong") def _statusUpdate(self, file=None, lengthInSeconds=None, bytesWritten=None): """ Emits the statusUpdate signal From a3b7bf2d4a7e30b117fd7cef8cdbefc9d3d3b031 Mon Sep 17 00:00:00 2001 From: Christoph Wiedemann <62332054+cwiede@users.noreply.github.com> Date: Fri, 13 Oct 2023 16:50:23 +0200 Subject: [PATCH 05/10] [MethodInvoker] use invokeMethod(...) instead of a single shot signal/slot connection since this seems to be more efficent [PlaybackControl] make removeConnections(...) less prone for deadlocks (but probably this is not really the issue) --- nexxT/core/Utils.py | 9 +++------ nexxT/services/SrvPlaybackControl.py | 14 +++++++++----- 2 files changed, 12 insertions(+), 11 deletions(-) diff --git a/nexxT/core/Utils.py b/nexxT/core/Utils.py index bf7ec28..f91c49f 100644 --- a/nexxT/core/Utils.py +++ b/nexxT/core/Utils.py @@ -17,7 +17,7 @@ import sqlite3 import time from nexxT.Qt.QtCore import (QObject, Signal, Slot, QMutex, QWaitCondition, QCoreApplication, QThread, - QMutexLocker, QRecursiveMutex, QTimer, Qt, QPoint) + QMutexLocker, QRecursiveMutex, QTimer, Qt, QPoint, QMetaObject) from nexxT.Qt.QtGui import QColor, QPainter, QTextLayout, QTextOption from nexxT.Qt.QtWidgets import QFrame, QSizePolicy from nexxT.core.Exceptions import NexTInternalError, InvalidIdentifierException @@ -30,8 +30,6 @@ class MethodInvoker(QObject): https://stackoverflow.com/questions/53296261/usage-of-qgenericargument-q-arg-when-using-invokemethod-in-pyside2 """ - signal = Signal() # 10 arguments - methodscalled = set() IDLE_TASK = "IDLE_TASK" @@ -58,10 +56,9 @@ def __init__(self, callback, connectiontype, *args): if connectiontype is self.IDLE_TASK: QTimer.singleShot(0, self.callbackWrapper) else: - self.signal.connect(self.callbackWrapper, connectiontype) - self.signal.emit() + QMetaObject.invokeMethod(self, "callbackWrapper", connectiontype) - @Slot(object) + @Slot() def callbackWrapper(self): """ Slot which actuall performs the method call. diff --git a/nexxT/services/SrvPlaybackControl.py b/nexxT/services/SrvPlaybackControl.py index 1f705ce..773f27d 100644 --- a/nexxT/services/SrvPlaybackControl.py +++ b/nexxT/services/SrvPlaybackControl.py @@ -270,16 +270,20 @@ def removeConnections(self, playbackDevice): :return: None """ with QMutexLocker(self._mutex): + # note: avoid signal/slot connections/disconnections while holding the mutex since this might lead to + # deadlocks found = [] for devid, dev in self._registeredDevices.items(): if dev["object"] is playbackDevice: - found.append(devid) + found.append((devid, dev)) if len(found) > 0: - for devid in found: + for devid, _ in found: del self._registeredDevices[devid] - logger.debug("disconnected connections of playback device. number of devices left: %d", - len(self._registeredDevices)) - MethodInvoker(dict(object=self, method="_updateFeatureSet", thread=mainThread()), Qt.QueuedConnection) + for devid, dev in found: + del dev + logger.debug("disconnected connections of playback device. number of devices left: %d", + len(self._registeredDevices)) + MethodInvoker(dict(object=self, method="_updateFeatureSet", thread=mainThread()), Qt.QueuedConnection) @handleException def _stopSetSequenceStart(self, filename): From daf3afc9954e56b9ef5fe42c44a336ded81b523c Mon Sep 17 00:00:00 2001 From: Christoph Wiedemann <62332054+cwiede@users.noreply.github.com> Date: Tue, 17 Oct 2023 12:00:35 +0200 Subject: [PATCH 06/10] reduce deadlock probability due to profiling service - do not add ProfilingService to Console Application anymore - this can be added manually by using -e 'from nexxT.interface import Services; Services.addService(Profiling())', but it is probably not needed and seems to be a source of deadlocks induced by PySide6 --- nexxT/core/AppConsole.py | 1 - nexxT/services/SrvProfiling.py | 4 ++++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/nexxT/core/AppConsole.py b/nexxT/core/AppConsole.py index fadfdea..e932448 100644 --- a/nexxT/core/AppConsole.py +++ b/nexxT/core/AppConsole.py @@ -50,7 +50,6 @@ def setupConsoleServices(config): Services.addService("PlaybackControl", PlaybackControlConsole(config)) Services.addService("RecordingControl", MVCRecordingControlBase(config)) Services.addService("Configuration", MVCConfigurationBase(config)) - Services.addService("Profiling", ProfilingService()) def setupGuiServices(config): """ diff --git a/nexxT/services/SrvProfiling.py b/nexxT/services/SrvProfiling.py index c9ef95c..6813d24 100644 --- a/nexxT/services/SrvProfiling.py +++ b/nexxT/services/SrvProfiling.py @@ -252,10 +252,14 @@ def deregisterThread(self): self._mi = None t = QThread.currentThread() logger.debug("deregistering thread %s", t.objectName()) + todel = [] with self._lockThreadSpecific: if t in self._threadSpecificProfiling: self._threadSpecificProfiling[t].timer.stop() + todel.append(self._threadSpecificProfiling[t]) del self._threadSpecificProfiling[t] + for tsp in todel: + del tsp self.threadDeregistered.emit(t.objectName()) @Slot() From bf87ee45a1de652beed2414aec65ff7cb9c5226c Mon Sep 17 00:00:00 2001 From: Christoph Wiedemann <62332054+cwiede@users.noreply.github.com> Date: Thu, 26 Oct 2023 20:39:22 +0200 Subject: [PATCH 07/10] avoid the warning "this shouldn't be happening" --- nexxT/core/Thread.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/nexxT/core/Thread.py b/nexxT/core/Thread.py index 3e196ce..55ecbf8 100644 --- a/nexxT/core/Thread.py +++ b/nexxT/core/Thread.py @@ -11,10 +11,10 @@ import logging import sys import threading -from nexxT.Qt.QtCore import QObject, Signal, Slot, QCoreApplication, QThread +from nexxT.Qt.QtCore import Qt, QObject, Signal, Slot, QCoreApplication, QThread from nexxT.interface import FilterState, Services from nexxT.core.Exceptions import NodeExistsError, NexTInternalError, NodeNotFoundError, NexTRuntimeError -from nexxT.core.Utils import handleException +from nexxT.core.Utils import handleException, MethodInvoker logger = logging.getLogger(__name__) @@ -168,8 +168,12 @@ def performOperation(self, operation, barrier): # wait that all threads are in their event loop. inProcessEvents = self._qthread.property("processEventsRunning") if inProcessEvents: - logging.getLogger(__name__).warning( + logging.getLogger(__name__).debug( "operation %s happening during receiveAsync's processEvents. This shouldn't be happening.", operation) + MethodInvoker(dict(object=self, method="performOperation", thread=self.thread()), + Qt.QueuedConnection, operation, barrier) + return + barrier.wait() if operation in self._operations: # pre-adaptation of states (e.g. from CONSTRUCTED to INITIALIZING) From a3dfd56b3a1b3ccce699efc290c153f3b8857393 Mon Sep 17 00:00:00 2001 From: Christoph Wiedemann <62332054+cwiede@users.noreply.github.com> Date: Thu, 26 Oct 2023 20:40:52 +0200 Subject: [PATCH 08/10] fix wrong slot decoration --- nexxT/core/Utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nexxT/core/Utils.py b/nexxT/core/Utils.py index bf7ec28..540ef99 100644 --- a/nexxT/core/Utils.py +++ b/nexxT/core/Utils.py @@ -61,7 +61,7 @@ def __init__(self, callback, connectiontype, *args): self.signal.connect(self.callbackWrapper, connectiontype) self.signal.emit() - @Slot(object) + @Slot() def callbackWrapper(self): """ Slot which actuall performs the method call. From a432a00f60d475f7b9d713e7c252cf2112c51713 Mon Sep 17 00:00:00 2001 From: Christoph Wiedemann <62332054+cwiede@users.noreply.github.com> Date: Thu, 26 Oct 2023 20:41:58 +0200 Subject: [PATCH 09/10] fix reload test --- nexxT/services/gui/MainWindow.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nexxT/services/gui/MainWindow.py b/nexxT/services/gui/MainWindow.py index 11d5732..46edd87 100644 --- a/nexxT/services/gui/MainWindow.py +++ b/nexxT/services/gui/MainWindow.py @@ -109,7 +109,7 @@ def restoreGeometry(self, geometry): restoredNormalGeometry = QRect(x, y, width, height) maximized = bool(stream.readUInt32()) fullScreen = bool(stream.readUInt32()) - frameHeight = 20 + frameHeight = 0 if (not restoredNormalGeometry.isValid()) and (restoredFrameGeometry.isValid()): # there seems to be an issue in PySide6 that the normalGeometry is always invalid (?) restoredNormalGeometry = restoredFrameGeometry From 2bb9a6840e19263faa28f04285c371aecc3f5c86 Mon Sep 17 00:00:00 2001 From: Christoph Wiedemann <62332054+cwiede@users.noreply.github.com> Date: Tue, 31 Oct 2023 09:08:00 +0100 Subject: [PATCH 10/10] revert change with postponing a performOperation when we are unexpectedly still in receiveAsync's processEvents --- nexxT/core/Thread.py | 5 +---- nexxT/tests/core/test_ActiveApplication.py | 1 - 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/nexxT/core/Thread.py b/nexxT/core/Thread.py index 55ecbf8..dcf2bb5 100644 --- a/nexxT/core/Thread.py +++ b/nexxT/core/Thread.py @@ -168,11 +168,8 @@ def performOperation(self, operation, barrier): # wait that all threads are in their event loop. inProcessEvents = self._qthread.property("processEventsRunning") if inProcessEvents: - logging.getLogger(__name__).debug( + logging.getLogger(__name__).warning( "operation %s happening during receiveAsync's processEvents. This shouldn't be happening.", operation) - MethodInvoker(dict(object=self, method="performOperation", thread=self.thread()), - Qt.QueuedConnection, operation, barrier) - return barrier.wait() if operation in self._operations: diff --git a/nexxT/tests/core/test_ActiveApplication.py b/nexxT/tests/core/test_ActiveApplication.py index 340eef7..5f331e0 100644 --- a/nexxT/tests/core/test_ActiveApplication.py +++ b/nexxT/tests/core/test_ActiveApplication.py @@ -10,7 +10,6 @@ from nexxT.interface import FilterState import os import time -import pprint import nexxT.Qt from nexxT.Qt.QtCore import QCoreApplication, QTimer