Fixed core dumped on close
This commit is contained in:
@@ -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()
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -55,6 +55,7 @@ class DuplicateFileCounter(QThread):
|
||||
|
||||
def stop(self):
|
||||
self._abort = True
|
||||
self.wait() # Add this line
|
||||
|
||||
def run(self):
|
||||
count = 0
|
||||
|
||||
Reference in New Issue
Block a user