Failed to connect QLocalSocket to QLocalServer inside QProcess, QProcess always crashes with unknown error

3 weeks ago 14
ARTICLE AD BOX

I'm trying to create isolated Camera, and want to use it with signals through QLocalSocket. That's needed to prevent freezing GUI of crashing when catching C++ error
Classes relationships: ThreadedCamGear is a main class, it creates QProcess and QLocalSocket inside. After calling init_cam(), it starts _CamWorker inside QProcess and tries to connect QLocal socket with _CamWorker.

Example:
cam = ThreadedCamGear(10)

*cam.init_cam(source) # creates camera, and emits pyqtsignal with success flag
cam.read() # tries to read the frame and emits it/None with pyqtsignal

The problem is that QProcess always crashing neither with Unknown error or ProcessError.Crashed without any additional information. Of course, after(or before?) that I get QLocalSocket::connectToServer: Connection refused
With some simple prints I figured out that method _CamWorker.set*_client is never called, and listening starts successfully.

Some strange behavior, not so related to problem: QProcess's input prints only if I put return nearly after self.process.start()
Al names, except for fixed path of .socket existing files are Invalid with QLocalSocket.ConnectToServer().

My device: Ubuntu 24.04.3 LTS MACHENIKE L16, NVIDIA GeForce RTX™ 5070 Laptop GPU
GNOME version: 46
Kernel version: Linux 6.17.0-14-generic

methods of class, that tries to use ThreadedCamGear:

def set_path(self, path: str): """Change frame, delete all previous data Emits success of the operation with set_path_succeded(bool) pyqtsignal """ if self._async_scenario != 0: return self.path = path self.data = [] self._video_cap = ThreadedCamGear(7) self._video_cap.init_succeed.connect(self._finalize1_set_path) self._video_cap.received_frame.connect(self._finalize2_set_path) self._async_scenario = 1 self._video_cap.init_cam(source=path) def _finalize1_set_path(self, success: bool): async_scenario = self._async_scenario self._async_scenario = 0 if async_scenario != 1: return if not success: if self._video_cap: self._video_cap.finished.connect(self._remove_deleted_threads) self._later_delete_threads.append(self._video_cap) self._video_cap.stop() self._video_cap = None self.set_path_succeded.emit(False) return self._async_scenario = 2 self._video_cap.read() def _finalize2_set_path(self, frame): async_scenario = self._async_scenario self._async_scenario = 0 if async_scenario != 2: return if not frame is None: self.change_frame(frame) self._video_cap.finished.connect(self._remove_deleted_threads) self._later_delete_threads.append(self._video_cap) # to prevent sudden delete self._video_cap.stop() self._video_cap = None self.set_path_succeded.emit(not frame is None)

threaded_camgear.py:

from PyQt6.QtCore import QObject, pyqtSignal, QTimer, QProcess from PyQt6.QtNetwork import QLocalSocket from enum import Enum import pickle from time import time_ns import os class ThreadedCamGear(QObject): process: QProcess received_frame = pyqtSignal(object) init_succeed = pyqtSignal(bool) finished = pyqtSignal() def __init__(self, max_timeout: float = 5): """ Parameters: max_timeout (float): max time to wait CamGear for initialization in seconds """ super().__init__(parent=None) self.cam = None self.timeout = max_timeout self.timer = QTimer() self.timer.setSingleShot(True) self.timer.timeout.connect(self._timeout) self._timer_type = _TimerType.NoWaiting self.process = QProcess() self.server_name = f"/home/user/Documents/project_dir/r1.socket" # self.server_name = f"/tmp/threadedcam_{id(self)}_{os.getpid()}.socket" - invalid name self.process.setObjectName(f"ThreadedCamGear.QProcess {id(self.process)}") self.socket = QLocalSocket() self.socket.readyRead.connect(self._receive_from_socket) self.process.readyReadStandardError.connect(self._read_process_error) self.process.readyReadStandardOutput.connect(self._read_process_output) self.process.errorOccurred.connect(self.error) self._init_source = "" def _read_process_error(self): data = self.process.readAllStandardError() print(f"[Child process stderr] {data.data().decode()}") def _read_process_output(self): data = self.process.readAllStandardOutput() print(f"[Child process stdout] {data.data().decode()}") def _receive_from_socket(self): message = pickle.loads(self.socket.readData(5*10**7)) # max image size in 4K if message["type"] == "init_success": self._finalize_init_cam(message["data"]) if message["type"] == "frame": self._finalize_read(message["data"]) def _send_to_socket(self, message: dict) -> bool: "Returns: success(bool)" return self.socket.writeData(pickle.dumps(message)) != -1 def _thread_finished(self): self.finished.emit() def _timeout(self): if self._timer_type == _TimerType.WaitingForInit: print( f"{ThreadedCamGear :: Failed init ThreadedCamGear with source: {self._init_source}" ) self.init_succeed.emit(False) self.stop() if self._timer_type == _TimerType.WaitingForRead: self.received_frame.emit(None) self.stop() self._timer_type = _TimerType.NoWaiting def error(self, error_msg: str): print(f"{self.objectName()}: {error_msg}") self.stop() def _finalize_init_cam(self, success: bool): print("fin", success) if self._timer_type != _TimerType.WaitingForInit: return self.timer.stop() self._timer_type = _TimerType.NoWaiting source = self._init_source self._init_source = "" try: if (not success): if self.process.state() == QProcess.ProcessState.Running: self._send_to_socket({ "type": "stop" }) print( f"{ThreadedCamGear :: Failed init ThreadedCamGear with source: {self._init_source}" ) self.init_succeed.emit(False) return except Exception as err: print( f"{ThreadedCamGear :: Failed init ThreadedCamGear with source: {self._init_source}" ) print("except") self.error(str(err)) self.init_succeed.emit(False) return self.init_succeed.emit(True) def init_cam( self, source: Any = 0, stream_mode: bool = False, backend: int = 0, colorspace: str = None, logging: bool = False, time_delay: int = 0, **options: dict, ): self._init_source = source self._timer_type = _TimerType.WaitingForInit print( f"ThreadedCamGear :: Trying init ThreadedCamGear with source: {source}" ) start_time = time_ns() self.process.start("python", ["-u", "/home/user/Documents/project_dir/source/camgear_process.py", self.server_name]) if not self.process.waitForStarted(self.timeout * 10**3): self.init_succeed.emit(False) return self.socket.connectToServer(self.server_name) timeout = self.timeout * 10**3 - int((time_ns() - start_time) * 10**(-6)) if (timeout <= 0) or not self.socket.waitForConnected(timeout): print(1, self.socket.errorString()) print(self.process.errorString(), 234675467654745456456456) self.init_succeed.emit(False) return print(1) # return self.timer.start(self.timeout * 10**3 - int((time_ns() - start_time) * 10**(-6))) self._send_to_socket({ "type": "init", "data": { "source": source, "stream_mode": stream_mode, "backend": backend, "colorspace": colorspace, "logging": logging, "time_delay": time_delay, **options, } }) def _finalize_read(self, frame): if self._timer_type != _TimerType.WaitingForRead: return self.timer.stop() self._timer_type = _TimerType.NoWaiting self.received_frame.emit(frame) def read(self): """Try to read frame. Result will be emitted within timeout with received_frame pyqtsignal With failed initialization emits None""" self.timer.stop() if self.process.state() != QProcess.ProcessState.Running: self.received_frame.emit(None) return self._timer_type = _TimerType.WaitingForRead self.timer.start(1000) self._send_to_socket({ "type": "read" }) def stop(self): try: self._send_to_socket({ "type": "stop" }) except Exception as err: pass try: self.process.finished.connect(self.deleteLater) self.process.terminate() except Exception as err: pass QTimer.singleShot(20*10**3, lambda: self.process.kill())

camgear_process.py:

from PyQt6.QtCore import pyqtSignal, QCoreApplication from PyQt6.QtNetwork import QLocalServer, QLocalSocket from vidgear.gears import CamGear import pickle class _CamWorker(QLocalServer): client: QLocalSocket | None error = pyqtSignal(str) finished = pyqtSignal() initialization_succeed = pyqtSignal(bool) received_frame = pyqtSignal(object) def __init__(self, name: str, parent=...): super().__init__(parent=parent) self.camgear = None self.client = None self.newConnection.connect(self.set_client) QLocalServer.removeServer(name) if not self.listen(name): print(self.errorString()) self.close() print("listening") def set_client(self): print("set client") self.client = self.nextPendingConnection() if not self.client: print(self.errorString()) self.close() return self.client.disconnected.connect(self.deleteLater) self.client.readyRead.connect(self.handle_message) def send_to_client(self, message: dict): if self.client.writeData(pickle.dumps(message)) == -1: print(self.errorString()) self.close() print("send", message) def handle_message(self): message = pickle.loads(self.client.read(5*10**7)) # max image size in 4K if message["type"] == "init": self.init_cam(message["data"]) if message["type"] == "read": self.read() if message["type"] == "stop": self.stop() def init_cam(self, cam_options): try: self.camgear = CamGear(**cam_options) except Exception as err: print("erro") self.camgear = None self.send_to_client({ "type": "init_success", "data": False }) print(err) return self.set_init_succeded() self.send_to_client({ "type": "init_success", "data": True }) def read(self): """Tries to read the frame ------ Emits frame with received_frame pyqtsignal """ if not self.camgear: # self.received_frame.emit(None) self.send_to_client({ "type": "frame", "data": None }) return try: frame = self.camgear.read() except Exception as err: self.send_to_client({ "type": "frame", "data": None }) print(err) return self.send_to_client({ "type": "frame", "data": frame }) def stop(self): if self.camgear: try: self.camgear.stop() except Exception as err: self.camgear = None print(err) self.close() if __name__ == "__main__": import sys if len(sys.argv) < 2: print("Socket's name weren't set", file=sys.stderr) sys.exit(1) app = QCoreApplication(sys.argv) try: server = _CamWorker(sys.argv[1], parent=None) except Exception as err: print(f"Unable to start the server: {err}") sys.exit(-1) if not server.isListening(): print(f"Unable to start the server: {server.errorString()}") sys.exit(-1) sys.exit(app.exec())

both files are located in /home/user/Documents/project_dir/source/ dir
GUI main.py file is running from /home/user/Documents/project_dir/GUI/

output example:

ThreadedCamGear :: Trying init ThreadedCamGear with source: /home/user/Downloads/video.avi

1 QLocalSocket::connectToServer: Connection refused

Unknown error 234675467654745456456456

: ProcessError.Crashed

output of QProcess, if return in the middle of init_cam() is uncommented:
[Child process stdout] 123532056798672

[Child process stdout] listening

Read Entire Article