Files
neo-pod-desktop/src/device_monitor.py
T
Maksim Totmin a36312c9d7 fix: QThread crash on iPod connect and device detection — proper wait() lifecycle
Root cause: QThread C++ destructor ran while underlying OS thread was
still terminating. The pattern of checking isRunning() and using
short timeouts (wait(3000/5000)) left a window where the Python
reference was dropped and Shiboken destroyed the C++ object before
the thread fully exited.

Fixes:
- Remove all self.finished.connect(self.deleteLater) — unsafe,
  deleteLater from within run() or from worker thread races with
  thread termination
- _cleanup_worker: always call wait() (no timeout) before nulling
  the Python reference — guarantees OS thread is fully dead
- Add _is_worker_running() helper with try/except RuntimeError
  guard to safely check stale C++ objects
- DeviceMonitor._poll/stop/wait: use wait() with no timeout
- closeEvent: guard isRunning() with try/except RuntimeError
2026-06-01 19:25:30 +07:00

71 lines
1.9 KiB
Python

import logging
from typing import Set
from PyQt6.QtCore import QObject, QTimer, QThread, pyqtSignal
from ipod_device import IPodDevice
logger = logging.getLogger(__name__)
class _DetectWorker(QThread):
finished = pyqtSignal(object)
def __init__(self, parent=None):
super().__init__(parent)
def run(self):
try:
devices = IPodDevice().detect_devices()
self.finished.emit(devices)
except Exception:
logger.exception("Device detection failed")
self.finished.emit([])
class DeviceMonitor(QObject):
state_changed = pyqtSignal(list)
def __init__(self, parent=None):
super().__init__(parent)
self.timer = QTimer(self)
self.timer.timeout.connect(self._poll)
self._device_ids: Set[str] = set()
self._poll_worker = None
def start(self, interval_ms: int = 3000):
self._device_ids.clear()
self._poll()
self.timer.start(interval_ms)
def stop(self):
self.timer.stop()
if self._poll_worker:
self._poll_worker.quit()
self._poll_worker.wait()
self._poll_worker = None
self._device_ids.clear()
def _poll(self):
if self._poll_worker:
try:
if self._poll_worker.isRunning():
return
except RuntimeError:
self._poll_worker = None
worker = _DetectWorker()
worker.finished.connect(self._on_devices_detected)
worker.start()
self._poll_worker = worker
def _on_devices_detected(self, devices):
if self._poll_worker:
self._poll_worker.quit()
self._poll_worker.wait()
self._poll_worker = None
current_ids = {d["id"] for d in devices}
if current_ids != self._device_ids:
self._device_ids = current_ids
self.state_changed.emit(devices)