From ccc5359c420dd8388cb5a55aa0948b9297027b08 Mon Sep 17 00:00:00 2001 From: Vikas Pachdha Date: Mon, 19 Dec 2016 18:41:00 +0100 Subject: [PATCH] iOS: Remove the step to spawn the app before launch on iOS This causes timing issues on certain devices resulting in app startup failure Task-number: QTCREATORBUG-17336 Change-Id: I190b5415bdef1fc80a415b0cb872b95b883db5d8 Reviewed-by: Eike Ziller --- src/plugins/ios/iosdebugsupport.cpp | 2 - src/plugins/ios/iosrunner.cpp | 6 - src/plugins/ios/iosrunner.h | 3 - src/plugins/ios/iostoolhandler.cpp | 135 ++++++--------------- src/plugins/ios/iostoolhandler.h | 1 - src/plugins/ios/simulatorcontrol.cpp | 172 ++++----------------------- src/plugins/ios/simulatorcontrol.h | 9 +- 7 files changed, 59 insertions(+), 269 deletions(-) diff --git a/src/plugins/ios/iosdebugsupport.cpp b/src/plugins/ios/iosdebugsupport.cpp index 754061d0153..b715ab0b29b 100644 --- a/src/plugins/ios/iosdebugsupport.cpp +++ b/src/plugins/ios/iosdebugsupport.cpp @@ -169,8 +169,6 @@ IosDebugSupport::IosDebugSupport(IosRunConfiguration *runConfig, m_runner, &IosRunner::start); connect(m_runControl, &RunControl::finished, m_runner, &IosRunner::stop); - connect(m_runControl, &DebuggerRunControl::stateChanged, - m_runner, &IosRunner::debuggerStateChanged); connect(m_runner, &IosRunner::gotServerPorts, this, &IosDebugSupport::handleServerPorts); diff --git a/src/plugins/ios/iosrunner.cpp b/src/plugins/ios/iosrunner.cpp index c8ece1871b9..75f83a243e2 100644 --- a/src/plugins/ios/iosrunner.cpp +++ b/src/plugins/ios/iosrunner.cpp @@ -168,12 +168,6 @@ void IosRunner::stop() } } -void IosRunner::debuggerStateChanged(Debugger::DebuggerState state) -{ - if (m_toolHandler) - m_toolHandler->debuggerStateChanged(state); -} - void IosRunner::handleDidStartApp(IosToolHandler *handler, const QString &bundlePath, const QString &deviceId, IosToolHandler::OpStatus status) { diff --git a/src/plugins/ios/iosrunner.h b/src/plugins/ios/iosrunner.h index d798b521eb2..2d41ff45457 100644 --- a/src/plugins/ios/iosrunner.h +++ b/src/plugins/ios/iosrunner.h @@ -65,9 +65,6 @@ public: void start(); void stop(); -public slots: - void debuggerStateChanged(Debugger::DebuggerState state); - signals: void didStartApp(Ios::IosToolHandler::OpStatus status); void gotServerPorts(Utils::Port gdbPort, Utils::Port qmlPort); diff --git a/src/plugins/ios/iostoolhandler.cpp b/src/plugins/ios/iostoolhandler.cpp index 3af160caddc..b12df9be16c 100644 --- a/src/plugins/ios/iostoolhandler.cpp +++ b/src/plugins/ios/iostoolhandler.cpp @@ -34,6 +34,7 @@ #include #include #include "utils/runextensions.h" +#include "utils/synchronousprocess.h" #include #include @@ -141,7 +142,6 @@ public: bool isRunning(); void start(const QString &exe, const QStringList &args); virtual void stop(int errorCode) = 0; - virtual void debuggerStateChanged(Debugger::DebuggerState state) { Q_UNUSED(state); } // signals void isTransferringApp(const QString &bundlePath, const QString &deviceId, int progress, @@ -230,20 +230,10 @@ private: * YES | * | | * v | - * +---------+-------------------------+ | - * | SimulatorControl::spawnAppProcess | <------------------+ - * +-----------------------------------+ - * | - * v - * +--------+-----------+ +-----------------------------+ - * | Debug Run ? +---YES------> + Wait for debugger to attach | - * +---------+----------+ +-----------+-----------------+ - * NO | - * | | - * v | - * +-----------------------------+ | - * | SimulatorControl::launchApp | <-------------------+ - * +-----------------------------+ + * +---------+------------------------------+ | + * | SimulatorControl::launchAppOnSimulator | <-------------+ + * +----------------------------------------+ + * ***************************************************************************/ class IosSimulatorToolHandlerPrivate : public IosToolHandlerPrivate { @@ -260,15 +250,11 @@ public: const QString &deviceIdentifier, int timeout = 1000) override; void requestDeviceInfo(const QString &deviceId, int timeout = 1000) override; void stop(int errorCode) override; - void debuggerStateChanged(Debugger::DebuggerState state) override; private: void installAppOnSimulator(); - void spawnAppOnSimulator(const QStringList &extraArgs); - void launchAppOnSimulator(); - + void launchAppOnSimulator(const QStringList &extraArgs); bool isResponseValid(const SimulatorControl::ResponseData &responseData); - void onResponseAppSpawn(const SimulatorControl::ResponseData &response); void simAppProcessError(QProcess::ProcessError error); void simAppProcessFinished(int exitCode, QProcess::ExitStatus exitStatus); @@ -277,7 +263,6 @@ private: private: qint64 appPId = -1; - bool appLaunched = false; SimulatorControl *simCtl; QList> futureList; }; @@ -802,7 +787,7 @@ void IosSimulatorToolHandlerPrivate::requestRunApp(const QString &appBundlePath, if (isResponseValid(response)) return; if (response.success) { - spawnAppOnSimulator(extraArgs); + launchAppOnSimulator(extraArgs); } else { errorMsg(IosToolHandler::tr("Application launch on Simulator failed. Simulator not running.") .arg(bundlePath)); @@ -811,7 +796,7 @@ void IosSimulatorToolHandlerPrivate::requestRunApp(const QString &appBundlePath, }; if (SimulatorControl::isSimulatorRunning(deviceId)) - spawnAppOnSimulator(extraArgs); + launchAppOnSimulator(extraArgs); else futureList << Utils::onResultReady(simCtl->startSimulator(deviceId), onSimulatorStart); } @@ -824,14 +809,7 @@ void IosSimulatorToolHandlerPrivate::requestDeviceInfo(const QString &deviceId, void IosSimulatorToolHandlerPrivate::stop(int errorCode) { - - if (process) { - QTC_ASSERT(process.unique(), process->kill(); qCDebug(toolHandlerLog)<<"App process is not unique."); - process.reset(); - appPId = -1; - appLaunched = false; - } - + appPId = -1; foreach (auto f, futureList) { if (!f.isFinished()) f.cancel(); @@ -841,14 +819,6 @@ void IosSimulatorToolHandlerPrivate::stop(int errorCode) q->finished(q); } -void IosSimulatorToolHandlerPrivate::debuggerStateChanged(Debugger::DebuggerState state) -{ - if (!appLaunched && state == Debugger::DebuggerState::InferiorRunOk) { - // Debugger attached. Launch it on the simulator. - launchAppOnSimulator(); - } -} - void IosSimulatorToolHandlerPrivate::installAppOnSimulator() { auto onResponseAppInstall = [this](const SimulatorControl::ResponseData &response) { @@ -871,23 +841,32 @@ void IosSimulatorToolHandlerPrivate::installAppOnSimulator() onResponseAppInstall); } -void IosSimulatorToolHandlerPrivate::spawnAppOnSimulator(const QStringList &extraArgs) +void IosSimulatorToolHandlerPrivate::launchAppOnSimulator(const QStringList &extraArgs) { - Utils::FileName appBundle = Utils::FileName::fromString(bundlePath); - bool debugRun = runKind == IosToolHandler::DebugRun; - futureList << Utils::onResultReady(simCtl->spawnAppProcess(deviceId, appBundle, debugRun, extraArgs), - std::bind(&IosSimulatorToolHandlerPrivate::onResponseAppSpawn, this, _1)); -} + auto monitorPid = [this](QFutureInterface &fi, qint64 pid) { + int exitCode = 0; + const QStringList args({QStringLiteral("-0"), QString::number(pid)}); + Utils::SynchronousProcess pKill; + while (!fi.isCanceled() && exitCode == 0) { + // Poll every 1 sec to check whether the app is running. + QThread::msleep(1000); + Utils::SynchronousProcessResponse resp = pKill.runBlocking(QStringLiteral("kill"), args); + exitCode = resp.exitCode; + } + // Future is cancelled if the app is stopped from the qt creator. + if (!fi.isCanceled()) + stop(0); + }; -void IosSimulatorToolHandlerPrivate::launchAppOnSimulator() -{ - auto onResponseAppLaunch = [this](const SimulatorControl::ResponseData &response) { + auto onResponseAppLaunch = [this, monitorPid](const SimulatorControl::ResponseData &response) { if (!isResponseValid(response)) return; - - if (response.pID != -1) { - appLaunched = true; + if (response.success) { + appPId = response.pID; + gotInferiorPid(bundlePath, deviceId, appPId); didStartApp(bundlePath, deviceId, Ios::IosToolHandler::Success); + // Start monitoring app's life signs. + futureList << Utils::runAsync(monitorPid, appPId); } else { errorMsg(IosToolHandler::tr("Application launch on Simulator failed. %1") .arg(QString::fromLocal8Bit(response.commandOutput))); @@ -896,16 +875,11 @@ void IosSimulatorToolHandlerPrivate::launchAppOnSimulator() q->finished(q); } }; - - if (appPId != -1) { - Utils::FileName appBundle = Utils::FileName::fromString(bundlePath); - futureList << Utils::onResultReady(simCtl->launchApp(deviceId, - SimulatorControl::bundleIdentifier(appBundle), appPId), - onResponseAppLaunch); - } else { - errorMsg(IosToolHandler::tr("Spawning the Application process on Simulator failed. Spawning timed out.")); - didStartApp(bundlePath, deviceId, Ios::IosToolHandler::Failure); - } + Utils::FileName appBundle = Utils::FileName::fromString(bundlePath); + futureList << Utils::onResultReady(simCtl->launchApp(deviceId, + SimulatorControl::bundleIdentifier(appBundle), + runKind == IosToolHandler::DebugRun, + extraArgs), onResponseAppLaunch); } bool IosSimulatorToolHandlerPrivate::isResponseValid(const SimulatorControl::ResponseData &responseData) @@ -921,40 +895,6 @@ bool IosSimulatorToolHandlerPrivate::isResponseValid(const SimulatorControl::Res return true; } -void IosSimulatorToolHandlerPrivate::onResponseAppSpawn(const SimulatorControl::ResponseData &response) -{ - if (!isResponseValid(response)) - return; - - if (response.processInstance) { - QTC_ASSERT(!process || !isRunning(), - qCDebug(toolHandlerLog) << "Spwaning app while an app instance exits."); - process = response.processInstance; - QObject::connect(process.get(), &QProcess::readyReadStandardOutput, - std::bind(&IosSimulatorToolHandlerPrivate::simAppProcessHasData, this)); - QObject::connect(process.get(), &QProcess::readyReadStandardError, - std::bind(&IosSimulatorToolHandlerPrivate::simAppProcessHasErrorOutput, this)); - QObject::connect(process.get(), static_cast(&QProcess::finished), - std::bind(&IosSimulatorToolHandlerPrivate::simAppProcessFinished, this, _1, _2)); - QObject::connect(process.get(), &QProcess::errorOccurred, - std::bind(&IosSimulatorToolHandlerPrivate::simAppProcessError, this, _1)); - - appPId = response.pID; - gotInferiorPid(bundlePath, deviceId, appPId); - - // For normal run. Launch app on Simulator. - // For debug run, wait for the debugger to attach and then launch the app. - if (runKind == IosToolHandler::NormalRun) - launchAppOnSimulator(); - } else { - errorMsg(IosToolHandler::tr("Spawning the Application process on Simulator failed. %1") - .arg(QString::fromLocal8Bit(response.commandOutput))); - didStartApp(bundlePath, deviceId, Ios::IosToolHandler::Failure); - stop(-1); - q->finished(q); - } -} - void IosSimulatorToolHandlerPrivate::simAppProcessError(QProcess::ProcessError error) { errorMsg(IosToolHandler::tr("Simulator application process error %1").arg(error)); @@ -1010,11 +950,6 @@ void IosToolHandler::stop() d->stop(-1); } -void IosToolHandler::debuggerStateChanged(int state) -{ - d->debuggerStateChanged((Debugger::DebuggerState)state); -} - void IosToolHandler::requestTransferApp(const QString &bundlePath, const QString &deviceId, int timeout) { diff --git a/src/plugins/ios/iostoolhandler.h b/src/plugins/ios/iostoolhandler.h index df1cd089cf8..d2243a932ec 100644 --- a/src/plugins/ios/iostoolhandler.h +++ b/src/plugins/ios/iostoolhandler.h @@ -64,7 +64,6 @@ public: void requestDeviceInfo(const QString &deviceId, int timeout = 1000); bool isRunning(); void stop(); - void debuggerStateChanged(int state); signals: void isTransferringApp(Ios::IosToolHandler *handler, const QString &bundlePath, diff --git a/src/plugins/ios/simulatorcontrol.cpp b/src/plugins/ios/simulatorcontrol.cpp index e3436d9fb01..005ef882b59 100644 --- a/src/plugins/ios/simulatorcontrol.cpp +++ b/src/plugins/ios/simulatorcontrol.cpp @@ -42,13 +42,7 @@ #include #include #include -#include #include -#include -#include -#include -#include -#include using namespace std; @@ -63,7 +57,7 @@ static int COMMAND_TIMEOUT = 10000; static int SIMULATOR_START_TIMEOUT = 60000; static QString SIM_UDID_TAG = QStringLiteral("SimUdid"); -static bool checkForTimeout(const chrono::time_point< chrono::high_resolution_clock, chrono::nanoseconds> &start, int msecs = COMMAND_TIMEOUT) +static bool checkForTimeout(const chrono::high_resolution_clock::time_point &start, int msecs = COMMAND_TIMEOUT) { bool timedOut = false; auto end = chrono::high_resolution_clock::now(); @@ -75,6 +69,7 @@ static bool checkForTimeout(const chrono::time_point< chrono::high_resolution_cl static bool runCommand(QString command, const QStringList &args, QByteArray *output) { Utils::SynchronousProcess p; + p.setTimeoutS(-1); Utils::SynchronousProcessResponse resp = p.runBlocking(command, args); if (output) *output = resp.allRawOutput(); @@ -89,31 +84,6 @@ static QByteArray runSimCtlCommand(QStringList args) return output; } -static bool waitForProcessSpawn(qint64 processPId, QFutureInterface &fi) -{ - bool success = false; - if (processPId != -1) { - // Wait for app to reach intruptible sleep state. - const QStringList args = {QStringLiteral("-p"), QString::number(processPId), - QStringLiteral("-o"), QStringLiteral("wq=")}; - int wqCount = -1; - QByteArray wqStr; - auto begin = chrono::high_resolution_clock::now(); - do { - if (fi.isCanceled() || !runCommand(QStringLiteral("ps"), args, &wqStr)) - break; - bool validInt = false; - wqCount = wqStr.trimmed().toInt(&validInt); - if (!validInt) - wqCount = -1; - } while (wqCount < 0 && !checkForTimeout(begin)); - success = wqCount >= 0; - } else { - qCDebug(simulatorLog) << "Wait for spawned failed. Invalid Process ID." << processPId; - } - return success; -} - class SimulatorControlPrivate { private: struct SimDeviceInfo { @@ -136,11 +106,9 @@ private: void startSimulator(QFutureInterface &fi, const QString &simUdid); void installApp(QFutureInterface &fi, const QString &simUdid, const Utils::FileName &bundlePath); - void spawnAppProcess(QFutureInterface &fi, const QString &simUdid, - const Utils::FileName &bundlePath, bool waitForDebugger, QStringList extraArgs, - QThread *mainThread); void launchApp(QFutureInterface &fi, const QString &simUdid, - const QString &bundleIdentifier, qint64 spawnedPID); + const QString &bundleIdentifier, bool waitForDebugger, + const QStringList &extraArgs); static QList availableDevices; friend class SimulatorControl; @@ -215,7 +183,7 @@ QString SimulatorControl::bundleExecutable(const Utils::FileName &bundlePath) return SimulatorControlPrivate::bundleExecutable(bundlePath); } -QFuture SimulatorControl::startSimulator(const QString &simUdid) +QFuture SimulatorControl::startSimulator(const QString &simUdid) const { return Utils::runAsync(&SimulatorControlPrivate::startSimulator, d, simUdid); } @@ -226,20 +194,12 @@ SimulatorControl::installApp(const QString &simUdid, const Utils::FileName &bund return Utils::runAsync(&SimulatorControlPrivate::installApp, d, simUdid, bundlePath); } -QFuture -SimulatorControl::spawnAppProcess(const QString &simUdid, const Utils::FileName &bundlePath, - bool waitForDebugger, const QStringList &extraArgs) const -{ - return Utils::runAsync(&SimulatorControlPrivate::spawnAppProcess, d, simUdid, bundlePath, - waitForDebugger, extraArgs, QThread::currentThread()); -} - QFuture SimulatorControl::launchApp(const QString &simUdid, const QString &bundleIdentifier, - qint64 spawnedPID) const + bool waitForDebugger, const QStringList &extraArgs) const { - return Utils::runAsync(&SimulatorControlPrivate::launchApp, d, simUdid, - bundleIdentifier, spawnedPID); + return Utils::runAsync(&SimulatorControlPrivate::launchApp, d, simUdid, bundleIdentifier, + waitForDebugger, extraArgs); } QList SimulatorControlPrivate::availableDevices; @@ -362,7 +322,6 @@ void SimulatorControlPrivate::startSimulator(QFutureInterface &fi, - const QString &simUdid, const Utils::FileName &bundlePath, - bool waitForDebugger, QStringList extraArgs, QThread *mainThread) -{ - SimulatorControl::ResponseData response(simUdid); - - // Find the path of the installed app. - QString bundleId = bundleIdentifier(bundlePath); - QByteArray appContainer = runSimCtlCommand({QStringLiteral("get_app_container"), simUdid, bundleId}); - QString appPath = QString::fromLocal8Bit(appContainer.trimmed()); - - if (fi.isCanceled()) - return; - - QString executableName = bundleExecutable(bundlePath); - if (!appPath.isEmpty() && !executableName.isEmpty()) { - appPath.append('/' + executableName); - QStringList args = {QStringLiteral("simctl"), QStringLiteral("spawn"), simUdid, appPath}; - if (waitForDebugger) - args.insert(2, QStringLiteral("-w")); - args << extraArgs; - - // Spawn the app. The spawned app is started in suspended mode. - shared_ptr simCtlProcess(new QProcess, [](QProcess *p) { - if (p->state() != QProcess::NotRunning) { - p->kill(); - p->waitForFinished(COMMAND_TIMEOUT); - } - delete p; - }); - simCtlProcess->start(QStringLiteral("xcrun"), args); - if (simCtlProcess->waitForStarted()) { - if (fi.isCanceled()) - return; - // Find the process id of the spawned app. - qint64 simctlPId = simCtlProcess->processId(); - QByteArray commandOutput; - const QStringList pGrepArgs = {QStringLiteral("-f"), appPath}; - auto begin = chrono::high_resolution_clock::now(); - int processID = -1; - while (processID == -1 && runCommand(QStringLiteral("pgrep"), pGrepArgs, &commandOutput)) { - if (fi.isCanceled()) { - qCDebug(simulatorLog) <<"Spawning the app failed. Future cancelled."; - return; - } - foreach (auto pidStr, commandOutput.trimmed().split('\n')) { - qint64 parsedPId = pidStr.toLongLong(); - if (parsedPId != simctlPId) - processID = parsedPId; - } - if (checkForTimeout(begin)) { - qCDebug(simulatorLog) << "Spawning the app failed. Process timed out"; - break; - } - } - - if (processID == -1) { - qCDebug(simulatorLog) << "Spawning the app failed. App PID not found."; - simCtlProcess->waitForReadyRead(COMMAND_TIMEOUT); - response.commandOutput = simCtlProcess->readAllStandardError(); - } else { - response.processInstance = simCtlProcess; - response.processInstance->moveToThread(mainThread); - response.pID = processID; - response.success = true; - } - } else { - qCDebug(simulatorLog) << "Spawning the app failed." << simCtlProcess->errorString(); - response.commandOutput = simCtlProcess->errorString().toLatin1(); - } - } else { - qCDebug(simulatorLog) << "Spawning the app failed. Check installed app." << appPath; - } - - if (!fi.isCanceled()) { - QThread::msleep(500); // give it some time. TODO: find an actual fix. fi.reportResult(response); } } void SimulatorControlPrivate::launchApp(QFutureInterface &fi, const QString &simUdid, const QString &bundleIdentifier, - qint64 spawnedPID) + bool waitForDebugger, const QStringList &extraArgs) { SimulatorControl::ResponseData response(simUdid); - if (!bundleIdentifier.isEmpty()) { - bool processSpawned = true; - // Wait for the process to be spawned properly before launching app. - if (spawnedPID > -1) - processSpawned = waitForProcessSpawn(spawnedPID, fi); + if (!bundleIdentifier.isEmpty() && !fi.isCanceled()) { + QStringList args({QStringLiteral("launch"), simUdid, bundleIdentifier}); - if (fi.isCanceled()) - return; + if (waitForDebugger) + args.insert(1, QStringLiteral("-w")); - if (processSpawned) { - QThread::msleep(500); // give it some time. TODO: find an actual fix. - const QStringList args({QStringLiteral("launch"), simUdid , bundleIdentifier}); - response.commandOutput = runSimCtlCommand(args); - const QByteArray pIdStr = response.commandOutput.trimmed().split(' ').last().trimmed(); - bool validInt = false; - response.pID = pIdStr.toLongLong(&validInt); - if (!validInt) { - // Launch Failed. - qCDebug(simulatorLog) << "Launch app failed. Process id returned is not valid. PID =" << pIdStr; - response.pID = -1; - } + foreach (const QString extraArgument, extraArgs) { + if (!extraArgument.trimmed().isEmpty()) + args << extraArgument; } + + response.commandOutput = runSimCtlCommand(args); + const QByteArray pIdStr = response.commandOutput.trimmed().split(' ').last().trimmed(); + bool validPid = false; + response.pID = pIdStr.toLongLong(&validPid); + response.success = validPid; } if (!fi.isCanceled()) { diff --git a/src/plugins/ios/simulatorcontrol.h b/src/plugins/ios/simulatorcontrol.h index 2c3b2cd296e..5082a437dad 100644 --- a/src/plugins/ios/simulatorcontrol.h +++ b/src/plugins/ios/simulatorcontrol.h @@ -52,9 +52,6 @@ public: bool success = false; qint64 pID = -1; QByteArray commandOutput = ""; - // For response type APP_SPAWN, the processInstance represents the control process of the spwaned app - // For other response types its null. - std::shared_ptr processInstance; }; public: @@ -69,12 +66,10 @@ public: static QString bundleExecutable(const Utils::FileName &bundlePath); public: - QFuture startSimulator(const QString &simUdid); + QFuture startSimulator(const QString &simUdid) const; QFuture installApp(const QString &simUdid, const Utils::FileName &bundlePath) const; - QFuture spawnAppProcess(const QString &simUdid, const Utils::FileName &bundlePath, - bool waitForDebugger, const QStringList &extraArgs) const; QFuture launchApp(const QString &simUdid, const QString &bundleIdentifier, - qint64 spawnedPID = -1) const; + bool waitForDebugger, const QStringList &extraArgs) const; private: SimulatorControlPrivate *d;