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
This commit is contained in:
Maksim Totmin
2026-06-01 19:25:30 +07:00
parent dd93440146
commit a36312c9d7
5 changed files with 63 additions and 24 deletions
+13 -5
View File
@@ -957,7 +957,7 @@ class LibraryTab(QWidget):
)
return
if self.worker_thread and self.worker_thread.isRunning():
if self._is_worker_running():
QMessageBox.information(self, "Info", "Please wait for the current task to finish")
return
@@ -1025,7 +1025,7 @@ class LibraryTab(QWidget):
QMessageBox.warning(self, "Error", "No iPod device mounted. Go to iPod tab and mount first.")
return
if self.worker_thread and self.worker_thread.isRunning():
if self._is_worker_running():
QMessageBox.information(self, "Info", "Please wait for the current task to finish")
return
@@ -1070,7 +1070,7 @@ class LibraryTab(QWidget):
QMessageBox.warning(self, "Error", "No iPod device mounted.")
return
if self.worker_thread and self.worker_thread.isRunning():
if self._is_worker_running():
QMessageBox.information(self, "Info", "Please wait for the current task to finish")
return
@@ -1140,10 +1140,18 @@ class LibraryTab(QWidget):
def _cleanup_worker(self):
if self.worker_thread:
self.worker_thread.wait(3000)
self.worker_thread.deleteLater()
self.worker_thread.quit()
self.worker_thread.wait()
self.worker_thread = None
def _is_worker_running(self):
if self.worker_thread:
try:
return self.worker_thread.isRunning()
except RuntimeError:
self.worker_thread = None
return False
def on_device_mounted(self, mount_point: str):
self._current_mount_point = mount_point
self.library_progress.setVisible(True)