Android: Synchronize startAvd()'s future

This change fixes a possible deadlock on Creator shutdown
after clicking "Start AVD" button from the Android device
settings dialog, and quickly closing the started emulator
window, the settings dialog and Creator (within 2 seconds).

In case of no synchronization we may be starting
new processes in separate thread after the QApplication
has already been destroyed. Blocking run of processes
requires constructing an event loop, which ends up with
the following warnings (and a deadlock):
"QEventLoop: Cannot be used without QApplication"
"QObject::setParent: Cannot set parent,
 new parent is in a different thread".

Add returned future to the global future synchronizer.
Avoid long synchronization of startAvd() on shutdown
by adding an optional future argument to the startAvd() and
pass it to the waitForAvd(). Otherwise, we would need to wait
up to 2 minutes for synchronization to finish.

Change-Id: Ia1d71d309cb2dcdb5e31decfdfdea6df9a117ed5
Reviewed-by: Alessandro Portale <alessandro.portale@qt.io>
This commit is contained in:
Jarek Kobus
2024-06-26 11:19:26 +02:00
parent dfbe37542d
commit 057a54364d
3 changed files with 6 additions and 7 deletions

View File

@@ -20,10 +20,10 @@ namespace Android::Internal::AndroidAvdManager {
static Q_LOGGING_CATEGORY(avdManagerLog, "qtc.android.avdManager", QtWarningMsg)
QString startAvd(const QString &name)
QString startAvd(const QString &name, const std::optional<QFuture<void>> &future)
{
if (!findAvd(name).isEmpty() || startAvdAsync(name))
return waitForAvd(name);
return waitForAvd(name, future);
return {};
}

View File

@@ -8,7 +8,7 @@
namespace Android::Internal::AndroidAvdManager {
QString startAvd(const QString &name);
QString startAvd(const QString &name, const std::optional<QFuture<void>> &future = {});
bool startAvdAsync(const QString &avdName);
QString findAvd(const QString &avdName);
QString waitForAvd(const QString &avdName, const std::optional<QFuture<void>> &future = {});

View File

@@ -111,15 +111,14 @@ static void startAvd(const IDevice::Ptr &device, QWidget *parent)
const AndroidDevice *androidDev = static_cast<const AndroidDevice *>(device.get());
const QString name = androidDev->avdName();
qCDebug(androidDeviceLog, "Starting Android AVD id \"%s\".", qPrintable(name));
auto future = Utils::asyncRun([name, device] {
const QString serialNumber = AndroidAvdManager::startAvd(name);
Utils::futureSynchronizer()->addFuture(Utils::asyncRun([name, device](QPromise<void> &promise) {
const QString serialNumber = AndroidAvdManager::startAvd(name, promise.future());
// Mark the AVD as ReadyToUse once we know it's started
if (!serialNumber.isEmpty()) {
DeviceManager *const devMgr = DeviceManager::instance();
devMgr->setDeviceState(device->id(), IDevice::DeviceReadyToUse);
}
});
// TODO: use future!
}));
}
static void setEmulatorArguments(QWidget *parent)