Fixed core dumped on close

This commit is contained in:
Ignacio Serantes
2026-04-01 08:48:06 +02:00
parent 2fbf04fdb8
commit ae00235db8
6 changed files with 52 additions and 8 deletions

View File

@@ -34,7 +34,7 @@ from itertools import groupby
from PySide6.QtWidgets import (
QApplication, QMainWindow, QWidget, QLabel, QVBoxLayout, QHBoxLayout, QLineEdit,
QTextEdit, QPushButton, QFileDialog, QComboBox, QSlider, QMessageBox, QSizePolicy,
QMenu, QInputDialog, QTableWidget, QTableWidgetItem, QHeaderView, QTabWidget,
QMenu, QInputDialog, QTableWidget, QTableWidgetItem, QHeaderView, QTabWidget, QProgressDialog,
QDockWidget, QAbstractItemView, QRadioButton, QButtonGroup, QListView,
QStyledItemDelegate, QStyle, QDialog, QKeySequenceEdit, QDialogButtonBox
)
@@ -2010,6 +2010,9 @@ class MainWindow(QMainWindow):
def perform_shutdown(self):
"""Performs cleanup operations before the application closes."""
if getattr(self, '_is_shutting_down', False):
return
self._is_shutting_down = True
self.is_cleaning = True
# Save configuration early if visible, as per user request.
@@ -2017,6 +2020,24 @@ class MainWindow(QMainWindow):
if self.isVisible():
self.save_config()
# Stop all pending UI timers to prevent background tasks from triggering
self.hide_progress_timer.stop()
self.filter_input_timer.stop()
self.filter_refresh_timer.stop()
self.thumbnails_refresh_timer.stop()
self.rebuild_timer.stop()
self._model_update_timer.stop()
self.resume_scan_timer.stop()
# Close all viewers first to stop their preloader threads
self.close_all_viewers()
# Close duplicate manager if open to stop its preloader threads
if HAVE_IMAGEHASH:
for widget in QApplication.topLevelWidgets():
if isinstance(widget, DuplicateManagerDialog):
widget.close()
self.fs_watcher.stop()
# 1. Stop all worker threads interacting with the cache
@@ -2053,7 +2074,19 @@ class MainWindow(QMainWindow):
# Ensure all QRunnables in the shared thread pool are finished
if self.thread_pool_manager:
self.thread_pool_manager.get_pool().waitForDone()
pool = self.thread_pool_manager.get_pool()
if pool.activeThreadCount() > 0:
progress = QProgressDialog(UITexts.SHUTTING_DOWN, None, 0, 0, self)
progress.setWindowTitle(PROG_NAME)
progress.setWindowModality(Qt.ApplicationModal)
progress.setMinimumDuration(0)
progress.show()
# Wait in small increments to keep the UI responsive
while not pool.waitForDone(100):
if QApplication.instance():
QApplication.processEvents()
progress.close()
if self.duplicate_cache:
self.duplicate_cache.lmdb_close()

View File

@@ -571,6 +571,7 @@ class DuplicateDetector(QThread):
def stop(self):
self._is_running = False
self.wait() # Add this line
def run(self):
total_files = len(self.paths_to_scan)

View File

@@ -143,6 +143,11 @@ class DuplicateManagerDialog(QDialog):
self.main_win.fs_watcher.file_moved.disconnect(self._on_file_moved_externally)
except (RuntimeError, TypeError):
pass
if hasattr(self, 'left_pane') and self.left_pane:
self.left_pane.cleanup()
if hasattr(self, 'right_pane') and self.right_pane:
self.right_pane.cleanup()
super().closeEvent(event)
def resizeEvent(self, event):

View File

@@ -42,6 +42,7 @@ class ImagePreloader(QThread):
def __init__(self):
"""Initializes the preloader thread."""
super().__init__()
self.setObjectName("ImagePreloaderThread")
self.path = None
self.index = -1
self.mutex = QMutex()

View File

@@ -134,13 +134,11 @@ class ScannerWorker(QRunnable):
sizes_to_check = self.target_sizes if self.target_sizes is not None \
else SCANNER_GENERATE_SIZES
if self._is_cancelled:
if self.semaphore:
self.semaphore.release()
return
fd = None
try:
if self._is_cancelled:
return
fd = None
# Optimize: Open file once to reuse FD for stat and xattrs
fd = os.open(self.path, os.O_RDONLY)
stat_res = os.fstat(fd)
@@ -285,6 +283,7 @@ class CacheWriter(QThread):
self._condition_new_data = QWaitCondition()
self._condition_space_available = QWaitCondition()
# Soft limit for blocking producers (background threads)
self.setObjectName("CacheWriterThread") # Add this line
self._max_size = 50
self._running = True
@@ -334,6 +333,7 @@ class CacheWriter(QThread):
self._running = False
# Do not clear the queue here; let the run loop drain it to prevent data loss.
self._condition_new_data.wakeAll()
logger.debug(f"{self.objectName()} stop requested, waking all.")
self._condition_space_available.wakeAll()
self._mutex.unlock()
@@ -380,6 +380,7 @@ class CacheWriter(QThread):
self.cache._batch_write_to_lmdb(batch)
except Exception as e:
logger.error(f"CacheWriter batch write error: {e}")
logger.debug(f"{self.objectName()} run method exiting.")
class CacheLoader(QThread):
@@ -1328,6 +1329,7 @@ class CacheCleaner(QThread):
def stop(self):
"""Signals the thread to stop."""
self._is_running = False
self.wait()
def run(self):
self.setPriority(QThread.IdlePriority)
@@ -1914,3 +1916,4 @@ class ImageScanner(QThread):
self.mutex.lock()
self.condition.wakeAll()
self.mutex.unlock()
self.wait()

View File

@@ -55,6 +55,7 @@ class DuplicateFileCounter(QThread):
def stop(self):
self._abort = True
self.wait() # Add this line
def run(self):
count = 0