Skip to content

Thumbnails #1262

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 37 commits into from
Jun 1, 2025
Merged

Thumbnails #1262

merged 37 commits into from
Jun 1, 2025

Conversation

leejuyuu
Copy link
Collaborator

@leejuyuu leejuyuu commented Feb 2, 2025

No description provided.

@novomesk
Copy link
Collaborator

Please rebase the branch so we have fresh Windows build on appveyor.

Copy link
Collaborator

@scrubbbbs scrubbbbs left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a lot slower for me (reading from NAS which tops out around 300MB/s). Which is not very fast by today's standards (nvme etc well over 1GB/s).

I'm not sure what to do about this but I think there needs to be a multi-threaded option. Maybe we can scale the number of threads based on how much time is spent in I/O vs decoding/scaling.

If the computed thumbs were cached somewhere then maybe we don't need multi-threading.

@leejuyuu
Copy link
Collaborator Author

@novomesk This branch has been rebased.

@scrubbbbs Did you observe slow down when loading files without EXIF thumbnail, or was it slow even for those with EXIF thumbnail?

@scrubbbbs
Copy link
Collaborator

@scrubbbbs Did you observe slow down when loading files without EXIF thumbnail, or was it slow even for those with EXIF thumbnail?

It is quite a bit slower in both cases, of course not as much for EXIF since it can load much faster. However much more noticeable when loading thousands of files in the grid view.

I've just noticed the embedded thumb is also not transformed correctly anymore, orientation "2" is not flipped anymore. Easy to see in formats_testset/orientations/with-thumb

@leejuyuu
Copy link
Collaborator Author

It is quite a bit slower in both cases, of course not as much for EXIF since it can load much faster. However much more noticeable when loading thousands of files in the grid view.

Do you mean scrolling down the grid view? I think it should only load what is currently visible, which is ~50 files for my screen. It's strange that EXIF thumbnails loads almost instantly for me (on NVME SSD), so they should not be compute bound...

I've just noticed the embedded thumb is also not transformed correctly anymore, orientation "2" is not flipped anymore. Easy to see in formats_testset/orientations/with-thumb

Thanks! I've fixed that.

@scrubbbbs
Copy link
Collaborator

scrubbbbs commented Mar 16, 2025

Scrolling is where this is most apparent, resorting is not too bad.

I prepared a couple of clips to show the difference. This folder has about 5000 files, they are small jpgs, most have exif thumbs but many do not. I tried to scroll both at the same rate.

Clip 1 new version]

thumbs.mp4

Clip 2 git master

thumbs-threads.mp4

If the grid view could reschedule items when scrolling, the difference would be a lot less. Maybe after the view scrolls we set a timer to reschedule provided there is not another scroll in the meantime?

I think I would prefer not reordering EXIF vs computed thumbs, just seems weird having gaps in the thumbs that fill in later. I guess I don't understand the benefit of rescheduling based on missing EXIF thumbs or not.

@novomesk
Copy link
Collaborator

I sorry but I haven't seen the videos.

@scrubbbbs
Copy link
Collaborator

I sorry but I haven't seen the videos.

Thanks, reencoded to fit the github limit now.

@novomesk
Copy link
Collaborator

I seems to me from the videos, that no-longer visible thumbnails are still being loaded before those active on the screen.

@scrubbbbs
Copy link
Collaborator

I seems to me from the videos, that no-longer visible thumbnails are still being loaded before those active on the screen.

This is legacy behavior AFAIK and yes I agree it is probably most of the apparent slowdown. I would suggest fixing this first as it will have a bigger impact.

However, this clearly shows that loading thumbnails is not always going to be I/O bound as @leejuyuu has asserted here with a single-threaded implementation. Given it the difference is around 2x in this testcase, multi-threaded thumbnail support should probably be retained.

@leejuyuu
Copy link
Collaborator Author

leejuyuu commented Apr 3, 2025

@scrubbbbs @novomesk I added some concurrency back to address the compute bound part, and the loading is now cancelable for the no longer visible thumbnails. The scrolling experience should be better now.

@scrubbbbs scrubbbbs self-requested a review April 3, 2025 23:42
@scrubbbbs
Copy link
Collaborator

Thanks for the update. The speed seems about the same as previously and the cancelling is a huge improvement. I haven't finished a code review but I did find a segfault when using DkFilePreview and also noticed that DkFilePreview is no longer working for zip files, it no longer scrolls and remains at its initial state with the first few thumbs loaded.

The crash is a bit difficult to reproduce. I am unlocking my scroll wheel and changing scroll directions a lot on small image files so there are perhaps a 100 of images per second going by. It took me quite a long time of doing this and loading different folders etc to reproduce so I only have this one trace for now, see attachment (the first time I had no debugger attached).

That's as far as I've gotten, and one interesting thing is that the image being painted seems to be valid (at least Qtcreator debugger displays it .. it has this cool feature that shows all QImages in local variables). So a use-after-free doesn't make much sense unless the debugger can read invalid memory locations.

trace.txt

@scrubbbbs
Copy link
Collaborator

valgrind --tool=helgrind ./nomacs found this in the same region as that trace.

==409938== ----------------------------------------------------------------
==409938== 
==409938== Possible data race during read of size 8 at 0x986F0470 by thread #1
==409938== Locks held: none
==409938==    at 0x5C873D0: ??? (in /usr/lib/x86_64-linux-gnu/libQt5Gui.so.5.15.13)
==409938==    by 0x5CBC64E: ??? (in /usr/lib/x86_64-linux-gnu/libQt5Gui.so.5.15.13)
==409938==    by 0x5D03A15: ??? (in /usr/lib/x86_64-linux-gnu/libQt5Gui.so.5.15.13)
==409938==    by 0x5D248CF: QPainter::drawImage(QRectF const&, QImage const&, QRectF const&, QFlags<Qt::ImageConversionFlag>) (in /usr/lib/x86_64-linux-gnu/libQt5Gui.so.5.15.13)
==409938==    by 0x5A7A814: QImage::setAlphaChannel(QImage const&) (in /usr/lib/x86_64-linux-gnu/libQt5Gui.so.5.15.13)
==409938==    by 0x4C6B210: nmc::DkFilePreview::drawFadeOut(QLinearGradient, QRectF, QImage*) (DkThumbsWidgets.cpp:486)
==409938==    by 0x4C6A19B: nmc::DkFilePreview::drawThumbs(QPainter*) (DkThumbsWidgets.cpp:380)
==409938==    by 0x4C68F36: nmc::DkFilePreview::paintEvent(QPaintEvent*) (DkThumbsWidgets.cpp:281)
==409938==    by 0x53CDDF7: QWidget::event(QEvent*) (in /usr/lib/x86_64-linux-gnu/libQt5Widgets.so.5.15.13)
==409938==    by 0x5388D44: QApplicationPrivate::notify_helper(QObject*, QEvent*) (in /usr/lib/x86_64-linux-gnu/libQt5Widgets.so.5.15.13)
==409938==    by 0x62AA117: QCoreApplication::notifyInternal2(QObject*, QEvent*) (in /usr/lib/x86_64-linux-gnu/libQt5Core.so.5.15.13)
==409938==    by 0x53C5AED: QWidgetPrivate::sendPaintEvent(QRegion const&) (in /usr/lib/x86_64-linux-gnu/libQt5Widgets.so.5.15.13)
==409938== 
==409938== This conflicts with a previous write of size 8 by thread #72
==409938== Locks held: none
==409938==    at 0x5C896CC: ??? (in /usr/lib/x86_64-linux-gnu/libQt5Gui.so.5.15.13)
==409938==    by 0x5A84279: ??? (in /usr/lib/x86_64-linux-gnu/libQt5Gui.so.5.15.13)
==409938==    by 0x5A8719F: ??? (in /usr/lib/x86_64-linux-gnu/libQt5Gui.so.5.15.13)
==409938==    by 0x60B0AB8: ??? (in /usr/lib/x86_64-linux-gnu/libQt5Core.so.5.15.13)
==409938==    by 0x60AD673: ??? (in /usr/lib/x86_64-linux-gnu/libQt5Core.so.5.15.13)
==409938==    by 0x4854B7A: ??? (in /usr/libexec/valgrind/vgpreload_helgrind-amd64-linux.so)
==409938==    by 0x660FAA3: start_thread (pthread_create.c:447)
==409938==    by 0x669CA33: clone (clone.S:100)
==409938==  Address 0x986f0470 is 0 bytes inside a block of size 36,352 alloc'd
==409938==    at 0x48488A8: malloc (in /usr/libexec/valgrind/vgpreload_helgrind-amd64-linux.so)
==409938==    by 0x5A75BCD: QImageData::create(QSize const&, QImage::Format) (in /usr/lib/x86_64-linux-gnu/libQt5Gui.so.5.15.13)
==409938==    by 0x5A75DB7: QImage::QImage(QSize const&, QImage::Format) (in /usr/lib/x86_64-linux-gnu/libQt5Gui.so.5.15.13)
==409938==    by 0x5A75DFD: QImage::QImage(int, int, QImage::Format) (in /usr/lib/x86_64-linux-gnu/libQt5Gui.so.5.15.13)
==409938==    by 0x5A79778: QImage::convertToFormat_helper(QImage::Format, QFlags<Qt::ImageConversionFlag>) const (in /usr/lib/x86_64-linux-gnu/libQt5Gui.so.5.15.13)
==409938==    by 0x5A7A715: QImage::setAlphaChannel(QImage const&) (in /usr/lib/x86_64-linux-gnu/libQt5Gui.so.5.15.13)
==409938==    by 0x4C6B210: nmc::DkFilePreview::drawFadeOut(QLinearGradient, QRectF, QImage*) (DkThumbsWidgets.cpp:486)
==409938==    by 0x4C6A19B: nmc::DkFilePreview::drawThumbs(QPainter*) (DkThumbsWidgets.cpp:380)
==409938==    by 0x4C68F36: nmc::DkFilePreview::paintEvent(QPaintEvent*) (DkThumbsWidgets.cpp:281)
==409938==    by 0x53CDDF7: QWidget::event(QEvent*) (in /usr/lib/x86_64-linux-gnu/libQt5Widgets.so.5.15.13)
==409938==    by 0x5388D44: QApplicationPrivate::notify_helper(QObject*, QEvent*) (in /usr/lib/x86_64-linux-gnu/libQt5Widgets.so.5.15.13)
==409938==    by 0x62AA117: QCoreApplication::notifyInternal2(QObject*, QEvent*) (in /usr/lib/x86_64-linux-gnu/libQt5Core.so.5.15.13)
==409938==  Block was alloc'd by thread #1
==409938== 
==409938== ----------------------------------------------------------------
==409938== 
==409938== Possible data race during write of size 8 at 0x880AA3F8 by thread #1
==409938== Locks held: none
==409938==    at 0x5A7596E: QImageData::~QImageData() (in /usr/lib/x86_64-linux-gnu/libQt5Gui.so.5.15.13)
==409938==    by 0x5A75E5B: QImage::~QImage() (in /usr/lib/x86_64-linux-gnu/libQt5Gui.so.5.15.13)
==409938==    by 0x5A7A824: QImage::setAlphaChannel(QImage const&) (in /usr/lib/x86_64-linux-gnu/libQt5Gui.so.5.15.13)
==409938==    by 0x4C6B210: nmc::DkFilePreview::drawFadeOut(QLinearGradient, QRectF, QImage*) (DkThumbsWidgets.cpp:486)
==409938==    by 0x4C6A19B: nmc::DkFilePreview::drawThumbs(QPainter*) (DkThumbsWidgets.cpp:380)
==409938==    by 0x4C68F36: nmc::DkFilePreview::paintEvent(QPaintEvent*) (DkThumbsWidgets.cpp:281)
==409938==    by 0x53CDDF7: QWidget::event(QEvent*) (in /usr/lib/x86_64-linux-gnu/libQt5Widgets.so.5.15.13)
==409938==    by 0x5388D44: QApplicationPrivate::notify_helper(QObject*, QEvent*) (in /usr/lib/x86_64-linux-gnu/libQt5Widgets.so.5.15.13)
==409938==    by 0x62AA117: QCoreApplication::notifyInternal2(QObject*, QEvent*) (in /usr/lib/x86_64-linux-gnu/libQt5Core.so.5.15.13)
==409938==    by 0x53C5AED: QWidgetPrivate::sendPaintEvent(QRegion const&) (in /usr/lib/x86_64-linux-gnu/libQt5Widgets.so.5.15.13)
==409938==    by 0x53C64A8: QWidgetPrivate::drawWidget(QPaintDevice*, QRegion const&, QPoint const&, QFlags<QWidgetPrivate::DrawWidgetFlag>, QPainter*, QWidgetRepaintManager*) (in /usr/lib/x86_64-linux-gnu/libQt5Widgets.so.5.15.13)
==409938==    by 0x53CAE75: QWidgetPrivate::render(QPaintDevice*, QPoint const&, QRegion const&, QFlags<QWidget::RenderFlag>) (in /usr/lib/x86_64-linux-gnu/libQt5Widgets.so.5.15.13)
==409938== 
==409938== This conflicts with a previous read of size 8 by thread #72
==409938== Locks held: none
==409938==    at 0x5A841B0: ??? (in /usr/lib/x86_64-linux-gnu/libQt5Gui.so.5.15.13)
==409938==    by 0x5A8719F: ??? (in /usr/lib/x86_64-linux-gnu/libQt5Gui.so.5.15.13)
==409938==    by 0x60B0AB8: ??? (in /usr/lib/x86_64-linux-gnu/libQt5Core.so.5.15.13)
==409938==    by 0x60AD673: ??? (in /usr/lib/x86_64-linux-gnu/libQt5Core.so.5.15.13)
==409938==    by 0x4854B7A: ??? (in /usr/libexec/valgrind/vgpreload_helgrind-amd64-linux.so)
==409938==    by 0x660FAA3: start_thread (pthread_create.c:447)
==409938==    by 0x669CA33: clone (clone.S:100)
==409938==  Address 0x880aa3f8 is 40 bytes inside a block of size 144 alloc'd
==409938==    at 0x4849023: operator new(unsigned long) (in /usr/libexec/valgrind/vgpreload_helgrind-amd64-linux.so)
==409938==    by 0x5A75B8F: QImageData::create(QSize const&, QImage::Format) (in /usr/lib/x86_64-linux-gnu/libQt5Gui.so.5.15.13)
==409938==    by 0x5A75DB7: QImage::QImage(QSize const&, QImage::Format) (in /usr/lib/x86_64-linux-gnu/libQt5Gui.so.5.15.13)
==409938==    by 0x5A75DFD: QImage::QImage(int, int, QImage::Format) (in /usr/lib/x86_64-linux-gnu/libQt5Gui.so.5.15.13)
==409938==    by 0x5A79778: QImage::convertToFormat_helper(QImage::Format, QFlags<Qt::ImageConversionFlag>) const (in /usr/lib/x86_64-linux-gnu/libQt5Gui.so.5.15.13)
==409938==    by 0x5A7A715: QImage::setAlphaChannel(QImage const&) (in /usr/lib/x86_64-linux-gnu/libQt5Gui.so.5.15.13)
==409938==    by 0x4C6B210: nmc::DkFilePreview::drawFadeOut(QLinearGradient, QRectF, QImage*) (DkThumbsWidgets.cpp:486)
==409938==    by 0x4C6A19B: nmc::DkFilePreview::drawThumbs(QPainter*) (DkThumbsWidgets.cpp:380)
==409938==    by 0x4C68F36: nmc::DkFilePreview::paintEvent(QPaintEvent*) (DkThumbsWidgets.cpp:281)
==409938==    by 0x53CDDF7: QWidget::event(QEvent*) (in /usr/lib/x86_64-linux-gnu/libQt5Widgets.so.5.15.13)
==409938==    by 0x5388D44: QApplicationPrivate::notify_helper(QObject*, QEvent*) (in /usr/lib/x86_64-linux-gnu/libQt5Widgets.so.5.15.13)
==409938==    by 0x62AA117: QCoreApplication::notifyInternal2(QObject*, QEvent*) (in /usr/lib/x86_64-linux-gnu/libQt5Core.so.5.15.13)
==409938==  Block was alloc'd by thread #1

@leejuyuu
Copy link
Collaborator Author

leejuyuu commented Apr 4, 2025

It's weird. It seems that DkFilePreview::paintEvent is somehow called in different threads? I thought it is only used in main thread. Which platform did you use? Is this specific to Qt5?

@scrubbbbs
Copy link
Collaborator

This patch seems to fix the segfault, but I can't explain why. Also it skips an extra conversion (rgba->gray8) in setAlphaChannel .. which is also where this has been faulting.

--- ImageLounge/src/DkGui/DkThumbsWidgets.cpp
+++ ImageLounge/src/DkGui/DkThumbsWidgets.cpp
@@ -477,8 +477,10 @@ void DkFilePreview::drawFadeOut(QLinearGradient gradient, QRectF imgRect, QImage
 
-    QImage mask = *img;
-    QPainter painter(&mask);
-    painter.fillRect(img->rect(), Qt::black);
-    painter.fillRect(img->rect(), imgGradient);
-    painter.end();
-
+    // setAlphaChannel converts the mask to 8-bit gray otherwise
+    QImage mask(img->size(), QImage::Format_Grayscale8);
+    {
+        QPainter painter(&mask);
+        painter.fillRect(img->rect(), Qt::black);
+        painter.fillRect(img->rect(), imgGradient);
+    }
+    // converts image to rgba
     img->setAlphaChannel(mask);

@scrubbbbs
Copy link
Collaborator

It's weird. It seems that DkFilePreview::paintEvent is somehow called in different threads? I thought it is only used in main thread. Which platform did you use? Is this specific to Qt5?

It's not the paintEvent that was called in multiple threads, notice the "Block alloc'd by thread 1" at the end. The weird part is that is says the allocation happened (according to helgrind) in thread 1, and that makes no sense since we know it is allocated in the worker thread.

However, with the patch above, the helgrind message goes away, so something must be up.

This is not specific to Qt5/6.

leejuyuu added 8 commits May 25, 2025 00:04
- Pass references
- Return modified image instead of modifying pointer
Fix DkFilePreview current index not found when viewing zip files. Images
inside zip files originally have encoded file path. However,
`DkImageContainerT::checkForFileUpdates` later replace the file path
with the path inside the archive, causing the path comparison to always
fail.
DkThumbLabel is also affected.

Record a copy of the original path that is not mutated inside
DkImageContainer to fix this.
Check if the thumbnail load was failed before requesting to avoid
infinite loop.
These booleans control whether the thumbnail is to be requested. This
fixes a weird bug that some thumbnails are never loaded and the gray
boxes in the DkThumbScrollWidget.
- Remove unused members
- Make constant members static constexpr
- Reorder member variables by size
- Inline the weirdly-named setThumb()
The original implementation did not scale full image thumbnail from
dispatchFullImage to prevent blocking the main thread. Add another queue
so these images can be scaled asynchronously.
Add a cache in DkThumbLoader, so the thumbnail consumers do not need to
store a copy of thumbnail anymore. Also, toggling the thumbnail preview
widget does not necessary trigger a reload.
- Create pixmap for drawing on receiving the thumbnail. Cache them for
  simple bounded memory management.
- Reduce the lambdas to save more memory.
- Regroup the updates on receiving a thumbnail. This allows us to store
  less data.
- Cache the tooltip so there is no repeated QFileInfo creation.
@leejuyuu
Copy link
Collaborator Author

Repeat this several times and on step 2 , one of these bugs occurs

* thumbs are not drawn at all (blank area)

* segfault

This crash is due to use after free. I've dropped 0261212. Should no longer be crashing.

@leejuyuu
Copy link
Collaborator Author

@scrubbbbs Sorry for the late response. I've added a cache layer so that disk i/o could be reduced at the expense of slightly more memory usage. However, the memory consumption of DkThumbScene should now be capped by the cache size, instead of growing linearly by the number of thumbnails.

One thing that's left is that the Pixmap cache could be used in DkFilePreview to reduce memory and increase performance. However, this seems not necessary for this PR to work and could be left as future work.

Please test and see if you have further questions. Thanks!

@leejuyuu
Copy link
Collaborator Author

Hi @novomesk , @scrubbbbs , if there are no further issues, I plan to merge this during this weekend.

Copy link
Collaborator

@scrubbbbs scrubbbbs left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Everything looks good to me. Thanks!

Copy link
Collaborator

@scrubbbbs scrubbbbs left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems we still have a segfault. It happened after clicking a few folders in explorer, then it crashed when double-clicking on a thumbnail.

Still trying to find a sequence to trigger it. But here is one trace from Qt5 version, release build.

Update: I seem to be completely unable to reproduce this at the moment. I guess we need some theory as to what's happening.

[INFO] [Thumbnail] /home/test/mnt/hd4/tmp/imbdb-wiki/imdb400/imdb/05/nm0001505_rm399476736_1947-11-13_2005.jpg exif=yes size=107x158 38ms
Warning: Unsupported date format
Warning: Unsupported time format
Warning: Unsupported date format
Warning: Unsupported date format
[INFO] [Thumbnail] /home/test/mnt/hd4/tmp/imbdb-wiki/imdb400/imdb/05/nm0001505_rm1223527168_1947-11-13_2005.jpg exif=yes size=160x105 16ms
Warning: Unsupported date format
Warning: Unsupported time format
Warning: Unsupported date format
Warning: Unsupported date format
[INFO] [Thumbnail] /home/test/mnt/hd4/tmp/imbdb-wiki/imdb400/imdb/05/nm0001505_rm1257081600_1947-11-13_2005.jpg exif=yes size=160x114 18ms
[INFO] [DkImageLoader] 5673 containers created in 269 ms
[INFO] [DkImageLoader] after sorting:  1944 ms

Thread 1 "nomacs" received signal SIGSEGV, Segmentation fault.
0x00007ffff63e385d in QDir::fromNativeSeparators(QString const&) () from /lib/x86_64-linux-gnu/libQt5Core.so.5
(gdb) bt
#0  0x00007ffff63e385d in QDir::fromNativeSeparators(QString const&) () at /lib/x86_64-linux-gnu/libQt5Core.so.5
#1  0x00007ffff63f834c in QFileInfo::QFileInfo(QString const&) () at /lib/x86_64-linux-gnu/libQt5Core.so.5
#2  0x00007ffff7e22103 in nmc::DkImageLoader::load (this=0x555555c1bd70, filePath=...)
    at /home/test/sw/nomacs/ImageLounge/src/DkCore/DkImageLoader.cpp:862
#3  0x00007ffff6512e16 in ??? () at /lib/x86_64-linux-gnu/libQt5Core.so.5
#4  0x00007ffff7cb1aa7 in nmc::DkThumbScene::loadFileSignal (this=<optimized out>, _t1=<optimized out>, _t2=<optimized out>)
    at /home/test/sw/nomacs-testing/build/nomacsCore_autogen/XJ2BUHI2UY/moc_DkThumbsWidgets.cpp:610
#5  0x00007ffff6512e16 in ??? () at /lib/x86_64-linux-gnu/libQt5Core.so.5
#6  0x00007ffff7cb19d7 in nmc::DkThumbLabel::loadFileSignal (this=<optimized out>, _t1=<optimized out>, _t2=<optimized out>)
    at /home/test/sw/nomacs-testing/build/nomacsCore_autogen/XJ2BUHI2UY/moc_DkThumbsWidgets.cpp:365
#7  0x00007ffff749ea86 in QGraphicsItem::sceneEvent(QEvent*) () at /lib/x86_64-linux-gnu/libQt5Widgets.so.5
#8  0x00007ffff74c2a99 in ??? () at /lib/x86_64-linux-gnu/libQt5Widgets.so.5
#9  0x00007ffff74cb67f in ??? () at /lib/x86_64-linux-gnu/libQt5Widgets.so.5
#10 0x00007ffff74d5903 in QGraphicsScene::event(QEvent*) () at /lib/x86_64-linux-gnu/libQt5Widgets.so.5
#11 0x00007ffff716bd45 in QApplicationPrivate::notify_helper(QObject*, QEvent*) () at /lib/x86_64-linux-gnu/libQt5Widgets.so.5
#12 0x00007ffff64d8118 in QCoreApplication::notifyInternal2(QObject*, QEvent*) () at /lib/x86_64-linux-gnu/libQt5Core.so.5
#13 0x00007ffff74f395f in QGraphicsView::mouseDoubleClickEvent(QMouseEvent*) () at /lib/x86_64-linux-gnu/libQt5Widgets.so.5

@leejuyuu
Copy link
Collaborator Author

I cannot reproduce this. Which Qt5 version are you using?

@scrubbbbs
Copy link
Collaborator

I cannot reproduce this. Which Qt5 version are you using?

The previous trace (I have yet to produce another one) was:
gcc 14.2
build type: RelWithDebInfo

7b53202, 2025-05-25, custom
Ubuntu 24.04.2 LTS, 32GB
Qt 5.15.13, xcb, scale=1.00
Exiv2 0.27.6
LibRAW 0.21.2-Release
OpenCV 4.6.0
TIFF 4.5.1
QuaZip v1

@scrubbbbs
Copy link
Collaborator

I have a sequence now, in both Qt5/6 same issue.

  • disable remember file history/show recent files at startup
  • enable explorer an point somewhere that has both .zip and folders with images
  • open nomacs
  • click on a zip file
  • click on a folder
  • double click on thumbnail

Also I have a new trace, "double free" is printed to console sometimes

[INFO] [Thumbnail] /home/test/mnt/hd4/tmp/imbdb-wiki/imdb400/imdb/07/nm0000107_rm1736571392_1953-12-8_1989.jpg exif=no size=400x264 5ms
[INFO] [DkImageLoader] 3872 containers created in 115 ms
[INFO] [DkImageLoader] after sorting:  1123 ms
double free or corruption (out)

Thread 1 "nomacs" received signal SIGABRT, Aborted.
__pthread_kill_implementation (no_tid=0, signo=6, threadid=<optimized out>) at ./nptl/pthread_kill.c:44
warning: 44	./nptl/pthread_kill.c: No such file or directory
(gdb) bt
#0  __pthread_kill_implementation (no_tid=0, signo=6, threadid=<optimized out>) at ./nptl/pthread_kill.c:44
#1  __pthread_kill_internal (signo=6, threadid=<optimized out>) at ./nptl/pthread_kill.c:78
#2  __GI___pthread_kill (threadid=<optimized out>, signo=signo@entry=6) at ./nptl/pthread_kill.c:89
#3  0x00007ffff5e4527e in __GI_raise (sig=sig@entry=6) at ../sysdeps/posix/raise.c:26
#4  0x00007ffff5e288ff in __GI_abort () at ./stdlib/abort.c:79
#5  0x00007ffff5e297b6 in __libc_message_impl (fmt=fmt@entry=0x7ffff5fce8d7 "%s\n") at ../sysdeps/posix/libc_fatal.c:134
#6  0x00007ffff5ea8ff5 in malloc_printerr (str=str@entry=0x7ffff5fd1ac0 "double free or corruption (out)") at ./malloc/malloc.c:5772
#7  0x00007ffff5eab120 in _int_free_merge_chunk (av=0x7ffff6003ac0 <main_arena>, p=0x55555780fff0, size=93825028652752) at ./malloc/malloc.c:4676
#8  0x00007ffff5eab43a in _int_free (av=0x7ffff6003ac0 <main_arena>, p=<optimized out>, have_lock=<optimized out>) at ./malloc/malloc.c:4646
#9  0x00007ffff5eaddae in __GI___libc_free (mem=0x555557810000) at ./malloc/malloc.c:3398
#10 0x00007ffff63f845f in QFileInfo::QFileInfo(QString const&) () at /lib/x86_64-linux-gnu/libQt5Core.so.5
#11 0x00007ffff7e22103 in nmc::DkImageLoader::load (this=0x555555c0b350, filePath=...) at /home/test/sw/nomacs/ImageLounge/src/DkCore/DkImageLoader.cpp:862
#12 0x00007ffff6512e16 in ??? () at /lib/x86_64-linux-gnu/libQt5Core.so.5
#13 0x00007ffff7cb1aa7 in nmc::DkThumbScene::loadFileSignal (this=<optimized out>, _t1=<optimized out>, _t2=<optimized out>)
    at /home/test/sw/nomacs-testing/build/nomacsCore_autogen/XJ2BUHI2UY/moc_DkThumbsWidgets.cpp:610
#14 0x00007ffff6512e16 in ??? () at /lib/x86_64-linux-gnu/libQt5Core.so.5
#15 0x00007ffff7cb19d7 in nmc::DkThumbLabel::loadFileSignal (this=<optimized out>, _t1=<optimized out>, _t2=<optimized out>)
    at /home/test/sw/nomacs-testing/build/nomacsCore_autogen/XJ2BUHI2UY/moc_DkThumbsWidgets.cpp:365
#16 0x00007ffff749ea86 in QGraphicsItem::sceneEvent(QEvent*) () at /lib/x86_64-linux-gnu/libQt5Widgets.so.5
#17 0x00007ffff74c2a99 in ??? () at /lib/x86_64-linux-gnu/libQt5Widgets.so.5
#18 0x00007ffff74cb67f in ??? () at /lib/x86_64-linux-gnu/libQt5Widgets.so.5
#19 0x00007ffff74d5903 in QGraphicsScene::event(QEvent*) () at /lib/x86_64-linux-gnu/libQt5Widgets.so.5
#20 0x00007ffff716bd45 in QApplicationPrivate::notify_helper(QObject*, QEvent*) () at /lib/x86_64-linux-gnu/libQt5Widgets.so.5
#21 0x00007ffff64d8118 in QCoreApplication::notifyInternal2(QObject*, QEvent*) () at /lib/x86_64-linux-gnu/libQt5Core.so.5
#22 0x00007ffff74f395f in QGraphicsView::mouseDoubleClickEvent(QMouseEvent*) () at /lib/x86_64-linux-gnu/libQt5Widgets.so.5

@scrubbbbs
Copy link
Collaborator

It is use-after-free on the DkThumbLabel::mFilePath again, this fixes it:

--- ImageLounge/src/DkGui/DkThumbsWidgets.cpp
+++ ImageLounge/src/DkGui/DkThumbsWidgets.cpp
@@ -991,3 +991,3 @@ void DkThumbLabel::mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event)
 {
-    emit loadFileSignal(mFilePath, event->modifiers() == Qt::ControlModifier);
+    emit loadFileSignal(QString(mFilePath), event->modifiers() == Qt::ControlModifier);
 }

@leejuyuu
Copy link
Collaborator Author

I cannot reproduce the crash. Do you have a screen recording? The steps are unclear (click where?).

The problem is that the event handlers are called by Qt. The DkThumbLabel should be owned by QGraphicsScene. I don't see why the event handlers are called after/during the DkThumbLabel is destroyed.

@leejuyuu
Copy link
Collaborator Author

Never mind. I just got a crash after posting the last comment... 🤦

@leejuyuu
Copy link
Collaborator Author

Record some logs. Something else other than DkThumbScene::updateThumbLabels is destroying the labels.

[DEBUG] mouse pressed
[INFO] [DkImageLoader] 8 containers created in 0 ms
[DEBUG] DkThumbScene::updateThumbLabels clear start
[DEBUG] DkThumbLabel destructor
[DEBUG] DkThumbLabel destructor
[DEBUG] DkThumbLabel destructor
[DEBUG] DkThumbLabel destructor
[DEBUG] DkThumbLabel destructor
[DEBUG] DkThumbLabel destructor
[DEBUG] DkThumbLabel destructor
[DEBUG] DkThumbLabel destructor
[DEBUG] DkThumbScene::updateThumbLabels clear done
[INFO] [DkImageLoader] after sorting:  1 ms
[DEBUG] getting file list.....
[WARNING] Qt has caught an exception thrown from an event handler. Throwing
exceptions from an event handler is not supported in Qt.
You must not let any exception whatsoever propagate through Qt code.
[INFO] loading /home/tzu-yu/book_sale/biochem.png
[DEBUG] [Exiv2] metadata loaded image/png 1300 1733
[INFO] [Loader::qt] biochem.png "image/png" QImage::Format_ARGB32 "GIMP_built-in_sRGB" transform:none 37ms
[DEBUG] [fade] nmc::DkPlayer show=false save=true showing=false hiding=false visible=false
[DEBUG] [Cacher]  "/home/tzu-yu/book_sale/calculus.png"  fully cached...
[DEBUG] [Cacher]  "/home/tzu-yu/book_sale/campbell.png"  file fetched...
[DEBUG] [Cacher] created in 0 ms ( 10.7818 MB)
[INFO] loading /home/tzu-yu/book_sale/calculus.png
[DEBUG] [Exiv2] metadata loaded image/png 1219 1625
[INFO] [Loader::qt] calculus.png "image/png" QImage::Format_ARGB32 "GIMP_built-in_sRGB" transform:none 41ms

[DEBUG] DkThumbLabel destructor
[DEBUG] DkThumbLabel destructor
[DEBUG] DkThumbLabel destructor
[DEBUG] DkThumbLabel destructor
[DEBUG] DkThumbLabel destructor
[DEBUG] DkThumbLabel destructor
[DEBUG] DkThumbLabel destructor
[DEBUG] DkThumbLabel destructor
[Thread 0x7fff514c76c0 (LWP 52225) exited]
[Thread 0x7fff68abb6c0 (LWP 52298) exited]

@leejuyuu
Copy link
Collaborator Author

More logs. This is indeed a use after free.

[DEBUG] DkTHumbLabel constructor nmc::DkThumbLabel(0x555555c0cf10, pos=0,0) "/home/tzu-yu/book_sale/analytical.png"
[DEBUG] DkTHumbLabel constructor nmc::DkThumbLabel(0x555555f94600, pos=0,0) "/home/tzu-yu/book_sale/biochem.png"
[DEBUG] DkTHumbLabel constructor nmc::DkThumbLabel(0x555555fb4030, pos=0,0) "/home/tzu-yu/book_sale/calculus.png"
[DEBUG] DkTHumbLabel constructor nmc::DkThumbLabel(0x555555c3c620, pos=0,0) "/home/tzu-yu/book_sale/campbell.png"
[DEBUG] DkTHumbLabel constructor nmc::DkThumbLabel(0x5555557249a0, pos=0,0) "/home/tzu-yu/book_sale/genetics.png"
[DEBUG] DkTHumbLabel constructor nmc::DkThumbLabel(0x555555d97660, pos=0,0) "/home/tzu-yu/book_sale/halliday.png"
[DEBUG] DkTHumbLabel constructor nmc::DkThumbLabel(0x55555607f610, pos=0,0) "/home/tzu-yu/book_sale/neuro.png"
[DEBUG] DkTHumbLabel constructor nmc::DkThumbLabel(0x555555f9ed40, pos=0,0) "/home/tzu-yu/book_sale/organic.png"
[DEBUG] mouse pressed
[DEBUG] mouse double click nmc::DkThumbLabel(0x555555c0cf10, pos=2,2, flags=(ItemIsSelectable))
[INFO] [DkImageLoader] 8 containers created in 0 ms
[DEBUG] sort update dir 8
[DEBUG] DkThumbScene::updateThumbLabels clear start
[DEBUG] DkThumbLabel destructor "/home/tzu-yu/book_sale/analytical.png" nmc::DkThumbLabel(0x555555c0cf10, pos=2,2, flags=(ItemIsSelectable))
[DEBUG] DkThumbLabel destructor "/home/tzu-yu/book_sale/biochem.png" nmc::DkThumbLabel(0x555555f94600, pos=68,2, flags=(ItemIsSelectable))
[DEBUG] DkThumbLabel destructor "/home/tzu-yu/book_sale/calculus.png" nmc::DkThumbLabel(0x555555fb4030, pos=134,2, flags=(ItemIsSelectable))
[DEBUG] DkThumbLabel destructor "/home/tzu-yu/book_sale/campbell.png" nmc::DkThumbLabel(0x555555c3c620, pos=2,68, flags=(ItemIsSelectable))
[DEBUG] DkThumbLabel destructor "/home/tzu-yu/book_sale/genetics.png" nmc::DkThumbLabel(0x5555557249a0, pos=68,68, flags=(ItemIsSelectable))
[DEBUG] DkThumbLabel destructor "/home/tzu-yu/book_sale/halliday.png" nmc::DkThumbLabel(0x555555d97660, pos=134,68, flags=(ItemIsSelectable))
[DEBUG] DkThumbLabel destructor "/home/tzu-yu/book_sale/neuro.png" nmc::DkThumbLabel(0x55555607f610, pos=2,134, flags=(ItemIsSelectable))
[DEBUG] DkThumbLabel destructor "/home/tzu-yu/book_sale/organic.png" nmc::DkThumbLabel(0x555555f9ed40, pos=68,134, flags=(ItemIsSelectable))
[DEBUG] DkThumbScene::updateThumbLabels clear done
[DEBUG] DkTHumbLabel constructor nmc::DkThumbLabel(0x55555586d9f0, pos=0,0) "/home/tzu-yu/book_sale/analytical.png"
[DEBUG] DkTHumbLabel constructor nmc::DkThumbLabel(0x555555dcef20, pos=0,0) "/home/tzu-yu/book_sale/biochem.png"
[DEBUG] DkTHumbLabel constructor nmc::DkThumbLabel(0x555556113e80, pos=0,0) "/home/tzu-yu/book_sale/calculus.png"
[DEBUG] DkTHumbLabel constructor nmc::DkThumbLabel(0x555555f52820, pos=0,0) "/home/tzu-yu/book_sale/campbell.png"
[DEBUG] DkTHumbLabel constructor nmc::DkThumbLabel(0x555555fc9990, pos=0,0) "/home/tzu-yu/book_sale/genetics.png"
[DEBUG] DkTHumbLabel constructor nmc::DkThumbLabel(0x55555607a890, pos=0,0) "/home/tzu-yu/book_sale/halliday.png"
[DEBUG] DkTHumbLabel constructor nmc::DkThumbLabel(0x55555607f760, pos=0,0) "/home/tzu-yu/book_sale/neuro.png"
[DEBUG] DkTHumbLabel constructor nmc::DkThumbLabel(0x55555607f610, pos=0,0) "/home/tzu-yu/book_sale/organic.png"
[INFO] [DkImageLoader] after sorting:  1 ms
[DEBUG] getting file list.....

Thread 1 "nomacs" received signal SIGSEGV, Segmentation fault.
0x00007ffff270fe2a in QFileInfo::QFileInfo(QString const&) () from /usr/lib/libQt6Core.so.6
(gdb) bt
#0  0x00007ffff270fe2a in QFileInfo::QFileInfo(QString const&) () from /usr/lib/libQt6Core.so.6
#1  0x00007ffff7ca7f00 in nmc::DkImageLoader::load (
    this=0x555555abb290, filePath=...)
    at /home/tzu-yu/git_repos/nomacs/ImageLounge/src/DkCore/DkImageLoader.cpp:867
#2  0x00007ffff7c07d4b in nmc::DkViewPort::loadFile (
    this=0x555555732b80, filePath=...)
    at /home/tzu-yu/git_repos/nomacs/ImageLounge/src/DkGui/DkViewPort.cpp:1707
#3  0x00007ffff7aa5887 in nmc::DkCentralWidget::loadFile (this=0x5555557cd300, filePath=..., newTab=false)
    at /home/tzu-yu/git_repos/nomacs/ImageLounge/src/DkGui/DkCentralWidget.cpp:1090
#4  0x00007ffff7ab0822 in QtPrivate::FunctorCall<std::integer_sequence<unsigned long, 0ul, 1ul>, QtPrivate::List<QString const&, bool>, void, void (nmc::DkCentralWidget::*)(QString const&, bool)>::call(void (nmc::DkCentralWidget::*)(QString const&, bool), nmc::DkCentralWidget*, void**)::{lambda()#1}::operator()() const (
    __closure=0x7fffffffc7d0)
    at /usr/include/qt6/QtCore/qobjectdefs_impl.h:127
#5  0x00007ffff7ab0dd2 in QtPrivate::FunctorCallBase::call_internal<void, QtPrivate::FunctorCall<std::integer_sequence<unsigned long, 0ul, 1ul>, QtPrivate::List<QString const&, bool>, void, void (nmc::DkCentralWidget::*)(QString const&, bool)>::call(void (nmc::DkCentralWidget::*)(QString const&, bool), nmc::DkCentralWidget*, void**)::{lambda()#1}>(void**, QtPrivate::FunctorCall<std::integer_sequence<unsigned long, 0ul, 1ul>, QtPrivate::List<QString const&, bool>, void, void (nmc::DkCentralWidget::*)(QString const&, bool)>::call(void (nmc::DkCentralWidget::*)(QString const&, bool), nmc::DkCentralWidget*, void**)::{lambda()#1}&&) (
    args=0x7fffffffc9b0, fn=...)
    at /usr/include/qt6/QtCore/qobjectdefs_impl.h:65
#6  0x00007ffff7ab0894 in QtPrivate::FunctorCall<std::integer_sequence<unsigned long, 0ul, 1ul>, QtPrivate::List<QString const&, bool>, void, void (nmc::DkCentralWidget::*)(QString const&, bool)>::call (
--Type <RET> for more, q to quit, c to continue without paging--
    f=(void (nmc::DkCentralWidget::*)(nmc::DkCentralWidget * const, const QString &, bool)) 0x7ffff7aa582a <nmc::DkCentralWidget::loadFile(QString const&, bool)>, o=0x5555557cd300, arg=0x7fffffffc9b0)
    at /usr/include/qt6/QtCore/qobjectdefs_impl.h:126
#7  0x00007ffff7aafa70 in QtPrivate::FunctionPointer<void (nmc::DkCentralWidget::*)(QString const&, bool)>::call<QtPrivate::List<QString const&, bool>, void> (
    f=(void (nmc::DkCentralWidget::*)(nmc::DkCentralWidget * const, const QString &, bool)) 0x7ffff7aa582a <nmc::DkCentralWidget::loadFile(QString const&, bool)>, o=0x5555557cd300, arg=0x7fffffffc9b0)
    at /usr/include/qt6/QtCore/qobjectdefs_impl.h:174
#8  0x00007ffff7aaf28f in QtPrivate::QCallableObject<void (nmc::DkCentralWidget::*)(QString const&, bool), QtPrivate::List<QString const&, bool>, void>::impl (
    which=1, this_=0x555555d59170, r=0x5555557cd300, 
    a=0x7fffffffc9b0, ret=0x0)
    at /usr/include/qt6/QtCore/qobjectdefs_impl.h:545
#9  0x00007ffff27b6cc9 in ?? ()
   from /usr/lib/libQt6Core.so.6
#10 0x00007ffff7a2822a in QMetaObject::activate<void, QString, bool> (sender=0x7fff6000f8e0, 
    mo=0x7ffff7f336a0 <nmc::DkThumbScene::staticMetaObject>, local_signal_index=0, ret=0x0)
    at /usr/include/qt6/QtCore/qobjectdefs.h:306
#11 0x00007ffff79feb40 in nmc::DkThumbScene::loadFileSignal (this=0x7fff6000f8e0, _t1=..., _t2=false)
    at /home/tzu-yu/git_repos/nomacs/build/nomacsCore_autogen/XJ2BUHI2UY/moc_DkThumbsWidgets.cpp:536
#12 0x00007ffff7bd94dc in QtPrivate::FunctorCall<std::integer_sequence<unsigned long, 0ul, 1ul>, QtPrivate::List<QString const&, bool>, void, void (nmc::DkThumbScene::*)(QString const&, bool) const>::call(void (nmc::DkThumbScene::*)(QString const&, bool) const, nmc::DkThumbScene*, void**)::{lambda()#1}::operator()() const
    (__closure=0x7fffffffca80)
    at /usr/include/qt6/QtCore/qobjectdefs_impl.h:138
#13 0x00007ffff7bddd9b in QtPrivate::FunctorCallBase::call_internal<void, QtPrivate::FunctorCall<std::intege--Type <RET> for more, q to quit, c to continue without paging--
r_sequence<unsigned long, 0ul, 1ul>, QtPrivate::List<QString const&, bool>, void, void (nmc::DkThumbScene::*)(QString const&, bool) const>::call(void (nmc::DkThumbScene::*)(QString const&, bool) const, nmc::DkThumbScene*, void**)::{lambda()#1}>(void**, QtPrivate::FunctorCall<std::integer_sequence<unsigned long, 0ul, 1ul>, QtPrivate::List<QString const&, bool>, void, void (nmc::DkThumbScene::*)(QString const&, bool) const>::call(void (nmc::DkThumbScene::*)(QString const&, bool) const, nmc::DkThumbScene*, void**)::{lambda()#1}&&) (
    args=0x7fffffffcc60, fn=...)
    at /usr/include/qt6/QtCore/qobjectdefs_impl.h:65
#14 0x00007ffff7bd954e in QtPrivate::FunctorCall<std::integer_sequence<unsigned long, 0ul, 1ul>, QtPrivate::List<QString const&, bool>, void, void (nmc::DkThumbScene::*)(QString const&, bool) const>::call (
    f=(void (nmc::DkThumbScene::*)(const nmc::DkThumbScene * const, const QString &, bool)) 0x7ffff79feb00 <nmc::DkThumbScene::loadFileSignal(QString const&, bool) const>, o=0x7fff6000f8e0, arg=0x7fffffffcc60)
    at /usr/include/qt6/QtCore/qobjectdefs_impl.h:137
#15 0x00007ffff7bd3a54 in QtPrivate::FunctionPointer<void (nmc::DkThumbScene::*)(QString const&, bool) const>::call<QtPrivate::List<QString const&, bool>, void>
    (
    f=(void (nmc::DkThumbScene::*)(const nmc::DkThumbScene * const, const QString &, bool)) 0x7ffff79feb00 <nmc::DkThumbScene::loadFileSignal(QString const&, bool) const>, o=0x7fff6000f8e0, arg=0x7fffffffcc60)
    at /usr/include/qt6/QtCore/qobjectdefs_impl.h:186
#16 0x00007ffff7bcdda5 in QtPrivate::QCallableObject<void (nmc::DkThumbScene::*)(QString const&, bool) const, QtPrivate::List<QString const&, bool>, void>::impl
    (which=1, this_=0x555555fa2820, 
    r=0x7fff6000f8e0, a=0x7fffffffcc60, ret=0x0)
    at /usr/include/qt6/QtCore/qobjectdefs_impl.h:545
#17 0x00007ffff27b6cc9 in ?? ()
   from /usr/lib/libQt6Core.so.6
#18 0x00007ffff7a2822a in QMetaObject::activate<void, QString, bool> (sender=0x555555c0cf10, 
--Type <RET> for more, q to quit, c to continue without paging--
    mo=0x7ffff7f32dc0 <nmc::DkThumbLabel::staticMetaObject>, local_signal_index=0, ret=0x0)
    at /usr/include/qt6/QtCore/qobjectdefs.h:306
#19 0x00007ffff79fe34c in nmc::DkThumbLabel::loadFileSignal (this=0x555555c0cf10, _t1=..., _t2=false)
    at /home/tzu-yu/git_repos/nomacs/build/nomacsCore_autogen/XJ2BUHI2UY/moc_DkThumbsWidgets.cpp:310
#20 0x00007ffff7bb2908 in nmc::DkThumbLabel::mouseDoubleClickEvent (this=0x555555c0cf10, 
    event=0x7fffffffd1f0)
    at /home/tzu-yu/git_repos/nomacs/ImageLounge/src/DkGui/DkThumbsWidgets.cpp:996
#21 0x00007ffff3cdcb5b in QGraphicsItem::sceneEvent(QEvent*) () from /usr/lib/libQt6Widgets.so.6
#22 0x00007ffff3d00797 in ?? ()
   from /usr/lib/libQt6Widgets.so.6
#23 0x00007ffff3d06500 in ?? ()
   from /usr/lib/libQt6Widgets.so.6
#24 0x00007ffff3d06e7e in QGraphicsScene::event(QEvent*) () from /usr/lib/libQt6Widgets.so.6
#25 0x00007ffff38fed9e in QApplicationPrivate::notify_helper(QObject*, QEvent*) ()
   from /usr/lib/libQt6Widgets.so.6
#26 0x00007ffff275a018 in QCoreApplication::notifyInternal2(QObject*, QEvent*) ()
   from /usr/lib/libQt6Core.so.6
#27 0x00007ffff3d3c8b3 in QGraphicsView::mouseDoubleClickEvent(QMouseEvent*) ()
   from /usr/lib/libQt6Widgets.so.6
#28 0x00007ffff3951893 in QWidget::event(QEvent*) ()
   from /usr/lib/libQt6Widgets.so.6
#29 0x00007ffff39f1538 in QFrame::event(QEvent*) ()
   from /usr/lib/libQt6Widgets.so.6
#30 0x00007ffff2759358 in QCoreApplicationPrivate::sendThroughObjectEventFilters(QObject*, QEvent*) ()
   from /usr/lib/libQt6Core.so.6
#31 0x00007ffff38fed8e in QApplicationPrivate::notify_helper(QObject*, QEvent*) ()
   from /usr/lib/libQt6Widgets.so.6
#32 0x00007ffff3902ec6 in QApplication::notify(QObject--Type <RET> for more, q to quit, c to continue without paging--
*, QEvent*) () from /usr/lib/libQt6Widgets.so.6
#33 0x00007ffff275a018 in QCoreApplication::notifyInternal2(QObject*, QEvent*) ()
   from /usr/lib/libQt6Core.so.6
#34 0x00007ffff38f683c in QApplicationPrivate::sendMouseEvent(QWidget*, QMouseEvent*, QWidget*, QWidget*, QWidget**, QPointer<QWidget>&, bool, bool) ()
   from /usr/lib/libQt6Widgets.so.6
#35 0x00007ffff396c85f in ?? ()
   from /usr/lib/libQt6Widgets.so.6
#36 0x00007ffff396db90 in ?? ()
   from /usr/lib/libQt6Widgets.so.6
#37 0x00007ffff38fed9e in QApplicationPrivate::notify_helper(QObject*, QEvent*) ()
   from /usr/lib/libQt6Widgets.so.6
#38 0x00007ffff275a018 in QCoreApplication::notifyInternal2(QObject*, QEvent*) ()
   from /usr/lib/libQt6Core.so.6
#39 0x00007ffff2f8c813 in QGuiApplicationPrivate::processMouseEvent(QWindowSystemInterfacePrivate::MouseEvent*) () from /usr/lib/libQt6Gui.so.6
#40 0x00007ffff300d0c4 in QWindowSystemInterface::sendWindowSystemEvents(QFlags<QEventLoop::ProcessEventsFlag>) () from /usr/lib/libQt6Gui.so.6
#41 0x00007ffff300d2b7 in QWindowSystemInterface::flushWindowSystemEvents(QFlags<QEventLoop::ProcessEventsFlag>) () from /usr/lib/libQt6Gui.so.6
#42 0x00007ffff27a54aa in QObject::event(QEvent*) ()
   from /usr/lib/libQt6Core.so.6
#43 0x00007ffff38fed9e in QApplicationPrivate::notify_helper(QObject*, QEvent*) ()
   from /usr/lib/libQt6Widgets.so.6
#44 0x00007ffff275a018 in QCoreApplication::notifyInternal2(QObject*, QEvent*) ()
   from /usr/lib/libQt6Core.so.6
#45 0x00007ffff275a3f2 in QCoreApplicationPrivate::sendPostedEvents(QObject*, int, QThreadData*) ()
   from /usr/lib/libQt6Core.so.6
#46 0x00007ffff29cfea8 in ?? ()
   from /usr/lib/libQt6Core.so.6
--Type <RET> for more, q to quit, c to continue without paging--
#47 0x00007fffef9c787d in ?? ()
   from /usr/lib/libglib-2.0.so.0
#48 0x00007fffef9c8cd7 in ?? ()
   from /usr/lib/libglib-2.0.so.0
#49 0x00007fffef9c8ee5 in g_main_context_iteration ()
   from /usr/lib/libglib-2.0.so.0
#50 0x00007ffff29cd59d in QEventDispatcherGlib::processEvents(QFlags<QEventLoop::ProcessEventsFlag>) ()
   from /usr/lib/libQt6Core.so.6
#51 0x00007ffff2765376 in QEventLoop::exec(QFlags<QEventLoop::ProcessEventsFlag>) ()
   from /usr/lib/libQt6Core.so.6
#52 0x00007ffff275d159 in QCoreApplication::exec() ()
   from /usr/lib/libQt6Core.so.6
#53 0x000055555556260e in main (argc=1, 
    argv=0x7fffffffe6b8)
    at /home/tzu-yu/git_repos/nomacs/ImageLounge/src/main.cpp:415
(gdb) f 20
#20 0x00007ffff7bb2908 in nmc::DkThumbLabel::mouseDoubleClickEvent (this=0x555555c0cf10, 
    event=0x7fffffffd1f0)
    at /home/tzu-yu/git_repos/nomacs/ImageLounge/src/DkGui/DkThumbsWidgets.cpp:996
996	   emit loadFileSignal(mFilePath, event->modifiers() == Qt::ControlModifier);
(gdb) p this
$1 = (nmc::DkThumbLabel * const) 0x555555c0cf10
(gdb) 

@leejuyuu
Copy link
Collaborator Author

leejuyuu commented May 31, 2025

I think I understand what is going on now. The call to DkThumbLabel::mouseDoubleClickEvent could free the object itself. The emitted signal calls a series of functions to load the image and reset the image loader to a new directory. During the process the file list would be updated, so the old DkThumbLabels are destroyed. Still not sure why this seems to happen sporadically.

@scrubbbbs
Copy link
Collaborator

Still not sure why this seems to happen sporadically.

It should only happen when switching from zip to non-zip folder, then double-clicking. This is the line that causes the directory update when double-clicking.

I can imagine there are other cases that can also cause this as there are several paths ending up in updateThumbLabels() (I counted 3 or 4 when debugging). So I think we just put the quick fix in for now (pass by value all signals from thumb label). We can remove some of these paths and/or re-architect DkThumbsScene to eliminate them, but that can be left for a future work.

void DkImageLoader::setCurrentImage(QSharedPointer<DkImageContainerT> newImg)
{
    // force index folder if we dir out of the zip
    if (mCurrentImage && newImg && mCurrentImage->isFromZip() && !newImg->isFromZip())
      mFolderUpdated = true;

The loadFileSignal is connected to slots that updates the current
directory and could potentially delete the DkThumbLabel during the
process, making `mFilePath` a dangling reference.
See the discussions in nomacs#1262

This temporarily bypasses the possible segfault by copying the
`mFilePath`, so the value outlives the signal. We should fix the
lifetime of DkThumbLabel in the future.
@leejuyuu leejuyuu merged commit 1238a7c into nomacs:master Jun 1, 2025
12 checks passed
@leejuyuu leejuyuu deleted the thumbs branch June 1, 2025 04:09
leejuyuu added a commit that referenced this pull request Jun 1, 2025
There was a crash at setAlphaChannel call that was probably due to a
race condition when converting the mask image to grayscale8.
See #1262 (comment)

Specify the format when constructing the mask to bypass this conversion.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants