diff --git a/src/libs/utils/launchersocket.cpp b/src/libs/utils/launchersocket.cpp index 0a6e7b54dd4..d4c5a5dcbdb 100644 --- a/src/libs/utils/launchersocket.cpp +++ b/src/libs/utils/launchersocket.cpp @@ -23,6 +23,7 @@ ** ****************************************************************************/ +#include "algorithm.h" #include "launchersocket.h" #include "launcherinterface.h" @@ -36,86 +37,551 @@ namespace Utils { namespace Internal { -class CallerHandle : public QObject +class LauncherSignal { - Q_OBJECT public: - // Called from caller's thread exclusively, lives in caller's thread. - CallerHandle() : QObject() {} + CallerHandle::SignalType signalType() const { return m_signalType; } +protected: + LauncherSignal(CallerHandle::SignalType signalType) : m_signalType(signalType) {} +private: + const CallerHandle::SignalType m_signalType; +}; - // Called from caller's thread exclusively. Returns the list of flushed signals. - QList flush() +class ErrorSignal : public LauncherSignal +{ +public: + ErrorSignal(QProcess::ProcessError error, const QString &errorString) + : LauncherSignal(CallerHandle::SignalType::Error) + , m_error(error) + , m_errorString(errorString) {} + QProcess::ProcessError error() const { return m_error; } + QString errorString() const { return m_errorString; } +private: + const QProcess::ProcessError m_error; + const QString m_errorString; +}; + +class StartedSignal : public LauncherSignal +{ +public: + StartedSignal(int processId) + : LauncherSignal(CallerHandle::SignalType::Started) + , m_processId(processId) {} + int processId() const { return m_processId; } +private: + const int m_processId; +}; + +class ReadyReadSignal : public LauncherSignal +{ +public: + ReadyReadSignal(const QByteArray &stdOut, const QByteArray &stdErr) + : LauncherSignal(CallerHandle::SignalType::ReadyRead) + , m_stdOut(stdOut) + , m_stdErr(stdErr) {} + QByteArray stdOut() const { return m_stdOut; } + QByteArray stdErr() const { return m_stdErr; } +private: + const QByteArray m_stdOut; + const QByteArray m_stdErr; +}; + +class FinishedSignal : public LauncherSignal +{ +public: + FinishedSignal(QProcess::ExitStatus exitStatus, + int exitCode) + : LauncherSignal(CallerHandle::SignalType::Finished) + , m_exitStatus(exitStatus) + , m_exitCode(exitCode) {} + QProcess::ExitStatus exitStatus() const { return m_exitStatus; } + int exitCode() const { return m_exitCode; } +private: + const QProcess::ExitStatus m_exitStatus; + const int m_exitCode; +}; + +bool CallerHandle::waitForStarted(int msecs) +{ + return waitForSignal(msecs, SignalType::Started); +} + +bool CallerHandle::waitForReadyRead(int msces) +{ + return waitForSignal(msces, SignalType::ReadyRead); +} + +bool CallerHandle::waitForFinished(int msecs) +{ + return waitForSignal(msecs, SignalType::Finished); +} + +QList CallerHandle::flush() +{ + return flushFor(CallerHandle::SignalType::NoSignal); +} + +QList CallerHandle::flushFor(CallerHandle::SignalType signalType) +{ + QTC_ASSERT(isCalledFromCallersThread(), return {}); + QList oldSignals; + QList flushedSignals; { - QTC_ASSERT(isCalledFromCallersThread(), return {}); - QList oldSignals; - { - QMutexLocker locker(&m_mutex); + // 1. If signalType is no signal - flush all + // 2. Flush all if we have any error + // 3. If we are flushing for Finished, flush Started / ReadyRead, too + // 4. If we are flushing for ReadyRead, flush Started, too + // 5. (?) If we have collected Finished, flush it only when flushing + // for ReadyRead / Finished (not for Started) + + QMutexLocker locker(&m_mutex); + + const QList storedSignals = + Utils::transform(qAsConst(m_signals), [](const LauncherSignal *launcherSignal) { + return launcherSignal->signalType(); + }); + + const bool flushAll = (signalType == CallerHandle::SignalType::NoSignal) + || (signalType == CallerHandle::SignalType::Finished) + || storedSignals.contains(CallerHandle::SignalType::Error); + if (flushAll) { oldSignals = m_signals; m_signals = {}; - } - for (LauncherHandle::SignalType signalType : qAsConst(oldSignals)) { - switch (signalType) { - case LauncherHandle::SignalType::NoSignal: - break; - case LauncherHandle::SignalType::Error: - emit errorOccurred(); - break; - case LauncherHandle::SignalType::Started: - emit started(); - break; - case LauncherHandle::SignalType::ReadyRead: - emit readyRead(); - break; - case LauncherHandle::SignalType::Finished: - emit finished(); - break; + flushedSignals = storedSignals; + } else { + auto matchingIndex = storedSignals.lastIndexOf(signalType); + if (matchingIndex < 0 && (signalType == CallerHandle::SignalType::ReadyRead)) + matchingIndex = storedSignals.lastIndexOf(CallerHandle::SignalType::Started); + if (matchingIndex >= 0) { + oldSignals = m_signals.mid(0, matchingIndex + 1); + m_signals = m_signals.mid(matchingIndex + 1); + flushedSignals = storedSignals.mid(0, matchingIndex + 1); } } - return oldSignals; } - // Called from caller's thread exclusively. - bool shouldFlushFor(LauncherHandle::SignalType signalType) - { - QTC_ASSERT(isCalledFromCallersThread(), return false); - // TODO: Should we always flush when the list isn't empty? - QMutexLocker locker(&m_mutex); - if (m_signals.contains(signalType)) + for (const LauncherSignal *storedSignal : qAsConst(oldSignals)) { + const CallerHandle::SignalType storedSignalType = storedSignal->signalType(); + switch (storedSignalType) { + case SignalType::NoSignal: + break; + case SignalType::Error: + handleError(static_cast(storedSignal)); + break; + case SignalType::Started: + handleStarted(static_cast(storedSignal)); + break; + case SignalType::ReadyRead: + handleReadyRead(static_cast(storedSignal)); + break; + case SignalType::Finished: + handleFinished(static_cast(storedSignal)); + break; + } + delete storedSignal; + } + return flushedSignals; +} + +// Called from caller's thread exclusively. +bool CallerHandle::shouldFlushFor(SignalType signalType) const +{ + QTC_ASSERT(isCalledFromCallersThread(), return false); + // TODO: Should we always flush when the list isn't empty? + QMutexLocker locker(&m_mutex); + for (const LauncherSignal *storedSignal : m_signals) { + const CallerHandle::SignalType storedSignalType = storedSignal->signalType(); + if (storedSignalType == signalType) return true; - if (m_signals.contains(LauncherHandle::SignalType::Error)) + if (storedSignalType == SignalType::Error) return true; - if (m_signals.contains(LauncherHandle::SignalType::Finished)) + if (storedSignalType == SignalType::Finished) return true; + } + return false; +} + +void CallerHandle::handleError(const ErrorSignal *launcherSignal) +{ + QTC_ASSERT(isCalledFromCallersThread(), return); + m_error = launcherSignal->error(); + m_errorString = launcherSignal->errorString(); + emit errorOccurred(m_error); +} + +void CallerHandle::handleStarted(const StartedSignal *launcherSignal) +{ + QTC_ASSERT(isCalledFromCallersThread(), return); + m_processState = QProcess::Running; + m_processId = launcherSignal->processId(); + emit started(); +} + +void CallerHandle::handleReadyRead(const ReadyReadSignal *launcherSignal) +{ + QTC_ASSERT(isCalledFromCallersThread(), return); + m_stdout += launcherSignal->stdOut(); + m_stderr += launcherSignal->stdErr(); + if (!m_stdout.isEmpty()) + emit readyReadStandardOutput(); + if (!m_stderr.isEmpty()) + emit readyReadStandardError(); +} + +void CallerHandle::handleFinished(const FinishedSignal *launcherSignal) +{ + QTC_ASSERT(isCalledFromCallersThread(), return); + m_processState = QProcess::NotRunning; + m_exitStatus = launcherSignal->exitStatus(); + m_exitCode = launcherSignal->exitCode(); + emit finished(m_exitCode, m_exitStatus); +} + +// Called from launcher's thread exclusively. +void CallerHandle::appendSignal(LauncherSignal *launcherSignal) +{ + QTC_ASSERT(!isCalledFromCallersThread(), return); + if (launcherSignal->signalType() == SignalType::NoSignal) + return; + + QMutexLocker locker(&m_mutex); + QTC_ASSERT(isCalledFromLaunchersThread(), return); + // TODO: merge ReadyRead signals into one. + m_signals.append(launcherSignal); +} + +QProcess::ProcessState CallerHandle::state() const +{ + QTC_ASSERT(isCalledFromCallersThread(), return QProcess::NotRunning); + return m_processState; +} + +void CallerHandle::cancel() +{ + QTC_ASSERT(isCalledFromCallersThread(), return); + switch (m_processState.exchange(QProcess::NotRunning)) { + case QProcess::NotRunning: + break; + case QProcess::Starting: + m_errorString = QCoreApplication::translate("Utils::LauncherHandle", + "Process canceled before it was started."); + m_error = QProcess::FailedToStart; + if (LauncherInterface::socket()->isReady()) // TODO: race condition with m_processState??? + sendPacket(StopProcessPacket(m_token)); + else + emit errorOccurred(m_error); + break; + case QProcess::Running: + sendPacket(StopProcessPacket(m_token)); + break; + } + + if (m_launcherHandle) + m_launcherHandle->setCanceled(); +} + +QByteArray CallerHandle::readAllStandardOutput() +{ + QTC_ASSERT(isCalledFromCallersThread(), return {}); + return readAndClear(m_stdout); +} + +QByteArray CallerHandle::readAllStandardError() +{ + QTC_ASSERT(isCalledFromCallersThread(), return {}); + return readAndClear(m_stderr); +} + +qint64 CallerHandle::processId() const +{ + QTC_ASSERT(isCalledFromCallersThread(), return 0); + return m_processId; +} + +QString CallerHandle::errorString() const +{ + QTC_ASSERT(isCalledFromCallersThread(), return {}); + return m_errorString; +} + +void CallerHandle::setErrorString(const QString &str) +{ + QTC_ASSERT(isCalledFromCallersThread(), return); + m_errorString = str; +} + +void CallerHandle::start(const QString &program, const QStringList &arguments, const QByteArray &writeData) +{ + QTC_ASSERT(isCalledFromCallersThread(), return); + if (!m_launcherHandle || m_launcherHandle->isSocketError()) { + m_error = QProcess::FailedToStart; + emit errorOccurred(m_error); + return; + } + m_command = program; + m_arguments = arguments; + // TODO: check if state is not running + // TODO: check if m_canceled is not true + m_writeData = writeData; + auto processLauncherNotStarted = [&program] { + qWarning() << "Trying to start" << program << "while process launcher wasn't started yet."; + }; + QTC_ASSERT(LauncherInterface::isStarted(), processLauncherNotStarted()); + + QMutexLocker locker(&m_mutex); + m_processState = QProcess::Starting; + StartProcessPacket *p = new StartProcessPacket(m_token); + p->command = m_command; + p->arguments = m_arguments; + p->env = m_environment.toStringList(); + p->workingDir = m_workingDirectory; + p->processMode = m_processMode; + p->writeData = m_writeData; + p->channelMode = m_channelMode; + p->standardInputFile = m_standardInputFile; + p->belowNormalPriority = m_belowNormalPriority; + p->nativeArguments = m_nativeArguments; + m_startPacket.reset(p); + if (LauncherInterface::socket()->isReady()) + doStart(); +} + +// Called from caller's or launcher's thread. +void CallerHandle::startIfNeeded() +{ + QMutexLocker locker(&m_mutex); + if (m_processState == QProcess::Starting) + doStart(); +} + +// Called from caller's or launcher's thread. Call me with mutex locked. +void CallerHandle::doStart() +{ + if (!m_startPacket) + return; + sendPacket(*m_startPacket); + m_startPacket.reset(nullptr); +} + +// Called from caller's or launcher's thread. +void CallerHandle::sendPacket(const Internal::LauncherPacket &packet) +{ + LauncherInterface::socket()->sendData(packet.serialize()); +} + +qint64 CallerHandle::write(const QByteArray &data) +{ + QTC_ASSERT(isCalledFromCallersThread(), return -1); + + if (m_processState != QProcess::Running) + return -1; + + WritePacket p(m_token); + p.inputData = data; + sendPacket(p); + return data.size(); +} + +QProcess::ProcessError CallerHandle::error() const +{ + QTC_ASSERT(isCalledFromCallersThread(), return QProcess::UnknownError); + return m_error; +} + +QString CallerHandle::program() const +{ + QTC_ASSERT(isCalledFromCallersThread(), return {}); + return m_command; +} + +void CallerHandle::setStandardInputFile(const QString &fileName) +{ + QTC_ASSERT(isCalledFromCallersThread(), return); + m_standardInputFile = fileName; +} + +void CallerHandle::setProcessChannelMode(QProcess::ProcessChannelMode mode) +{ + QTC_ASSERT(isCalledFromCallersThread(), return); + if (mode != QProcess::SeparateChannels && mode != QProcess::MergedChannels) { + qWarning("setProcessChannelMode: The only supported modes are SeparateChannels and MergedChannels."); + return; + } + m_channelMode = mode; +} + +void CallerHandle::setProcessEnvironment(const QProcessEnvironment &environment) +{ + QTC_ASSERT(isCalledFromCallersThread(), return); + m_environment = environment; +} + +void CallerHandle::setWorkingDirectory(const QString &dir) +{ + QTC_ASSERT(isCalledFromCallersThread(), return); + m_workingDirectory = dir; +} + +QProcess::ExitStatus CallerHandle::exitStatus() const +{ + QTC_ASSERT(isCalledFromCallersThread(), return QProcess::CrashExit); + return m_exitStatus; +} + +void CallerHandle::setBelowNormalPriority() +{ + QTC_ASSERT(isCalledFromCallersThread(), return); + m_belowNormalPriority = true; +} + +void CallerHandle::setNativeArguments(const QString &arguments) +{ + QTC_ASSERT(isCalledFromCallersThread(), return); + m_nativeArguments = arguments; +} + +void CallerHandle::setLowPriority() +{ + QTC_ASSERT(isCalledFromCallersThread(), return); + m_lowPriority = true; // TODO: check me, not passed to the launcher currently +} + +void CallerHandle::setUnixTerminalDisabled() +{ + QTC_ASSERT(isCalledFromCallersThread(), return); + m_unixTerminalDisabled = true; // TODO: check me, not passed to the launcher currently +} + +static void warnAboutWrongSignal(QProcess::ProcessState state, CallerHandle::SignalType newSignal) +{ + qWarning() << "LauncherHandle::doWaitForSignal: Can't wait for" << newSignal << + "while being in" << state << "state."; +} + +bool CallerHandle::waitForSignal(int msecs, CallerHandle::SignalType newSignal) +{ + QTC_ASSERT(isCalledFromCallersThread(), return false); + if (!canWaitFor(newSignal)) return false; - } - // Called from launcher's thread exclusively. - void appendSignal(LauncherHandle::SignalType signalType) - { - QTC_ASSERT(!isCalledFromCallersThread(), return); - if (signalType == LauncherHandle::SignalType::NoSignal) - return; + if (!m_launcherHandle) + return false; + return m_launcherHandle->waitForSignal(msecs, newSignal); +} - QMutexLocker locker(&m_mutex); - if (m_signals.contains(signalType)) - return; - - m_signals.append(signalType); - } -signals: - // Emitted from caller's thread exclusively. - void errorOccurred(); - void started(); - void readyRead(); - void finished(); -private: - // Called from caller's or launcher's thread. - bool isCalledFromCallersThread() const - { - return QThread::currentThread() == thread(); +bool CallerHandle::canWaitFor(SignalType newSignal) const +{ + QTC_ASSERT(isCalledFromCallersThread(), return false); + switch (newSignal) { + case SignalType::Started: + return m_processState == QProcess::Starting; + case SignalType::ReadyRead: + case SignalType::Finished: + return m_processState != QProcess::NotRunning; + default: + break; } + return false; +} - QMutex m_mutex; - QList m_signals; -}; +// Called from caller's or launcher's thread. +bool CallerHandle::isCalledFromCallersThread() const +{ + return QThread::currentThread() == thread(); +} + +// Called from caller's or launcher's thread. Call me with mutex locked. +bool CallerHandle::isCalledFromLaunchersThread() const +{ + if (!m_launcherHandle) + return false; + return QThread::currentThread() == m_launcherHandle->thread(); +} + +// Called from caller's thread exclusively. +bool LauncherHandle::waitForSignal(int msecs, CallerHandle::SignalType newSignal) +{ + QTC_ASSERT(!isCalledFromLaunchersThread(), return false); + QElapsedTimer timer; + timer.start(); + while (true) { + const int remainingMsecs = msecs - timer.elapsed(); + if (remainingMsecs <= 0) + break; + const bool timedOut = !doWaitForSignal(qMax(remainingMsecs, 0), newSignal); + if (timedOut) + break; + m_awaitingShouldContinue = true; // TODO: make it recursive? + const QList flushedSignals = m_callerHandle->flushFor(newSignal); + const bool wasCanceled = !m_awaitingShouldContinue; + m_awaitingShouldContinue = false; + const bool errorOccurred = flushedSignals.contains(CallerHandle::SignalType::Error); + if (errorOccurred) + return false; // apparently QProcess behaves like this in case of error + const bool newSignalFlushed = flushedSignals.contains(newSignal); + if (newSignalFlushed) // so we don't continue waiting + return true; + if (wasCanceled) + return true; // or false? is false only in case of timeout? + if (timer.hasExpired(msecs)) + break; + } + return false; +} + +// Called from caller's thread exclusively. +bool LauncherHandle::doWaitForSignal(int msecs, CallerHandle::SignalType newSignal) +{ + QMutexLocker locker(&m_mutex); + QTC_ASSERT(isCalledFromCallersThread(), return false); + QTC_ASSERT(m_waitingFor == CallerHandle::SignalType::NoSignal, return false); + // It may happen, that after calling start() and before calling waitForStarted() we might have + // reached the Running (or even Finished) state already. In this case we should have + // collected Started (or even Finished) signal to be flushed - so we return true + // and we are going to flush pending signals synchronously. + // It could also happen, that some new readyRead data has appeared, so before we wait for + // more we flush it, too. + if (m_callerHandle->shouldFlushFor(newSignal)) + return true; + + m_waitingFor = newSignal; + const bool ret = m_waitCondition.wait(&m_mutex, msecs); + m_waitingFor = CallerHandle::SignalType::NoSignal; + return ret; +} + +// Called from launcher's thread exclusively. Call me with mutex locked. +void LauncherHandle::wakeUpIfWaitingFor(CallerHandle::SignalType newSignal) +{ + QTC_ASSERT(isCalledFromLaunchersThread(), return); + // TODO: should we always wake up in case m_waitingFor != NoSignal? + // The matching signal came + const bool signalMatched = (m_waitingFor == newSignal); + // E.g. if we are waiting for ReadyRead and we got Finished or Error signal instead -> wake it, too. + const bool finishedOrErrorWhileWaiting = + (m_waitingFor != CallerHandle::SignalType::NoSignal) + && ((newSignal == CallerHandle::SignalType::Finished) || (newSignal == CallerHandle::SignalType::Error)); + // Wake up, flush and continue waiting. + // E.g. when being in waitingForFinished() state and Started or ReadyRead signal came. + const bool continueWaitingAfterFlushing = + ((m_waitingFor == CallerHandle::SignalType::Finished) && (newSignal != CallerHandle::SignalType::Finished)) + || ((m_waitingFor == CallerHandle::SignalType::ReadyRead) && (newSignal == CallerHandle::SignalType::Started)); + const bool shouldWake = signalMatched + || finishedOrErrorWhileWaiting + || continueWaitingAfterFlushing; + + if (shouldWake) + m_waitCondition.wakeOne(); +} + +// Called from launcher's thread exclusively. Call me with mutex locked. +void LauncherHandle::flushCaller() +{ + QTC_ASSERT(isCalledFromLaunchersThread(), return); + if (!m_callerHandle) + return; + + // call in callers thread + QMetaObject::invokeMethod(m_callerHandle, &CallerHandle::flush); +} void LauncherHandle::handlePacket(LauncherPacketType type, const QByteArray &payload) { @@ -145,45 +611,106 @@ void LauncherHandle::handleErrorPacket(const QByteArray &packetData) { QTC_ASSERT(isCalledFromLaunchersThread(), return); QMutexLocker locker(&m_mutex); - wakeUpIfWaitingFor(SignalType::Error); - - const auto packet = LauncherPacket::extractPacket(m_token, packetData); - m_error = packet.error; - m_errorString = packet.errorString; + wakeUpIfWaitingFor(CallerHandle::SignalType::Error); if (!m_callerHandle) return; - m_callerHandle->appendSignal(SignalType::Error); + const auto packet = LauncherPacket::extractPacket(m_token, packetData); + m_callerHandle->appendSignal(new ErrorSignal(packet.error, packet.errorString)); flushCaller(); } -// call me with mutex locked -void LauncherHandle::wakeUpIfWaitingFor(SignalType newSignal) +void LauncherHandle::handleStartedPacket(const QByteArray &packetData) { QTC_ASSERT(isCalledFromLaunchersThread(), return); - // TODO: should we always wake up in case m_waitingFor != NoSignal? - // The matching signal came - const bool signalMatched = (m_waitingFor == newSignal); - // E.g. if we are waiting for ReadyRead and we got Finished or Error signal instead -> wake it, too. - const bool finishedOrErrorWhileWaiting = - (m_waitingFor != SignalType::NoSignal) - && ((newSignal == SignalType::Finished) || (newSignal == SignalType::Error)); - // Wake up, flush and continue waiting. - // E.g. when being in waitingForFinished() state and Started or ReadyRead signal came. - const bool continueWaitingAfterFlushing = - ((m_waitingFor == SignalType::Finished) && (newSignal != SignalType::Finished)) - || ((m_waitingFor == SignalType::ReadyRead) && (newSignal == SignalType::Started)); - const bool shouldWake = signalMatched - || finishedOrErrorWhileWaiting - || continueWaitingAfterFlushing; + QMutexLocker locker(&m_mutex); + wakeUpIfWaitingFor(CallerHandle::SignalType::Started); + if (!m_callerHandle) + return; - if (shouldWake) - m_waitCondition.wakeOne(); + const auto packet = LauncherPacket::extractPacket(m_token, packetData); + m_callerHandle->appendSignal(new StartedSignal(packet.processId)); + flushCaller(); } -void LauncherHandle::sendPacket(const Internal::LauncherPacket &packet) +void LauncherHandle::handleReadyReadStandardOutput(const QByteArray &packetData) { - LauncherInterface::socket()->sendData(packet.serialize()); + QTC_ASSERT(isCalledFromLaunchersThread(), return); + QMutexLocker locker(&m_mutex); + wakeUpIfWaitingFor(CallerHandle::SignalType::ReadyRead); + if (!m_callerHandle) + return; + + const auto packet = LauncherPacket::extractPacket(m_token, packetData); + if (packet.standardChannel.isEmpty()) + return; + + m_callerHandle->appendSignal(new ReadyReadSignal(packet.standardChannel, {})); + flushCaller(); +} + +void LauncherHandle::handleReadyReadStandardError(const QByteArray &packetData) +{ + QTC_ASSERT(isCalledFromLaunchersThread(), return); + QMutexLocker locker(&m_mutex); + wakeUpIfWaitingFor(CallerHandle::SignalType::ReadyRead); + if (!m_callerHandle) + return; + + const auto packet = LauncherPacket::extractPacket(m_token, packetData); + if (packet.standardChannel.isEmpty()) + return; + + m_callerHandle->appendSignal(new ReadyReadSignal({}, packet.standardChannel)); + flushCaller(); +} + +void LauncherHandle::handleFinishedPacket(const QByteArray &packetData) +{ + QTC_ASSERT(isCalledFromLaunchersThread(), return); + QMutexLocker locker(&m_mutex); + wakeUpIfWaitingFor(CallerHandle::SignalType::Finished); + if (!m_callerHandle) + return; + + const auto packet = LauncherPacket::extractPacket(m_token, packetData); + const QByteArray stdOut = packet.stdOut; + const QByteArray stdErr = packet.stdErr; + const QProcess::ProcessError error = packet.error; + const QString errorString = packet.errorString; + + // We assume that if error is UnknownError, everything went fine. + // By default QProcess returns "Unknown error" for errorString() + if (error != QProcess::UnknownError) + m_callerHandle->appendSignal(new ErrorSignal(error, errorString)); + if (!stdOut.isEmpty() || !stdErr.isEmpty()) + m_callerHandle->appendSignal(new ReadyReadSignal(stdOut, stdErr)); + m_callerHandle->appendSignal(new FinishedSignal(packet.exitStatus, packet.exitCode)); + flushCaller(); +} + +void LauncherHandle::handleSocketReady() +{ + QTC_ASSERT(isCalledFromLaunchersThread(), return); + m_socketError = false; + QMutexLocker locker(&m_mutex); + if (m_callerHandle) + m_callerHandle->startIfNeeded(); +} + +void LauncherHandle::handleSocketError(const QString &message) +{ + QTC_ASSERT(isCalledFromLaunchersThread(), return); + m_socketError = true; // TODO: ??? + QMutexLocker locker(&m_mutex); + wakeUpIfWaitingFor(CallerHandle::SignalType::Error); + if (!m_callerHandle) + return; + + const QString errorString = QCoreApplication::translate("Utils::QtcProcess", + "Internal socket error: %1").arg(message); + m_callerHandle->appendSignal(new ErrorSignal(QProcess::FailedToStart, errorString)); + flushCaller(); } bool LauncherHandle::isCalledFromLaunchersThread() const @@ -199,460 +726,6 @@ bool LauncherHandle::isCalledFromCallersThread() const return QThread::currentThread() == m_callerHandle->thread(); } -// call me with mutex locked -void LauncherHandle::flushCaller() -{ - QTC_ASSERT(isCalledFromLaunchersThread(), return); - if (!m_callerHandle) - return; - - // call in callers thread - QMetaObject::invokeMethod(m_callerHandle, &CallerHandle::flush); -} - -void LauncherHandle::handleStartedPacket(const QByteArray &packetData) -{ - QTC_ASSERT(isCalledFromLaunchersThread(), return); - QMutexLocker locker(&m_mutex); - wakeUpIfWaitingFor(SignalType::Started); - m_processState = QProcess::Running; - const auto packet = LauncherPacket::extractPacket(m_token, packetData); - m_processId = packet.processId; - if (!m_callerHandle) - return; - - m_callerHandle->appendSignal(SignalType::Started); - flushCaller(); -} - -void LauncherHandle::handleReadyReadStandardOutput(const QByteArray &packetData) -{ - QTC_ASSERT(isCalledFromLaunchersThread(), return); - QMutexLocker locker(&m_mutex); - wakeUpIfWaitingFor(SignalType::ReadyRead); - const auto packet = LauncherPacket::extractPacket(m_token, packetData); - if (packet.standardChannel.isEmpty()) - return; - - m_stdout += packet.standardChannel; - if (!m_callerHandle) - return; - - m_callerHandle->appendSignal(SignalType::ReadyRead); - flushCaller(); -} - -void LauncherHandle::handleReadyReadStandardError(const QByteArray &packetData) -{ - QTC_ASSERT(isCalledFromLaunchersThread(), return); - QMutexLocker locker(&m_mutex); - wakeUpIfWaitingFor(SignalType::ReadyRead); - const auto packet = LauncherPacket::extractPacket(m_token, packetData); - if (packet.standardChannel.isEmpty()) - return; - - m_stderr += packet.standardChannel; - if (!m_callerHandle) - return; - - m_callerHandle->appendSignal(SignalType::ReadyRead); - flushCaller(); -} - -void LauncherHandle::handleFinishedPacket(const QByteArray &packetData) -{ - QTC_ASSERT(isCalledFromLaunchersThread(), return); - QMutexLocker locker(&m_mutex); - wakeUpIfWaitingFor(SignalType::Finished); - m_processState = QProcess::NotRunning; - const auto packet = LauncherPacket::extractPacket(m_token, packetData); - m_exitCode = packet.exitCode; - m_stdout += packet.stdOut; - m_stderr += packet.stdErr; - m_errorString = packet.errorString; - m_exitStatus = packet.exitStatus; - if (!m_callerHandle) - return; - - if (!m_stdout.isEmpty() || !m_stderr.isEmpty()) - m_callerHandle->appendSignal(SignalType::ReadyRead); - m_callerHandle->appendSignal(SignalType::Finished); - flushCaller(); -} - -void LauncherHandle::handleSocketReady() -{ - QTC_ASSERT(isCalledFromLaunchersThread(), return); - QMutexLocker locker(&m_mutex); - m_socketError = false; - if (m_processState == QProcess::Starting) - doStart(); -} - -void LauncherHandle::handleSocketError(const QString &message) -{ - QTC_ASSERT(isCalledFromLaunchersThread(), return); - QMutexLocker locker(&m_mutex); - m_socketError = true; - m_errorString = QCoreApplication::translate("Utils::QtcProcess", - "Internal socket error: %1").arg(message); - if (m_processState != QProcess::NotRunning) { - m_error = QProcess::FailedToStart; - emit errorOccurred(m_error); - } -} - -bool LauncherHandle::waitForSignal(int msecs, SignalType newSignal) -{ - QElapsedTimer timer; - timer.start(); - while (true) { - const int remainingMsecs = msecs - timer.elapsed(); - if (remainingMsecs <= 0) - break; - const bool timedOut = !doWaitForSignal(qMax(remainingMsecs, 0), newSignal); - if (timedOut) - break; - m_awaitingShouldContinue = true; // TODO: make it recursive? - const QList flushedSignals = m_callerHandle->flush(); - const bool wasCanceled = !m_awaitingShouldContinue; - m_awaitingShouldContinue = false; - const bool errorOccurred = flushedSignals.contains(SignalType::Error); - if (errorOccurred) - return false; // apparently QProcess behaves like this in case of error - const bool newSignalFlushed = flushedSignals.contains(newSignal); - if (newSignalFlushed) // so we don't continue waiting - return true; - if (wasCanceled) - return true; // or false? is false only in case of timeout? - if (timer.hasExpired(msecs)) - break; - } - return false; -} - -static void warnAboutWrongSignal(QProcess::ProcessState state, LauncherHandle::SignalType newSignal) -{ - qWarning() << "LauncherHandle::doWaitForSignal: Can't wait for" << newSignal << - "while being in" << state << "state."; -} - -bool LauncherHandle::doWaitForSignal(int msecs, SignalType newSignal) -{ - QMutexLocker locker(&m_mutex); - QTC_ASSERT(isCalledFromCallersThread(), return false); - QTC_ASSERT(m_waitingFor == SignalType::NoSignal, return false); - // It may happen, that after calling start() and before calling waitForStarted() we might have - // reached the Running (or even Finished) state already. In this case we should have - // collected Started (or even Finished) signal to be flushed - so we return true - // and we are going to flush pending signals synchronously. - // It could also happen, that some new readyRead data has appeared, so before we wait for - // more we flush it, too. - if (m_callerHandle->shouldFlushFor(newSignal)) - return true; - - if (canWaitFor(newSignal)) { // e.g. can't wait for started if we are in not running state. - m_waitingFor = newSignal; - const bool ret = m_waitCondition.wait(&m_mutex, msecs)/* && !m_failed*/; - m_waitingFor = SignalType::NoSignal; - return ret; - } - // Can't wait for passed signal, should never happen. - QTC_ASSERT(false, warnAboutWrongSignal(m_processState, newSignal)); - return false; -} - -// call me with mutex locked -bool LauncherHandle::canWaitFor(SignalType newSignal) const -{ - QTC_ASSERT(isCalledFromCallersThread(), return false); - switch (newSignal) { - case SignalType::Started: - return m_processState == QProcess::Starting; - case SignalType::ReadyRead: - case SignalType::Finished: - return m_processState != QProcess::NotRunning; - default: - break; - } - return false; -} - -QProcess::ProcessState LauncherHandle::state() const -{ - QMutexLocker locker(&m_mutex); - QTC_ASSERT(isCalledFromCallersThread(), return QProcess::NotRunning); - return m_processState; -} - -void LauncherHandle::cancel() -{ - QMutexLocker locker(&m_mutex); - QTC_ASSERT(isCalledFromCallersThread(), return); - - switch (m_processState) { - case QProcess::NotRunning: - break; - case QProcess::Starting: - m_errorString = QCoreApplication::translate("Utils::LauncherHandle", - "Process canceled before it was started."); - m_error = QProcess::FailedToStart; - if (LauncherInterface::socket()->isReady()) - sendPacket(StopProcessPacket(m_token)); - else - emit errorOccurred(m_error); - break; - case QProcess::Running: - sendPacket(StopProcessPacket(m_token)); - break; - } - - m_processState = QProcess::NotRunning; - m_awaitingShouldContinue = false; -} - -QByteArray LauncherHandle::readAllStandardOutput() -{ - QMutexLocker locker(&m_mutex); - QTC_ASSERT(isCalledFromCallersThread(), return {}); - return readAndClear(m_stdout); -} - -QByteArray LauncherHandle::readAllStandardError() -{ - QMutexLocker locker(&m_mutex); - QTC_ASSERT(isCalledFromCallersThread(), return {}); - return readAndClear(m_stderr); -} - -qint64 LauncherHandle::processId() const -{ - QMutexLocker locker(&m_mutex); - QTC_ASSERT(isCalledFromCallersThread(), return 0); - return m_processId; -} - -QString LauncherHandle::errorString() const -{ - QMutexLocker locker(&m_mutex); - QTC_ASSERT(isCalledFromCallersThread(), return {}); - return m_errorString; -} - -void LauncherHandle::setErrorString(const QString &str) -{ - QMutexLocker locker(&m_mutex); - QTC_ASSERT(isCalledFromCallersThread(), return); - m_errorString = str; -} - -void LauncherHandle::start(const QString &program, const QStringList &arguments, const QByteArray &writeData) -{ - QMutexLocker locker(&m_mutex); - QTC_ASSERT(isCalledFromCallersThread(), return); - - if (m_socketError) { - m_error = QProcess::FailedToStart; - emit errorOccurred(m_error); - return; - } - m_command = program; - m_arguments = arguments; - // TODO: check if state is not running - // TODO: check if m_canceled is not true - m_processState = QProcess::Starting; - m_writeData = writeData; - auto processLauncherNotStarted = [&program] { - qWarning() << "Trying to start" << program << "while process launcher wasn't started yet."; - }; - QTC_ASSERT(LauncherInterface::isStarted(), processLauncherNotStarted()); - if (LauncherInterface::socket()->isReady()) - doStart(); -} - -qint64 LauncherHandle::write(const QByteArray &data) -{ - QMutexLocker locker(&m_mutex); - QTC_ASSERT(isCalledFromCallersThread(), return -1); - - if (m_processState != QProcess::Running) - return -1; - - WritePacket p(m_token); - p.inputData = data; - sendPacket(p); - return data.size(); -} - -QProcess::ProcessError LauncherHandle::error() const -{ - QMutexLocker locker(&m_mutex); - QTC_ASSERT(isCalledFromCallersThread(), return QProcess::UnknownError); - return m_error; -} - -QString LauncherHandle::program() const -{ - QMutexLocker locker(&m_mutex); - QTC_ASSERT(isCalledFromCallersThread(), return {}); - return m_command; -} - -void LauncherHandle::setStandardInputFile(const QString &fileName) -{ - QMutexLocker locker(&m_mutex); - QTC_ASSERT(isCalledFromCallersThread(), return); - m_standardInputFile = fileName; -} - -void LauncherHandle::setProcessChannelMode(QProcess::ProcessChannelMode mode) -{ - QMutexLocker locker(&m_mutex); - QTC_ASSERT(isCalledFromCallersThread(), return); - if (mode != QProcess::SeparateChannels && mode != QProcess::MergedChannels) { - qWarning("setProcessChannelMode: The only supported modes are SeparateChannels and MergedChannels."); - return; - } - m_channelMode = mode; -} - -void LauncherHandle::setProcessEnvironment(const QProcessEnvironment &environment) -{ - QMutexLocker locker(&m_mutex); - QTC_ASSERT(isCalledFromCallersThread(), return); - m_environment = environment; -} - -void LauncherHandle::setWorkingDirectory(const QString &dir) -{ - QMutexLocker locker(&m_mutex); - QTC_ASSERT(isCalledFromCallersThread(), return); - m_workingDirectory = dir; -} - -QProcess::ExitStatus LauncherHandle::exitStatus() const -{ - QMutexLocker locker(&m_mutex); - QTC_ASSERT(isCalledFromCallersThread(), return QProcess::CrashExit); - return m_exitStatus; -} - -void LauncherHandle::setBelowNormalPriority() -{ - QMutexLocker locker(&m_mutex); - QTC_ASSERT(isCalledFromCallersThread(), return); - m_belowNormalPriority = true; -} - -void LauncherHandle::setNativeArguments(const QString &arguments) -{ - QMutexLocker locker(&m_mutex); - QTC_ASSERT(isCalledFromCallersThread(), return); - m_nativeArguments = arguments; -} - -void LauncherHandle::setLowPriority() -{ - QMutexLocker locker(&m_mutex); - QTC_ASSERT(isCalledFromCallersThread(), return); - m_lowPriority = true; -} - -void LauncherHandle::setUnixTerminalDisabled() -{ - QMutexLocker locker(&m_mutex); - QTC_ASSERT(isCalledFromCallersThread(), return); - m_unixTerminalDisabled = true; -} - -// Ensure it's called from caller's thread, after moving LauncherHandle into the launcher's thread -void LauncherHandle::createCallerHandle() -{ - QMutexLocker locker(&m_mutex); // may be not needed, as we call it just after Launcher's c'tor - QTC_ASSERT(!isCalledFromLaunchersThread(), return); - QTC_ASSERT(m_callerHandle == nullptr, return); - m_callerHandle = new CallerHandle(); - connect(m_callerHandle, &CallerHandle::errorOccurred, this, &LauncherHandle::slotErrorOccurred, Qt::DirectConnection); - connect(m_callerHandle, &CallerHandle::started, this, &LauncherHandle::slotStarted, Qt::DirectConnection); - connect(m_callerHandle, &CallerHandle::readyRead, this, &LauncherHandle::slotReadyRead, Qt::DirectConnection); - connect(m_callerHandle, &CallerHandle::finished, this, &LauncherHandle::slotFinished, Qt::DirectConnection); -} - -void LauncherHandle::destroyCallerHandle() -{ - QMutexLocker locker(&m_mutex); - QTC_ASSERT(isCalledFromCallersThread(), return); - QTC_ASSERT(m_callerHandle, return); - m_callerHandle->deleteLater(); - m_callerHandle = nullptr; -} - -void LauncherHandle::slotErrorOccurred() -{ - QProcess::ProcessError error = QProcess::UnknownError; - { - QMutexLocker locker(&m_mutex); - QTC_ASSERT(isCalledFromCallersThread(), return); - error = m_error; - } - emit errorOccurred(error); -} - -void LauncherHandle::slotStarted() -{ - { - QMutexLocker locker(&m_mutex); - QTC_ASSERT(isCalledFromCallersThread(), return); - } - emit started(); -} - -void LauncherHandle::slotReadyRead() -{ - bool hasOutput = false; - bool hasError = false; - { - QMutexLocker locker(&m_mutex); - QTC_ASSERT(isCalledFromCallersThread(), return); - hasOutput = !m_stdout.isEmpty(); - hasError = !m_stderr.isEmpty(); - } - if (hasOutput) - emit readyReadStandardOutput(); - if (hasError) - emit readyReadStandardError(); -} - -void LauncherHandle::slotFinished() -{ - int exitCode = 0; - QProcess::ExitStatus exitStatus = QProcess::NormalExit; - { - QMutexLocker locker(&m_mutex); - QTC_ASSERT(isCalledFromCallersThread(), return); - exitCode = m_exitCode; - exitStatus = m_exitStatus; - } - emit finished(exitCode, exitStatus); -} - -// call me with mutex locked -void LauncherHandle::doStart() -{ - StartProcessPacket p(m_token); - p.command = m_command; - p.arguments = m_arguments; - p.env = m_environment.toStringList(); - p.workingDir = m_workingDirectory; - p.processMode = m_processMode; - p.writeData = m_writeData; - p.channelMode = m_channelMode; - p.standardInputFile = m_standardInputFile; - p.belowNormalPriority = m_belowNormalPriority; - p.nativeArguments = m_nativeArguments; - sendPacket(p); -} - LauncherSocket::LauncherSocket(QObject *parent) : QObject(parent) { qRegisterMetaType(); @@ -675,25 +748,27 @@ void LauncherSocket::sendData(const QByteArray &data) QMetaObject::invokeMethod(this, &LauncherSocket::handleRequests); } -LauncherHandle *LauncherSocket::registerHandle(quintptr token, ProcessMode mode) +CallerHandle *LauncherSocket::registerHandle(quintptr token, ProcessMode mode) { QTC_ASSERT(!isCalledFromLaunchersThread(), return nullptr); QMutexLocker locker(&m_mutex); if (m_handles.contains(token)) return nullptr; // TODO: issue a warning - LauncherHandle *handle = new LauncherHandle(token, mode); - handle->moveToThread(thread()); + CallerHandle *callerHandle = new CallerHandle(token, mode); + LauncherHandle *launcherHandle = new LauncherHandle(token, mode); + callerHandle->setLauncherHandle(launcherHandle); + launcherHandle->setCallerHandle(callerHandle); + launcherHandle->moveToThread(thread()); // Call it after moving LauncherHandle to the launcher's thread. // Since this method is invoked from caller's thread, CallerHandle will live in caller's thread. - handle->createCallerHandle(); - m_handles.insert(token, handle); + m_handles.insert(token, launcherHandle); connect(this, &LauncherSocket::ready, - handle, &LauncherHandle::handleSocketReady); + launcherHandle, &LauncherHandle::handleSocketReady); connect(this, &LauncherSocket::errorOccurred, - handle, &LauncherHandle::handleSocketError); + launcherHandle, &LauncherHandle::handleSocketError); - return handle; + return callerHandle; } void LauncherSocket::unregisterHandle(quintptr token) @@ -704,9 +779,12 @@ void LauncherSocket::unregisterHandle(quintptr token) if (it == m_handles.end()) return; // TODO: issue a warning - LauncherHandle *handle = it.value(); - handle->destroyCallerHandle(); - handle->deleteLater(); + LauncherHandle *launcherHandle = it.value(); + CallerHandle *callerHandle = launcherHandle->callerHandle(); + launcherHandle->setCallerHandle(nullptr); + callerHandle->setLauncherHandle(nullptr); + launcherHandle->deleteLater(); + callerHandle->deleteLater(); m_handles.erase(it); } @@ -826,5 +904,3 @@ bool LauncherSocket::isCalledFromLaunchersThread() const } // namespace Internal } // namespace Utils - -#include "launchersocket.moc" diff --git a/src/libs/utils/launchersocket.h b/src/libs/utils/launchersocket.h index dd5ca64fda4..b861e1384c4 100644 --- a/src/libs/utils/launchersocket.h +++ b/src/libs/utils/launchersocket.h @@ -34,8 +34,9 @@ #include #include -#include #include +#include +#include QT_BEGIN_NAMESPACE class QLocalSocket; @@ -45,13 +46,16 @@ namespace Utils { namespace Internal { class LauncherInterfacePrivate; -class CallerHandle; +class LauncherHandle; +class LauncherSignal; +class ErrorSignal; +class StartedSignal; +class ReadyReadSignal; +class FinishedSignal; -// Moved to the launcher thread, returned to caller's thread. -// It's assumed that this object will be alive at least -// as long as the corresponding QtcProcess is alive. - -class LauncherHandle : public QObject +// All the methods and data fields in this class are called / accessed from the caller's thread. +// Exceptions are explicitly marked. +class CallerHandle : public QObject { Q_OBJECT public: @@ -63,14 +67,21 @@ public: Finished }; Q_ENUM(SignalType) + CallerHandle(quintptr token, ProcessMode mode) : QObject(), m_token(token), m_processMode(mode) {} - // All the public methods in this class are called exclusively from the caller's thread. - bool waitForStarted(int msecs) - { return waitForSignal(msecs, SignalType::Started); } - bool waitForReadyRead(int msces) - { return waitForSignal(msces, SignalType::ReadyRead); } - bool waitForFinished(int msecs) - { return waitForSignal(msecs, SignalType::Finished); } + LauncherHandle *launcherHandle() const { return m_launcherHandle; } + void setLauncherHandle(LauncherHandle *handle) { QMutexLocker locker(&m_mutex); m_launcherHandle = handle; } + + bool waitForStarted(int msecs); + bool waitForReadyRead(int msces); + bool waitForFinished(int msecs); + + // Returns the list of flushed signals. + QList flush(); + QList flushFor(SignalType signalType); + bool shouldFlushFor(SignalType signalType) const; + // Called from launcher's thread exclusively. + void appendSignal(LauncherSignal *launcherSignal); QProcess::ProcessState state() const; void cancel(); @@ -83,6 +94,8 @@ public: void setErrorString(const QString &str); void start(const QString &program, const QStringList &arguments, const QByteArray &writeData); + // Called from caller's or launcher's thread. + void startIfNeeded(); qint64 write(const QByteArray &data); @@ -105,46 +118,20 @@ signals: void finished(int exitCode, QProcess::ExitStatus status); void readyReadStandardOutput(); void readyReadStandardError(); + private: + bool waitForSignal(int msecs, CallerHandle::SignalType newSignal); + bool canWaitFor(SignalType newSignal) const; // TODO: employ me before calling waitForSignal() - // Called from caller's thread exclusively. - bool waitForSignal(int msecs, SignalType newSignal); - bool doWaitForSignal(int msecs, SignalType newSignal); - bool canWaitFor(SignalType newSignal) const; - - // Called from caller's or launcher's thread. + // Called from caller's or launcher's thread. Call me with mutex locked. void doStart(); + // Called from caller's or launcher's thread. + void sendPacket(const Internal::LauncherPacket &packet); + // Called from caller's or launcher's thread. + bool isCalledFromCallersThread() const; + // Called from caller's or launcher's thread. Call me with mutex locked. + bool isCalledFromLaunchersThread() const; - // Called from caller's thread exclusively. - void slotErrorOccurred(); - void slotStarted(); - void slotReadyRead(); - void slotFinished(); - - // Called from caller's thread, moved to launcher's thread. - LauncherHandle(quintptr token, ProcessMode mode) : m_token(token), m_processMode(mode) {} - - // Called from caller's thread exclusively. - void createCallerHandle(); - void destroyCallerHandle(); - - // Called from launcher's thread exclusively. - void flushCaller(); - void handlePacket(LauncherPacketType type, const QByteArray &payload); - void handleErrorPacket(const QByteArray &packetData); - void handleStartedPacket(const QByteArray &packetData); - void handleReadyReadStandardOutput(const QByteArray &packetData); - void handleReadyReadStandardError(const QByteArray &packetData); - void handleFinishedPacket(const QByteArray &packetData); - - // Called from launcher's thread exclusively. - void handleSocketReady(); - void handleSocketError(const QString &message); - - // Called from launcher's thread exclusively. - void wakeUpIfWaitingFor(SignalType newSignal); - - // Called from caller's thread exclusively. QByteArray readAndClear(QByteArray &data) const { const QByteArray tmp = data; @@ -152,22 +139,24 @@ private: return tmp; } - // Called from caller's or launcher's thread. - void sendPacket(const Internal::LauncherPacket &packet); + void handleError(const ErrorSignal *launcherSignal); + void handleStarted(const StartedSignal *launcherSignal); + void handleReadyRead(const ReadyReadSignal *launcherSignal); + void handleFinished(const FinishedSignal *launcherSignal); - // Called from caller's or launcher's thread. - bool isCalledFromLaunchersThread() const; - bool isCalledFromCallersThread() const; + // Lives in launcher's thread. Modified from caller's thread. + LauncherHandle *m_launcherHandle = nullptr; mutable QMutex m_mutex; - QWaitCondition m_waitCondition; + // Accessed from caller's and launcher's thread + QList m_signals; + const quintptr m_token; const ProcessMode m_processMode; - SignalType m_waitingFor = SignalType::NoSignal; - QProcess::ProcessState m_processState = QProcess::NotRunning; - // cancel() sets it to false, modified only in caller's thread, don't need to be protected by mutex - bool m_awaitingShouldContinue = false; + // Modified from caller's thread, read from launcher's thread + std::atomic m_processState = QProcess::NotRunning; + std::unique_ptr m_startPacket; int m_processId = 0; int m_exitCode = 0; QProcess::ExitStatus m_exitStatus = QProcess::ExitStatus::NormalExit; @@ -175,7 +164,6 @@ private: QByteArray m_stderr; QString m_errorString; QProcess::ProcessError m_error = QProcess::UnknownError; - bool m_socketError = false; QString m_command; QStringList m_arguments; @@ -185,15 +173,67 @@ private: QProcess::ProcessChannelMode m_channelMode = QProcess::SeparateChannels; QString m_standardInputFile; - // Lives in caller's thread. - CallerHandle *m_callerHandle = nullptr; - bool m_belowNormalPriority = false; QString m_nativeArguments; bool m_lowPriority = false; bool m_unixTerminalDisabled = false; +}; - friend class LauncherSocket; +// Moved to the launcher thread, returned to caller's thread. +// It's assumed that this object will be alive at least +// as long as the corresponding QtcProcess is alive. + +class LauncherHandle : public QObject +{ + Q_OBJECT +public: + // Called from caller's thread, moved to launcher's thread afterwards. + LauncherHandle(quintptr token, ProcessMode mode) : m_token(token) {} + // Called from caller's thread exclusively. + bool waitForSignal(int msecs, CallerHandle::SignalType newSignal); + CallerHandle *callerHandle() const { return m_callerHandle; } + void setCallerHandle(CallerHandle *handle) { QMutexLocker locker(&m_mutex); m_callerHandle = handle; } + // Called from caller's thread exclusively. + void setCanceled() { m_awaitingShouldContinue = false; } + + // Called from launcher's thread exclusively. + void handleSocketReady(); + void handleSocketError(const QString &message); + void handlePacket(LauncherPacketType type, const QByteArray &payload); + + // Called from caller's thread exclusively. + bool isSocketError() const { return m_socketError; } + +private: + // Called from caller's thread exclusively. + bool doWaitForSignal(int msecs, CallerHandle::SignalType newSignal); + // Called from launcher's thread exclusively. Call me with mutex locked. + void wakeUpIfWaitingFor(CallerHandle::SignalType newSignal); + + // Called from launcher's thread exclusively. Call me with mutex locked. + void flushCaller(); + // Called from launcher's thread exclusively. + void handleErrorPacket(const QByteArray &packetData); + void handleStartedPacket(const QByteArray &packetData); + void handleReadyReadStandardOutput(const QByteArray &packetData); + void handleReadyReadStandardError(const QByteArray &packetData); + void handleFinishedPacket(const QByteArray &packetData); + + // Called from caller's or launcher's thread. + bool isCalledFromLaunchersThread() const; + bool isCalledFromCallersThread() const; + + // Lives in caller's thread. Modified only in caller's thread. TODO: check usages - all should be with mutex + CallerHandle *m_callerHandle = nullptr; + + // Modified only in caller's thread. + bool m_awaitingShouldContinue = false; + mutable QMutex m_mutex; + QWaitCondition m_waitCondition; + const quintptr m_token; + std::atomic_bool m_socketError = false; + // Modified only in caller's thread. + CallerHandle::SignalType m_waitingFor = CallerHandle::SignalType::NoSignal; }; class LauncherSocket : public QObject @@ -206,7 +246,7 @@ public: void sendData(const QByteArray &data); // Called from caller's thread exclusively. - LauncherHandle *registerHandle(quintptr token, ProcessMode mode); + CallerHandle *registerHandle(quintptr token, ProcessMode mode); void unregisterHandle(quintptr token); signals: diff --git a/src/libs/utils/qtcprocess.cpp b/src/libs/utils/qtcprocess.cpp index dac11eab36c..8bcbe0fc52b 100644 --- a/src/libs/utils/qtcprocess.cpp +++ b/src/libs/utils/qtcprocess.cpp @@ -252,15 +252,15 @@ public: : ProcessInterface(processMode), m_token(uniqueToken()) { m_handle = LauncherInterface::socket()->registerHandle(token(), processMode); - connect(m_handle, &LauncherHandle::errorOccurred, + connect(m_handle, &CallerHandle::errorOccurred, this, &ProcessInterface::errorOccurred); - connect(m_handle, &LauncherHandle::started, + connect(m_handle, &CallerHandle::started, this, &ProcessInterface::started); - connect(m_handle, &LauncherHandle::finished, + connect(m_handle, &CallerHandle::finished, this, &ProcessInterface::finished); - connect(m_handle, &LauncherHandle::readyReadStandardOutput, + connect(m_handle, &CallerHandle::readyReadStandardOutput, this, &ProcessInterface::readyReadStandardOutput); - connect(m_handle, &LauncherHandle::readyReadStandardError, + connect(m_handle, &CallerHandle::readyReadStandardError, this, &ProcessInterface::readyReadStandardError); } ~ProcessLauncherImpl() override @@ -318,7 +318,7 @@ private: const uint m_token = 0; // Lives in launcher's thread. - LauncherHandle *m_handle = nullptr; + CallerHandle *m_handle = nullptr; }; void ProcessLauncherImpl::cancel() @@ -456,6 +456,9 @@ QtcProcess::QtcProcess(ProcessImpl processImpl, ProcessMode processMode, QObject Q_UNUSED(qProcessProcessErrorMeta) } +QtcProcess::QtcProcess(ProcessImpl processImpl, QObject *parent) + : QtcProcess(processImpl, ProcessMode::Reader, parent) {} + QtcProcess::QtcProcess(ProcessMode processMode, QObject *parent) : QtcProcess(defaultProcessImpl(), processMode, parent) {} diff --git a/src/libs/utils/qtcprocess.h b/src/libs/utils/qtcprocess.h index 93d37bdbc85..69eb2028e7a 100644 --- a/src/libs/utils/qtcprocess.h +++ b/src/libs/utils/qtcprocess.h @@ -66,6 +66,7 @@ public: }; QtcProcess(ProcessImpl processImpl, ProcessMode processMode, QObject *parent = nullptr); + QtcProcess(ProcessImpl processImpl, QObject *parent = nullptr); QtcProcess(ProcessMode processMode, QObject *parent = nullptr); QtcProcess(QObject *parent = nullptr); ~QtcProcess();