Bug 404995 - QApplication::quitOnLastWindowClosed() doesn't work if dialog opened before QApplication::exec() started
Summary: QApplication::quitOnLastWindowClosed() doesn't work if dialog opened before Q...
Status: REPORTED
Alias: None
Product: plasma-integration
Classification: Plasma
Component: general (show other bugs)
Version: unspecified
Platform: Other Linux
: NOR normal
Target Milestone: ---
Assignee: Plasma Bugs List
URL:
Keywords:
Depends on:
Blocks:
 
Reported: 2019-03-02 17:40 UTC by Martin Sandsmark
Modified: 2021-09-24 12:03 UTC (History)
2 users (show)

See Also:
Latest Commit:
Version Fixed In:
Sentry Crash Report:


Attachments
Call stack of a leaking KIO::ListJob (7.89 KB, text/plain)
2021-09-24 12:03 UTC, Raphael Robatsch
Details

Note You need to log in before you can comment on or make changes to this bug.
Description Martin Sandsmark 2019-03-02 17:40:59 UTC
Some applications call e. g. QFileDialog::getOpenFileName() before starting QApplication.

As an example (which was my test case), here I call getOpenFileName() in the constructor of my custom widget for the file: https://github.com/sandsmark/epubreader/blob/master/widget.cpp#L26

... and then after creating the widget I actually start the event loop: https://github.com/sandsmark/epubreader/blob/master/main.cpp#L8-L11


This works fine either without a QPA or with a QPA without custom dialogs (tested with qt5ct), but for some reason I couldn't track down KDEPlatformFileDialog breaks quitOnLastWindowClosed; i. e. the application opens a file dialog, starts the QApplication, and then shows its main widget, but QApplication doesn't exit when the main widget is closed.


Debugging done so far:

I looked briefly in the code that handles quitOnLastWindowClosed, and copied the logic into my test case: https://github.com/sandsmark/epubreader/blob/master/widget.cpp#L92-L111

But that prints «last closed? true» both when it works (e.g. with qt5ct) and when it doesn't.

I also tried various combinations of setting Qt::WA_DeleteOnClose on the KDEPlatformFileDialog, and my own application, and replacing hide() for close(), etc., but nothing worked. I also verified that the KDEPlatformFileDialog and the KDEPlatformFileDialogHelper got deleted.

The only thing I've thought about that I haven't tested is changing KDEPlatformFileDialog to not inherit from QDialog, but making it just a plain QWidget.


Partial solution:

After I tracked down that it only happened iff the application tried to open a dialog before starting the QApplication, I added a hack (to my own private "fork" of plasma-integration with ugly hacks that I can't put in plasma-integration proper), that just checks if the QApplication is running, otherwise it just lets Qt use its own QFileDialog:

https://github.com/sandsmark/sandsmark-integration/commit/476c4e16c21128d6f390b9c80c8dad9290c222f7


In conclusion calling getOpenFile() etc. before launching the QApplication is probably a bit of an anti-pattern, but it's not that uncommon, so it should probably be fixed properly. It might be a bug in Qt, but emitLastWindowClosed() checks if in_exec is true (in_exec is set when the QCoreApplication event loop is started) every time it is called, so the logic there looks sound.
Comment 1 David Edmundson 2019-03-02 18:33:04 UTC
Can you put a breakpoint on QEventLoopLocker::QEventLoopLocker()

I think it'll be KIO causing it.
Comment 2 Martin Sandsmark 2019-03-02 20:13:11 UTC
unfortunately I don't have debug symbols for kio or qt, but I think you are spot on:

(gdb) bt
#0  0x00007ffff6fb4850 in QEventLoopLocker::QEventLoopLocker() () from /usr/lib/libQt5Core.so.5
#1  0x00007fffefbb530c in KJobPrivate::KJobPrivate() () from /usr/lib/libKF5CoreAddons.so.5
#2  0x00007fffefbb1a1a in KCompositeJobPrivate::KCompositeJobPrivate() () from /usr/lib/libKF5CoreAddons.so.5
#3  0x00007fffefbb1a61 in KCompositeJob::KCompositeJob(QObject*) () from /usr/lib/libKF5CoreAddons.so.5
#4  0x00007ffff05a2b34 in KIO::Job::Job(KIO::JobPrivate&) () from /usr/lib/libKF5KIOCore.so.5
#5  0x00007ffff05b71da in KIO::SimpleJob::SimpleJob(KIO::SimpleJobPrivate&) () from /usr/lib/libKF5KIOCore.so.5
#6  0x00007ffff05bc4fa in KIO::StatJob::StatJob(KIO::StatJobPrivate&) () from /usr/lib/libKF5KIOCore.so.5
#7  0x00007ffff05bd24d in KIO::stat(QUrl const&, KIO::StatJob::StatSide, short, QFlags<KIO::JobFlag>) () from /usr/lib/libKF5KIOCore.so.5
#8  0x00007ffff12cc810 in KFileWidget::KFileWidget(QUrl const&, QWidget*) () from /usr/lib/libKF5KIOFileWidgets.so.5
#9  0x00007ffff13c06dd in KDEPlatformFileDialog::KDEPlatformFileDialog (this=0x55555560ed60) at /home/sandsmark/src/sandsmark-integration/src/platformtheme/kdeplatformfiledialoghelper.cpp:100
#10 0x00007ffff13c1514 in KDEPlatformFileDialogHelper::KDEPlatformFileDialogHelper (this=0x5555556109b0) at /home/sandsmark/src/sandsmark-integration/src/platformtheme/kdeplatformfiledialoghelper.cpp:254
#11 0x00007ffff13b4274 in KdePlatformTheme::createPlatformDialogHelper (this=0x555555601c50, type=QPlatformTheme::FileDialog) at /home/sandsmark/src/sandsmark-integration/src/platformtheme/kdeplatformtheme.cpp:294
#12 0x00007ffff7bf005e in QDialogPrivate::platformHelper() const () from /usr/lib/libQt5Widgets.so.5
#13 0x00007ffff7c02afd in QFileDialogPrivate::init(QUrl const&, QString const&, QString const&) () from /usr/lib/libQt5Widgets.so.5
#14 0x00007ffff7c0311a in QFileDialog::QFileDialog(QFileDialogArgs const&) () from /usr/lib/libQt5Widgets.so.5
#15 0x00007ffff7c03284 in QFileDialog::getOpenFileUrl(QWidget*, QString const&, QUrl const&, QString const&, QString*, QFlags<QFileDialog::Option>, QStringList const&) () from /usr/lib/libQt5Widgets.so.5
#16 0x00007ffff7c034d6 in QFileDialog::getOpenFileName(QWidget*, QString const&, QString const&, QString const&, QString*, QFlags<QFileDialog::Option>) () from /usr/lib/libQt5Widgets.so.5
#17 0x000055555555c31a in Widget::Widget (this=0x7fffffffe000, parent=0x0) at widget.cpp:31
#18 0x000055555555bfc4 in main (argc=1, argv=0x7fffffffe168) at main.cpp:8
Comment 3 Martin Sandsmark 2019-03-02 20:17:22 UTC
Put a breakpoint on the destructor as well, though, and it seems like all the lockers are destroyed, but I guess they don't handle the application not running yet very well.
Comment 4 Raphael Robatsch 2021-09-24 12:03:31 UTC
Created attachment 141860 [details]
Call stack of a leaking KIO::ListJob

I've debugged this some more and found a KIO::ListJob that doesn't get destroyed.

I added qInfo() calls to the constructor, destructor, and KJob::finishJob (before the deleteLater()) call. I made a small program which shows a file picker, and then a main window. I started the application and pressed Escape to close the file picker. Here's what happens:

    NEW KIO::ListJob(0x55ecf47bd0a0) QUrl("tags:/")
    deleteLater KIO::ListJob(0x55ecf47bd0a0)
    DELETE KIO::ListJob(0x55ecf47bd0a0)
    NEW KIO::ListJob(0x55ecf48e6720) QUrl("file:///home/raphi/src/scratch/pleaseclose")
    deleteLater KIO::ListJob(0x55ecf48e6720)
    NEW KIO::ListJob(0x55ecf50cccd0) QUrl("file:///home/raphi/src/scratch/pleaseclose")
    deleteLater KIO::ListJob(0x55ecf50cccd0)
    DELETE KIO::ListJob(0x55ecf50cccd0)

the 2nd KIO::ListJob (0x...6720) doesn't get deleted, even though deleteLater() gets called.

I've attached call stacks of the leaking job's creation and killing. But I can't figure out why that job doesn't get deleted - deleteLater() gets called after all.
Manually deleting the job in KCoreDirListerCache::stopListingUrl() causes the application to exit as expected once the main window is closed, which confrims that the leaking ListJob is the cause of this bug (this is not a fix, of course). I'm not sure how to debug this further.