RemoteLinux: Do not announce connection attempts from non-UI threads

This can lead to a deadlock. Example scenario:
  - Have a kit with an unreachable device as the build device.
  - Open a project with a .user file not matching the current QtC.
  - The target setup page will open, showing a candidate widget
    for each kit. These widgets contains path choosers, which nowadays
    start a dedicated thread to validate their input.
  - The path validation thread for the Linux kit arrives at setupShell()
    and tries to announce the connection attempt. To that end, it
    invokes a method of Core::ICore::infoBar, using
    Qt::BlockingQueuedConnection, as otherwise the call would either
    not be tread-safe or arrive too late.
  - As the InfoBar lives in the main thread, control is now passed back
    to that one, and the target setup page continues its work, which is
    to gather candidates for importing a build from. This involves
    perusing potential build directories for all kits. In case of the
    Linux kit, QDirIterator is backed by a remote call to find(), which
    triggers a call to runInShell(), which tries to acquire the mutex
    that is already held by the path validation thread, which in turn is
    waiting for the UI thread.
  - Qt Creator now hangs indefinitely.
Skipping the announcements for non-UI threads limits the usefulness of
this feature, but I don't see a better way.

Change-Id: I816c83358f543aa9a6e6e97eee7fa8ad95e66ea8
Reviewed-by: Marcus Tillmanns <marcus.tillmanns@qt.io>
This commit is contained in:
Christian Kandeler
2024-06-03 13:03:00 +02:00
parent 249312acd0
commit bfd3b88bb8

View File

@@ -1231,25 +1231,20 @@ RunResult LinuxDevicePrivate::runInShell(const CommandLine &cmd, const QByteArra
void LinuxDevicePrivate::announceConnectionAttempt()
{
const auto announce = [id = announceId(), name = q->displayName()] {
Core::ICore::infoBar()->addInfo(
InfoBarEntry(id,
Tr::tr("Establishing initial connection to device \"%1\". "
"This might take a moment.")
.arg(name)));
const QString message = Tr::tr("Establishing initial connection to device \"%1\". "
"This might take a moment.").arg(q->displayName());
qCDebug(linuxDeviceLog) << message;
if (isMainThread()) {
Core::ICore::infoBar()->addInfo(InfoBarEntry(announceId(), message));
QCoreApplication::processEvents(QEventLoop::ExcludeUserInputEvents);
QCoreApplication::processEvents(QEventLoop::ExcludeUserInputEvents); // Yes, twice.
};
if (QThread::currentThread() == qApp->thread())
announce();
else
QMetaObject::invokeMethod(Core::ICore::infoBar(), announce, Qt::BlockingQueuedConnection);
}
}
void LinuxDevicePrivate::unannounceConnectionAttempt()
{
QMetaObject::invokeMethod(Core::ICore::infoBar(),
[id = announceId()] { Core::ICore::infoBar()->removeInfo(id); });
if (isMainThread())
Core::ICore::infoBar()->removeInfo(announceId());
}
bool LinuxDevicePrivate::checkDisconnectedWithWarning()