Open forwarded files outside the socket handler, not inside it

QETApp::receiveMessage() called openFiles() directly. That slot runs inside
SingleApplication's socket handling: SingleApplicationPrivate::
slotDataAvailable() emits receivedMessage synchronously from the readyRead
lambda (singleapplication_p.cpp:452). openFiles() then loads a project --
seconds of work on a large one -- and openAndAddProject() puts up a modal
BackupDialog whose exec() runs a nested event loop while the socket handler
is still on the stack.

During that nested loop the secondary instance exits, the connection closes
and the QLocalSocket is deleted. When the dialog is dismissed and the stack
unwinds, QMetaObject::activate() carries on emitting on the freed sender and
the process dies.

A zero-timer returns to the event loop first, so the socket stack is fully
unwound before any project is opened.

Found by scorpio810 while testing PR #861, with a backtrace showing no QET
frame above the crash. His second suggestion, looking for a delete that
should be deleteLater(), turned out to be already satisfied at
singleapplication_p.cpp:331 -- which is why the deferred delete is not enough
on its own once a nested loop is in play.

Dismissing the dialog is the step that makes it fail: two earlier attempts to
reproduce it left the dialog open, the stack never unwound, and nothing
crashed. With the dialog dismissed it segfaults twice out of two; with this
change it survives twice out of two, opens the project as before, and ctest
stays green. Qt 6.10.2 on X11/xcb -- also checked under a headless Wayland
compositor and under Qt 5.15.18, so it is neither Wayland-specific nor a Qt6
regression.

The crash needs PR #861 to be reachable at all: without it splitWithSpaces()
returns an empty list, no project opens, and nothing enters this path.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
ispyisail
2026-09-14 21:27:57 +12:00
parent 3cbb930751
commit ce890da342
+25 -1
View File
@@ -1659,7 +1659,31 @@ void QETApp::receiveMessage(int instanceId, QByteArray message)
{
QString my_message(str.mid(20));
QStringList args_list = QET::splitWithSpaces(my_message);
openFiles(QETArguments(args_list));
// Deferred, not called directly.
//
// This slot runs inside SingleApplication's readyRead handling:
// SingleApplicationPrivate::slotDataAvailable() emits
// receivedMessage() synchronously from the socket's readyRead
// lambda. openFiles() then loads a project -- seconds of work on
// a large one -- and openAndAddProject() puts up a modal
// BackupDialog, whose exec() runs a nested event loop while the
// socket handler is still on the stack.
//
// During that nested loop the secondary instance exits, the
// connection closes and the QLocalSocket is deleted. When the
// dialog is dismissed and the stack unwinds, QMetaObject::
// activate() continues emitting on the freed sender and the
// process dies. Reported with a backtrace on PR #861;
// reproduced on Qt 6.10.2 by dismissing the dialog, which is the
// step that makes it fail -- leaving it open never unwinds.
//
// A zero-timer returns to the event loop first, so the socket
// stack is fully unwound before any of this runs.
const QETArguments deferred_args{args_list};
QTimer::singleShot(0, this, [this, deferred_args]() {
openFiles(deferred_args);
});
}
}