commit 4d6b7476
Documented all functions that runs in threads
Changed files
| M | src/imagemodel.cpp before |
| M | src/imageviewwidget.cpp before |
| M | src/mainwindow.cpp before |
| M | src/thumbnailcache.cpp before |
| M | src/thumbnailworker.cpp before |
diff --git a/src/imagemodel.cpp b/src/imagemodel.cpp
index 27f2401..2f2f702 100644
--- a/src/imagemodel.cpp
+++ b/src/imagemodel.cpp
@@ -240,6 +240,9 @@ void ImageModel::queueRow(int row)
m_thumbnailSize,
&m_cancelFlag);
+ // THREADING: Explicit QueuedConnection ensures thumbnailReady() is invoked
+ // on the main thread even though the signal is emitted from a pool thread.
+ // This means all m_items[] mutations happen on the main thread only.
connect(worker, &ThumbnailWorker::finished,
this, &ImageModel::thumbnailReady,
Qt::QueuedConnection);
diff --git a/src/imageviewwidget.cpp b/src/imageviewwidget.cpp
index 0ee4d4e..5e83fab 100644
--- a/src/imageviewwidget.cpp
+++ b/src/imageviewwidget.cpp
@@ -19,6 +19,12 @@ ImageLoadWorker::ImageLoadWorker(const QByteArray &path, QObject *parent)
{
}
+// THREADING: This function runs on its own QThread, NOT the main thread.
+// It must not access any ImageViewWidget member state. The only data it uses
+// is m_path which is immutable after construction. The emitted imageLoaded()
+// signal is delivered to the main thread via Qt::AutoConnection which resolves
+// to QueuedConnection at emit time (worker thread != receiver's main-thread
+// affinity), so onImageLoaded() always executes on the main thread.
void ImageLoadWorker::run()
{
int fd = ::open(m_path.constData(), O_RDONLY | O_CLOEXEC);
@@ -133,6 +139,10 @@ void ImageViewWidget::prefetchImage(const QByteArray &path)
m_pendingLoads.insert(path);
auto *worker = new ImageLoadWorker(path, this);
+ // THREADING: Qt::AutoConnection (default) resolves to QueuedConnection at
+ // emit time because ImageLoadWorker::run() emits from its worker thread
+ // while 'this' (ImageViewWidget) has main-thread affinity. This guarantees
+ // onImageLoaded() always runs on the main thread — no mutex needed.
connect(worker, &ImageLoadWorker::imageLoaded, this, &ImageViewWidget::onImageLoaded);
connect(worker, &QThread::finished, worker, &QObject::deleteLater);
worker->start();
diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp
index 0fd7ed4..be58306 100644
--- a/src/mainwindow.cpp
+++ b/src/mainwindow.cpp
@@ -580,6 +580,13 @@ void MainWindow::setFullScreenMode(bool fullScreen)
void MainWindow::keyPressEvent(QKeyEvent *event)
{
+ // THREADING: No race condition with F11 fullscreen toggle. All cross-thread
+ // signal deliveries (thumbnailReady via explicit QueuedConnection,
+ // onImageLoaded via AutoConnection resolving to QueuedConnection) execute on
+ // the main thread's event loop. The event loop processes events one at a
+ // time, so this handler and any signal slot can never interleave. All widget
+ // state (m_wasMaximized, m_stack, fullscreen flag) is only accessed from the
+ // main thread.
if (event->key() == Qt::Key_F11 && m_stack->currentWidget() == m_imageView)
{
setFullScreenMode(!isFullScreen());
diff --git a/src/thumbnailcache.cpp b/src/thumbnailcache.cpp
index 8f6ad00..f5df06e 100644
--- a/src/thumbnailcache.cpp
+++ b/src/thumbnailcache.cpp
@@ -59,6 +59,9 @@ bool ThumbnailCache::isInsideCache(const QByteArray &absPath)
return absPath.startsWith(root + '/') || absPath == root;
}
+// THREADING: Called from worker threads (ThumbnailWorker::run()). This
+// function is free of shared mutable state — it only performs filesystem I/O
+// using its arguments. Safe to call concurrently from multiple threads.
QImage ThumbnailCache::load(const QByteArray &sourcePath, Size size)
{
struct ::stat st{};
@@ -81,6 +84,10 @@ QImage ThumbnailCache::load(const QByteArray &sourcePath, Size size)
return img;
}
+// THREADING: Called from worker threads (ThumbnailWorker::run()). This
+// function is free of shared mutable state — it only performs filesystem I/O
+// using its arguments. Writes are atomic (temp file + rename) so concurrent
+// calls for different source files are safe.
void ThumbnailCache::save(const QByteArray &sourcePath, Size size, const QImage &thumbImage)
{
if (thumbImage.isNull())
diff --git a/src/thumbnailworker.cpp b/src/thumbnailworker.cpp
index 3e1024f..2eedea3 100644
--- a/src/thumbnailworker.cpp
+++ b/src/thumbnailworker.cpp
@@ -12,6 +12,7 @@ namespace
{
constexpr int kDisplaySize = 128;
+// THREADING: Pure function called from a worker thread. No shared mutable state.
QImage downscaleForDisplay(const QImage &src)
{
if (src.isNull())
@@ -36,6 +37,12 @@ ThumbnailWorker::ThumbnailWorker(const QByteArray &path,
setAutoDelete(true);
}
+// THREADING: This function runs on a QThreadPool worker thread, NOT the main
+// thread. It must not access any ImageModel state. The only shared data it
+// touches is the atomic m_cancelFlag (for cooperative cancellation). All other
+// members (m_path, m_row, m_size) are immutable after construction. Results
+// are delivered to the main thread via the finished() signal connected with
+// Qt::QueuedConnection.
void ThumbnailWorker::run()
{
if (*m_cancelFlag)