diff --git a/src/libs/utils/launcherinterface.cpp b/src/libs/utils/launcherinterface.cpp index eb04120c2cf..b22da63b355 100644 --- a/src/libs/utils/launcherinterface.cpp +++ b/src/libs/utils/launcherinterface.cpp @@ -69,35 +69,51 @@ private: } }; -} // namespace Internal - -using namespace Utils::Internal; - static QString launcherSocketName() { return QStringLiteral("qtcreator_processlauncher-%1") .arg(QString::number(qApp->applicationPid())); } -LauncherInterface::LauncherInterface() +class LauncherInterfacePrivate : public QObject +{ + Q_OBJECT +public: + LauncherInterfacePrivate(); + ~LauncherInterfacePrivate() override; + + void doStart(); + void doStop(); + void handleNewConnection(); + void handleProcessError(); + void handleProcessFinished(); + void handleProcessStderr(); + Internal::LauncherSocket *socket() const { return m_socket; } + +signals: + void errorOccurred(const QString &error); + +private: + QLocalServer * const m_server; + Internal::LauncherSocket *const m_socket; + Internal::LauncherProcess *m_process = nullptr; + int m_startRequests = 0; + +}; + +LauncherInterfacePrivate::LauncherInterfacePrivate() : m_server(new QLocalServer(this)), m_socket(new LauncherSocket(this)) { QObject::connect(m_server, &QLocalServer::newConnection, - this, &LauncherInterface::handleNewConnection); + this, &LauncherInterfacePrivate::handleNewConnection); } -LauncherInterface &LauncherInterface::instance() -{ - static LauncherInterface p; - return p; -} - -LauncherInterface::~LauncherInterface() +LauncherInterfacePrivate::~LauncherInterfacePrivate() { m_server->disconnect(); } -void LauncherInterface::doStart() +void LauncherInterfacePrivate::doStart() { if (++m_startRequests > 1) return; @@ -108,19 +124,19 @@ void LauncherInterface::doStart() return; } m_process = new LauncherProcess(this); - connect(m_process, &QProcess::errorOccurred, this, &LauncherInterface::handleProcessError); + connect(m_process, &QProcess::errorOccurred, this, &LauncherInterfacePrivate::handleProcessError); connect(m_process, static_cast(&QProcess::finished), - this, &LauncherInterface::handleProcessFinished); + this, &LauncherInterfacePrivate::handleProcessFinished); connect(m_process, &QProcess::readyReadStandardError, - this, &LauncherInterface::handleProcessStderr); + this, &LauncherInterfacePrivate::handleProcessStderr); m_process->start(qApp->applicationDirPath() + QLatin1Char('/') + QLatin1String(RELATIVE_LIBEXEC_PATH) + QLatin1String("/qtcreator_processlauncher"), QStringList(m_server->fullServerName())); } -void LauncherInterface::doStop() +void LauncherInterfacePrivate::doStop() { if (--m_startRequests > 0) return; @@ -134,7 +150,7 @@ void LauncherInterface::doStop() m_process = nullptr; } -void LauncherInterface::handleNewConnection() +void LauncherInterfacePrivate::handleNewConnection() { QLocalSocket * const socket = m_server->nextPendingConnection(); if (!socket) @@ -143,7 +159,7 @@ void LauncherInterface::handleNewConnection() m_socket->setSocket(socket); } -void LauncherInterface::handleProcessError() +void LauncherInterfacePrivate::handleProcessError() { if (m_process->error() == QProcess::FailedToStart) { const QString launcherPathForUser @@ -154,16 +170,60 @@ void LauncherInterface::handleProcessError() } } -void LauncherInterface::handleProcessFinished() +void LauncherInterfacePrivate::handleProcessFinished() { emit errorOccurred(QCoreApplication::translate("Utils::LauncherSocket", "Process launcher closed unexpectedly: %1") .arg(m_process->errorString())); } -void LauncherInterface::handleProcessStderr() +void LauncherInterfacePrivate::handleProcessStderr() { qDebug() << "[launcher]" << m_process->readAllStandardError(); } +} // namespace Internal + +using namespace Utils::Internal; + +LauncherInterface::LauncherInterface() + : m_private(new LauncherInterfacePrivate()) +{ + m_private->moveToThread(&m_thread); + connect(m_private, &LauncherInterfacePrivate::errorOccurred, + this, &LauncherInterface::errorOccurred); + connect(&m_thread, &QThread::finished, m_private, &QObject::deleteLater); + m_thread.start(); +} + +LauncherInterface &LauncherInterface::instance() +{ + static LauncherInterface p; + return p; +} + +LauncherInterface::~LauncherInterface() +{ + m_thread.quit(); + m_thread.wait(); +} + +void LauncherInterface::startLauncher() +{ + QMetaObject::invokeMethod(instance().m_private, &LauncherInterfacePrivate::doStart); +} + +void LauncherInterface::stopLauncher() +{ + QMetaObject::invokeMethod(instance().m_private, &LauncherInterfacePrivate::doStop); +} + +Internal::LauncherSocket *LauncherInterface::socket() +{ + return instance().m_private->socket(); +} + + } // namespace Utils + +#include "launcherinterface.moc" diff --git a/src/libs/utils/launcherinterface.h b/src/libs/utils/launcherinterface.h index 5844aa8ce0e..d274c5ca51c 100644 --- a/src/libs/utils/launcherinterface.h +++ b/src/libs/utils/launcherinterface.h @@ -27,7 +27,8 @@ #include "utils_global.h" -#include +#include +#include QT_BEGIN_NAMESPACE class QLocalServer; @@ -37,6 +38,7 @@ namespace Utils { namespace Internal { class LauncherProcess; class LauncherSocket; +class LauncherInterfacePrivate; } class QTCREATOR_UTILS_EXPORT LauncherInterface : public QObject @@ -46,9 +48,9 @@ public: static LauncherInterface &instance(); ~LauncherInterface() override; - static void startLauncher() { instance().doStart(); } - static void stopLauncher() { instance().doStop(); } - static Internal::LauncherSocket *socket() { return instance().m_socket; } + static void startLauncher(); + static void stopLauncher(); + static Internal::LauncherSocket *socket(); signals: void errorOccurred(const QString &error); @@ -56,17 +58,8 @@ signals: private: LauncherInterface(); - void doStart(); - void doStop(); - void handleNewConnection(); - void handleProcessError(); - void handleProcessFinished(); - void handleProcessStderr(); - - QLocalServer * const m_server; - Internal::LauncherSocket *const m_socket; - Internal::LauncherProcess *m_process = nullptr; - int m_startRequests = 0; + QThread m_thread; + Internal::LauncherInterfacePrivate *m_private; }; } // namespace Utils diff --git a/src/libs/utils/launchersocket.cpp b/src/libs/utils/launchersocket.cpp index 49a926bea53..a93dccf9a01 100644 --- a/src/libs/utils/launchersocket.cpp +++ b/src/libs/utils/launchersocket.cpp @@ -24,16 +24,293 @@ ****************************************************************************/ #include "launchersocket.h" +#include "launcherinterface.h" #include "qtcassert.h" #include -#include #include namespace Utils { namespace Internal { +class CallerHandle : public QObject +{ + Q_OBJECT +public: + CallerHandle() : QObject() {} + + enum class SignalType { + Started, + Finished + }; + + // always called in caller's thread + void flush() + { + QList oldSignals; + { + QMutexLocker locker(&m_mutex); + oldSignals = m_signals; + m_signals = {}; + } + for (SignalType signalType : qAsConst(oldSignals)) { + switch (signalType) { + case SignalType::Started: + emit started(); + break; + case SignalType::Finished: + emit finished(); + break; + } + } + } + void appendSignal(SignalType signalType) { QMutexLocker locker(&m_mutex); m_signals.append(signalType); } +signals: + void started(); + void finished(); +private: + QMutex m_mutex; + QList m_signals; +}; + +void LauncherHandle::handlePacket(LauncherPacketType type, const QByteArray &payload) +{ + switch (type) { + case LauncherPacketType::ProcessError: + handleErrorPacket(payload); + break; + case LauncherPacketType::ProcessStarted: + handleStartedPacket(payload); + break; + case LauncherPacketType::ProcessFinished: + handleFinishedPacket(payload); + break; + default: + QTC_ASSERT(false, break); + } +} + +void LauncherHandle::handleErrorPacket(const QByteArray &packetData) +{ + QMutexLocker locker(&m_mutex); + if (!m_canceled) + m_processState = QProcess::NotRunning; + if (m_waitingFor != WaitingForState::Idle) { + m_waitCondition.wakeOne(); + m_waitingFor = WaitingForState::Idle; + } + m_failed = true; + + const auto packet = LauncherPacket::extractPacket(m_token, packetData); + m_error = packet.error; + m_errorString = packet.errorString; + // TODO: pipe it through the callers handle? + emit errorOccurred(m_error); +} + +// call me with mutex locked +void LauncherHandle::stateReached(WaitingForState wakeUpState, QProcess::ProcessState newState) +{ + if (!m_canceled) + m_processState = newState; + const bool shouldWake = m_waitingFor == wakeUpState; + if (shouldWake) { + m_waitCondition.wakeOne(); + m_waitingFor = WaitingForState::Idle; + } +} + +void LauncherHandle::sendPacket(const Internal::LauncherPacket &packet) +{ + LauncherInterface::socket()->sendData(packet.serialize()); +} + +// call me with mutex locked +void LauncherHandle::flushCaller() +{ + if (!m_callerHandle) + return; + + // call in callers thread + QMetaObject::invokeMethod(m_callerHandle, &CallerHandle::flush); +} + +void LauncherHandle::handleStartedPacket(const QByteArray &packetData) +{ + QMutexLocker locker(&m_mutex); + stateReached(WaitingForState::Started, QProcess::Running); + if (m_canceled) + return; + const auto packet = LauncherPacket::extractPacket(m_token, packetData); + m_processId = packet.processId; + if (m_callerHandle) { + m_callerHandle->appendSignal(CallerHandle::SignalType::Started); + flushCaller(); + } +} + +void LauncherHandle::handleFinishedPacket(const QByteArray &packetData) +{ + QMutexLocker locker(&m_mutex); + stateReached(WaitingForState::Finished, QProcess::NotRunning); + if (m_canceled) + return; + m_finished = true; + const auto packet = LauncherPacket::extractPacket(m_token, packetData); + m_exitCode = packet.exitCode; + m_stdout = packet.stdOut; + m_stderr = packet.stdErr; + if (!m_stdout.isEmpty()) + emit readyReadStandardOutput(); + if (!m_stderr.isEmpty()) + emit readyReadStandardError(); + m_errorString = packet.errorString; + m_exitStatus = packet.exitStatus; + if (m_callerHandle) { + m_callerHandle->appendSignal(CallerHandle::SignalType::Finished); + flushCaller(); + } +} + +void LauncherHandle::handleSocketReady() +{ + QMutexLocker locker(&m_mutex); + m_socketError = false; + if (m_processState == QProcess::Starting) + doStart(); +} + +void LauncherHandle::handleSocketError(const QString &message) +{ + 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::waitForState(int msecs, WaitingForState newState, QProcess::ProcessState targetState) +{ + bool ok = false; + { + QMutexLocker locker(&m_mutex); + // TODO: ASSERT if we are in Idle state + if (m_canceled) // we don't want to wait if we have canceled it before (ASSERT it?) + return false; + + if (m_processState == targetState) { + qDebug() << "THE TARGET STATE IS ALREADY REACHED"; + ok = true; + } else if (m_finished) { // it may happen, than after calling start() and before calling waitForStarted() we might have finished already + qDebug() << "THE PROCESS HAS ALREADY FINISHED"; + ok = true; + } + if (!ok) { + m_waitingFor = newState; + ok = m_waitCondition.wait(&m_mutex, msecs) && !m_failed; + } + } + if (ok) // since we are in caller's thread, m_callerHandle must be still valid + m_callerHandle->flush(); + return ok; +} + +void LauncherHandle::cancel() +{ + QMutexLocker locker(&m_mutex); + + 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_canceled = true; +} + +void LauncherHandle::start(const QString &program, const QStringList &arguments, QIODevice::OpenMode mode) +{ + QMutexLocker locker(&m_mutex); + + 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_openMode = mode; + if (LauncherInterface::socket()->isReady()) + doStart(); +} + +// 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_CHECK(m_callerHandle == nullptr); + m_callerHandle = new CallerHandle(); + connect(m_callerHandle, &CallerHandle::started, this, &LauncherHandle::slotStarted, Qt::DirectConnection); + connect(m_callerHandle, &CallerHandle::finished, this, &LauncherHandle::slotFinished, Qt::DirectConnection); +} + +void LauncherHandle::destroyCallerHandle() +{ + QMutexLocker locker(&m_mutex); + QTC_CHECK(m_callerHandle); + m_callerHandle->deleteLater(); + m_callerHandle = nullptr; +} + +void LauncherHandle::slotStarted() +{ + emit started(); +} + +void LauncherHandle::slotFinished() +{ + int exitCode = 0; + QProcess::ExitStatus exitStatus = QProcess::NormalExit; + { + QMutexLocker locker(&m_mutex); + 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.openMode = m_openMode; + p.channelMode = m_channelMode; + sendPacket(p); +} + LauncherSocket::LauncherSocket(QObject *parent) : QObject(parent) { qRegisterMetaType(); @@ -44,10 +321,49 @@ void LauncherSocket::sendData(const QByteArray &data) { if (!isReady()) return; - std::lock_guard locker(m_requestsMutex); + QMutexLocker locker(&m_mutex); m_requests.push_back(data); if (m_requests.size() == 1) - QTimer::singleShot(0, this, &LauncherSocket::handleRequests); + QMetaObject::invokeMethod(this, &LauncherSocket::handleRequests); +} + +LauncherHandle *LauncherSocket::registerHandle(quintptr token) +{ + QMutexLocker locker(&m_mutex); + if (m_handles.contains(token)) + return nullptr; // TODO: issue a warning + + LauncherHandle *handle = new LauncherHandle(token); + handle->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); + connect(this, &LauncherSocket::ready, + handle, &LauncherHandle::handleSocketReady); + connect(this, &LauncherSocket::errorOccurred, + handle, &LauncherHandle::handleSocketError); + + return handle; +} + +void LauncherSocket::unregisterHandle(quintptr token) +{ + QMutexLocker locker(&m_mutex); + auto it = m_handles.find(token); + if (it == m_handles.end()) + return; // TODO: issue a warning + + LauncherHandle *handle = it.value(); + handle->destroyCallerHandle(); + handle->deleteLater(); + m_handles.erase(it); +} + +LauncherHandle *LauncherSocket::handleForToken(quintptr token) const +{ + QMutexLocker locker(&m_mutex); + return m_handles.value(token); } void LauncherSocket::shutdown() @@ -58,7 +374,7 @@ void LauncherSocket::shutdown() socket->disconnect(); socket->write(ShutdownPacket().serialize()); socket->waitForBytesWritten(1000); - socket->deleteLater(); + socket->deleteLater(); // or schedule a queued call to delete later? } void LauncherSocket::setSocket(QLocalSocket *socket) @@ -98,18 +414,23 @@ void LauncherSocket::handleSocketDataAvailable() "Internal protocol error: invalid packet size %1.").arg(e.size)); return; } - switch (m_packetParser.type()) { - case LauncherPacketType::ProcessError: - case LauncherPacketType::ProcessStarted: - case LauncherPacketType::ProcessFinished: - emit packetArrived(m_packetParser.type(), m_packetParser.token(), - m_packetParser.packetData()); - break; - default: - handleError(QCoreApplication::translate("Utils::LauncherSocket", - "Internal protocol error: invalid packet type %1.") - .arg(static_cast(m_packetParser.type()))); - return; + LauncherHandle *handle = handleForToken(m_packetParser.token()); + if (handle) { + switch (m_packetParser.type()) { + case LauncherPacketType::ProcessError: + case LauncherPacketType::ProcessStarted: + case LauncherPacketType::ProcessFinished: + handle->handlePacket(m_packetParser.type(), m_packetParser.packetData()); + break; + default: + handleError(QCoreApplication::translate("Utils::LauncherSocket", + "Internal protocol error: invalid packet type %1.") + .arg(static_cast(m_packetParser.type()))); + return; + } + } else { +// qDebug() << "No handler for token" << m_packetParser.token() << m_handles; + // in this case the QtcProcess was canceled and deleted } handleSocketDataAvailable(); } @@ -132,7 +453,7 @@ void LauncherSocket::handleRequests() { const auto socket = m_socket.load(); QTC_ASSERT(socket, return); - std::lock_guard locker(m_requestsMutex); + QMutexLocker locker(&m_mutex); for (const QByteArray &request : qAsConst(m_requests)) socket->write(request); m_requests.clear(); @@ -140,3 +461,5 @@ void LauncherSocket::handleRequests() } // namespace Internal } // namespace Utils + +#include "launchersocket.moc" diff --git a/src/libs/utils/launchersocket.h b/src/libs/utils/launchersocket.h index 0090121894e..e8a9ba460c6 100644 --- a/src/libs/utils/launchersocket.h +++ b/src/libs/utils/launchersocket.h @@ -29,8 +29,14 @@ #include -#include +#include +#include +#include +#include +#include + #include +#include QT_BEGIN_NAMESPACE class QLocalSocket; @@ -41,22 +47,157 @@ class LauncherInterface; namespace Internal { +class LauncherInterfacePrivate; +class CallerHandle; + +// 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. + +// We should have LauncherSocket::registerHandle() and LauncherSocket::unregisterHandle() +// methods. + +class LauncherHandle : public QObject +{ + Q_OBJECT +public: + // called from main thread + bool waitForStarted(int msecs) // TODO: we might already be in finished state when calling this method - fix it! + { return waitForState(msecs, WaitingForState::Started, QProcess::Running); } + bool waitForFinished(int msecs) + { return waitForState(msecs, WaitingForState::Finished, QProcess::NotRunning); } + + QProcess::ProcessState state() const + { QMutexLocker locker(&m_mutex); return m_processState; } + void cancel(); + bool isCanceled() const { return m_canceled; } + + QByteArray readAllStandardOutput() + { QMutexLocker locker(&m_mutex); return readAndClear(m_stdout); } + QByteArray readAllStandardError() + { QMutexLocker locker(&m_mutex); return readAndClear(m_stderr); } + + qint64 processId() const { QMutexLocker locker(&m_mutex); return m_processId; } + QString errorString() const { QMutexLocker locker(&m_mutex); return m_errorString; } + void setErrorString(const QString &str) { QMutexLocker locker(&m_mutex); m_errorString = str; } + + // Called from other thread. Create a temp object receiver which lives in caller's thread. + // Add started and finished signals to it and post a flush to it. + // When we are in waitForState() which is called from the same thread, + // we may flush the signal queue and emit these signals immediately. + // Who should remove this object? deleteLater()? + void start(const QString &program, const QStringList &arguments, QIODevice::OpenMode mode); + + QProcess::ProcessError error() const { QMutexLocker locker(&m_mutex); return m_error; } + QString program() const { QMutexLocker locker(&m_mutex); return m_command; } + void setProcessChannelMode(QProcess::ProcessChannelMode mode) { + QMutexLocker locker(&m_mutex); + if (mode != QProcess::SeparateChannels && mode != QProcess::MergedChannels) { + qWarning("setProcessChannelMode: The only supported modes are SeparateChannels and MergedChannels."); + return; + } + m_channelMode = mode; + } + void setProcessEnvironment(const QProcessEnvironment &environment) + { QMutexLocker locker(&m_mutex); m_environment = environment; } + void setWorkingDirectory(const QString &dir) { QMutexLocker locker(&m_mutex); m_workingDirectory = dir; } + QProcess::ExitStatus exitStatus() const { QMutexLocker locker(&m_mutex); return m_exitStatus; } +signals: + void errorOccurred(QProcess::ProcessError error); + void started(); + void finished(int exitCode, QProcess::ExitStatus status); + void readyReadStandardOutput(); + void readyReadStandardError(); +private: + enum class WaitingForState { + Idle, + Started, + ReadyRead, + Finished + }; + // called from other thread + bool waitForState(int msecs, WaitingForState newState, QProcess::ProcessState targetState); + + void doStart(); + + void slotStarted(); + void slotFinished(); + + // called from this thread + LauncherHandle(quintptr token) : m_token(token) {} + void createCallerHandle(); + void destroyCallerHandle(); + + void flushCaller(); + + void handlePacket(LauncherPacketType type, const QByteArray &payload); + void handleErrorPacket(const QByteArray &packetData); + void handleStartedPacket(const QByteArray &packetData); + void handleFinishedPacket(const QByteArray &packetData); + + void handleSocketReady(); + void handleSocketError(const QString &message); + + void stateReached(WaitingForState wakeUpState, QProcess::ProcessState newState); + + QByteArray readAndClear(QByteArray &data) + { + const QByteArray tmp = data; + data.clear(); + return tmp; + } + + void sendPacket(const Internal::LauncherPacket &packet); + + mutable QMutex m_mutex; + QWaitCondition m_waitCondition; + const quintptr m_token; + WaitingForState m_waitingFor = WaitingForState::Idle; + + QProcess::ProcessState m_processState = QProcess::NotRunning; + std::atomic_bool m_canceled = false; + std::atomic_bool m_failed = false; + std::atomic_bool m_finished = false; + int m_processId = 0; + int m_exitCode = 0; + QProcess::ExitStatus m_exitStatus = QProcess::ExitStatus::NormalExit; + QByteArray m_stdout; + QByteArray m_stderr; + QString m_errorString; + QProcess::ProcessError m_error = QProcess::UnknownError; + bool m_socketError = false; + + QString m_command; + QStringList m_arguments; + QProcessEnvironment m_environment; + QString m_workingDirectory; + QIODevice::OpenMode m_openMode = QIODevice::ReadWrite; + QProcess::ProcessChannelMode m_channelMode = QProcess::SeparateChannels; + + CallerHandle *m_callerHandle = nullptr; + + friend class LauncherSocket; +}; + class LauncherSocket : public QObject { Q_OBJECT - friend class Utils::LauncherInterface; + friend class LauncherInterfacePrivate; public: bool isReady() const { return m_socket.load(); } void sendData(const QByteArray &data); + LauncherHandle *registerHandle(quintptr token); + void unregisterHandle(quintptr token); + signals: void ready(); void errorOccurred(const QString &error); - void packetArrived(Utils::Internal::LauncherPacketType type, quintptr token, - const QByteArray &payload); private: - LauncherSocket(QObject *parent); + LauncherSocket(QObject *parent = nullptr); + + LauncherHandle *handleForToken(quintptr token) const; void setSocket(QLocalSocket *socket); void shutdown(); @@ -70,7 +211,8 @@ private: std::atomic m_socket{nullptr}; PacketParser m_packetParser; std::vector m_requests; - std::mutex m_requestsMutex; + mutable QMutex m_mutex; + QHash m_handles; }; } // namespace Internal diff --git a/src/libs/utils/qtcprocess.cpp b/src/libs/utils/qtcprocess.cpp index 8b10ed92f73..e4869ba55e1 100644 --- a/src/libs/utils/qtcprocess.cpp +++ b/src/libs/utils/qtcprocess.cpp @@ -281,49 +281,53 @@ class ProcessLauncherImpl : public ProcessInterface public: ProcessLauncherImpl() : ProcessInterface() { - connect(LauncherInterface::socket(), &LauncherSocket::ready, - this, &ProcessLauncherImpl::handleSocketReady); - connect(LauncherInterface::socket(), &LauncherSocket::errorOccurred, - this, &ProcessLauncherImpl::handleSocketError); - connect(LauncherInterface::socket(), &LauncherSocket::packetArrived, - this, &ProcessLauncherImpl::handlePacket); + m_handle = LauncherInterface::socket()->registerHandle(token()); + connect(m_handle, &LauncherHandle::errorOccurred, + this, &ProcessInterface::errorOccurred); + connect(m_handle, &LauncherHandle::started, + this, &ProcessInterface::started); + connect(m_handle, &LauncherHandle::finished, + this, &ProcessInterface::finished); + connect(m_handle, &LauncherHandle::readyReadStandardOutput, + this, &ProcessInterface::readyReadStandardOutput); + connect(m_handle, &LauncherHandle::readyReadStandardError, + this, &ProcessInterface::readyReadStandardError); + } + ~ProcessLauncherImpl() override + { + cancel(); + LauncherInterface::socket()->unregisterHandle(token()); } - ~ProcessLauncherImpl() override { cancel(); } - QByteArray readAllStandardOutput() override { return readAndClear(m_stdout); } - QByteArray readAllStandardError() override { return readAndClear(m_stderr); } + QByteArray readAllStandardOutput() override { return m_handle->readAllStandardOutput(); } + QByteArray readAllStandardError() override { return m_handle->readAllStandardError(); } void setProcessEnvironment(const QProcessEnvironment &environment) override - { m_environment = environment; } - void setWorkingDirectory(const QString &dir) override { m_workingDirectory = dir; } - void start(const QString &program, const QStringList &arguments, QIODevice::OpenMode mode) override; + { m_handle->setProcessEnvironment(environment); } + void setWorkingDirectory(const QString &dir) override { m_handle->setWorkingDirectory(dir); } + void start(const QString &program, const QStringList &arguments, QIODevice::OpenMode mode) override + { m_handle->start(program, arguments, mode); } void terminate() override { cancel(); } // TODO: what are differences among terminate, kill and close? void kill() override { cancel(); } // TODO: see above void close() override { cancel(); } // TODO: see above qint64 write(const QByteArray &data) override { QTC_CHECK(false); return -1; } - void closeWriteChannel() override { QTC_CHECK(false); } + void closeWriteChannel() override { /*QTC_CHECK(false);*/ } void setStandardInputFile(const QString &fileName) override { QTC_CHECK(false); } - void setProcessChannelMode(QProcess::ProcessChannelMode mode) override { - if (mode != QProcess::SeparateChannels && mode != QProcess::MergedChannels) { - qWarning("setProcessChannelMode: The only supported modes are SeparateChannels and MergedChannels."); - return; - } - m_channelMode = mode; - } + void setProcessChannelMode(QProcess::ProcessChannelMode mode) override { m_handle->setProcessChannelMode(mode); } qint64 bytesAvailable() const override { QTC_CHECK(false); return 0; } - QString program() const override { return m_command; } - QProcess::ProcessError error() const override { return m_error; } - QProcess::ProcessState state() const override { return m_state; } - qint64 processId() const override { return m_processId; } - QProcess::ExitStatus exitStatus() const override { QTC_CHECK(false); return QProcess::NormalExit; } - QString errorString() const override { return m_errorString; } - void setErrorString(const QString &str) override { m_errorString = str; } + QString program() const override { return m_handle->program(); } + QProcess::ProcessError error() const override { return m_handle->error(); } + QProcess::ProcessState state() const override { return m_handle->state(); } + qint64 processId() const override { return m_handle->processId(); } + QProcess::ExitStatus exitStatus() const override { return m_handle->exitStatus(); } + QString errorString() const override { return m_handle->errorString(); } + void setErrorString(const QString &str) override { m_handle->setErrorString(str); } - bool waitForStarted(int msecs) override; - bool waitForReadyRead(int msecs) override; - bool waitForFinished(int msecs) override; + bool waitForStarted(int msecs) override { return m_handle->waitForStarted(msecs); } + bool waitForReadyRead(int msecs) override { QTC_CHECK(false); return false; } + bool waitForFinished(int msecs) override { return m_handle->waitForFinished(msecs); } void setLowPriority() override { QTC_CHECK(false); } bool lowPriority() const override { QTC_CHECK(false); return false; } @@ -337,223 +341,24 @@ public: void setNativeArguments(const QString &arguments) override { QTC_CHECK(false); } #endif -signals: - void preStarted(); - void preReadyRead(); - void preFinished(); - private: typedef void (ProcessLauncherImpl::*PreSignal)(void); - bool waitForSignal(int msecs, const PreSignal &preSignal); - void doStart(); void cancel(); void sendPacket(const Internal::LauncherPacket &packet) { LauncherInterface::socket()->sendData(packet.serialize()); } - QByteArray readAndClear(QByteArray &data) - { - const QByteArray tmp = data; - data.clear(); - return tmp; - } void handleSocketError(const QString &message); - void handlePacket(Internal::LauncherPacketType type, quintptr token, - const QByteArray &payload); - void handleErrorPacket(const QByteArray &packetData); - void handleStartedPacket(const QByteArray &packetData); - void handleFinishedPacket(const QByteArray &packetData); void handleSocketReady(); quintptr token() const { return reinterpret_cast(this); } - QString m_command; - QStringList m_arguments; - QProcessEnvironment m_environment; - QString m_workingDirectory; - QByteArray m_stdout; - QByteArray m_stderr; - QString m_errorString; - QProcess::ProcessError m_error = QProcess::UnknownError; - QProcess::ProcessState m_state = QProcess::NotRunning; - QIODevice::OpenMode m_openMode = QIODevice::ReadWrite; - QProcess::ProcessChannelMode m_channelMode = QProcess::SeparateChannels; - int m_processId = 0; - int m_exitCode = 0; - bool m_canceled = false; - bool m_socketError = false; + LauncherHandle *m_handle = nullptr; // This object lives in a different thread! }; -bool ProcessLauncherImpl::waitForStarted(int msecs) -{ - if (m_state == QProcess::Running) - return true; - return waitForSignal(msecs, &ProcessLauncherImpl::preStarted); -} - -bool ProcessLauncherImpl::waitForReadyRead(int msecs) -{ - // TODO: check if any data is ready, return true if there is data - return waitForSignal(msecs, &ProcessLauncherImpl::preReadyRead); -} - -bool ProcessLauncherImpl::waitForFinished(int msecs) -{ - if (m_state == QProcess::NotRunning) - return true; - return waitForSignal(msecs, &ProcessLauncherImpl::preFinished); -} - -bool ProcessLauncherImpl::waitForSignal(int msecs, const PreSignal &preSignal) -{ - if (m_canceled) - return false; - - bool ok = false; - QEventLoop loop; - QTimer::singleShot(msecs, &loop, &QEventLoop::quit); - connect(this, preSignal, &loop, [&loop, &ok]() { - ok = true; - loop.quit(); - }); - loop.exec(QEventLoop::ExcludeUserInputEvents); - return ok; -} - -void ProcessLauncherImpl::start(const QString &program, const QStringList &arguments, QIODevice::OpenMode mode) -{ - // TODO: pass the open mode to StartProcessPacket - if (m_socketError) { - m_error = QProcess::FailedToStart; - emit errorOccurred(m_error); - return; - } - m_command = program; - m_arguments = arguments; - m_state = QProcess::Starting; - m_openMode = mode; - if (LauncherInterface::socket()->isReady()) - doStart(); -} - -void ProcessLauncherImpl::doStart() -{ - StartProcessPacket p(token()); - p.command = m_command; - p.arguments = m_arguments; - p.env = m_environment.toStringList(); - p.workingDir = m_workingDirectory; - p.openMode = m_openMode; - p.channelMode = m_channelMode; - sendPacket(p); -} - void ProcessLauncherImpl::cancel() { - if (m_canceled) - return; - switch (m_state) { - case QProcess::NotRunning: - break; - case QProcess::Starting: - m_errorString = QCoreApplication::translate("Utils::QtcProcess", - "Process canceled before it was started."); - m_error = QProcess::FailedToStart; - m_state = QProcess::NotRunning; - if (LauncherInterface::socket()->isReady()) - sendPacket(StopProcessPacket(token())); - else - emit errorOccurred(m_error); - break; - case QProcess::Running: - sendPacket(StopProcessPacket(token())); - m_state = QProcess::NotRunning; - break; - } - m_canceled = true; -} - -void ProcessLauncherImpl::handlePacket(LauncherPacketType type, quintptr token, const QByteArray &payload) -{ - if (token != this->token()) - return; - switch (type) { - case LauncherPacketType::ProcessError: - handleErrorPacket(payload); - break; - case LauncherPacketType::ProcessStarted: - handleStartedPacket(payload); - break; - case LauncherPacketType::ProcessFinished: - handleFinishedPacket(payload); - break; - default: - QTC_ASSERT(false, break); - } -} - -void ProcessLauncherImpl::handleSocketReady() -{ - m_socketError = false; - if (m_state == QProcess::Starting) - doStart(); -} - -void ProcessLauncherImpl::handleSocketError(const QString &message) -{ - m_socketError = true; - m_errorString = QCoreApplication::translate("Utils::QtcProcess", - "Internal socket error: %1").arg(message); - if (m_state != QProcess::NotRunning) { - m_state = QProcess::NotRunning; - m_error = QProcess::FailedToStart; - emit errorOccurred(m_error); - } -} - -void ProcessLauncherImpl::handleErrorPacket(const QByteArray &packetData) -{ - QTC_ASSERT(m_state != QProcess::NotRunning, return); - const auto packet = LauncherPacket::extractPacket(token(), packetData); - m_error = packet.error; - m_errorString = packet.errorString; - m_state = QProcess::NotRunning; - emit errorOccurred(m_error); -} - -void ProcessLauncherImpl::handleStartedPacket(const QByteArray &packetData) -{ - if (m_canceled) - return; - QTC_ASSERT(m_state == QProcess::Starting, return); - m_state = QProcess::Running; - const auto packet = LauncherPacket::extractPacket(token(), packetData); - m_processId = packet.processId; - emit preStarted(); - emit started(); -} - -void ProcessLauncherImpl::handleFinishedPacket(const QByteArray &packetData) -{ - if (m_canceled) - return; - QTC_ASSERT(m_state == QProcess::Running, return); - m_state = QProcess::NotRunning; - const auto packet = LauncherPacket::extractPacket(token(), packetData); - m_exitCode = packet.exitCode; - m_stdout = packet.stdOut; - m_stderr = packet.stdErr; - if (!m_stdout.isEmpty()) { - emit preReadyRead(); - emit readyReadStandardOutput(); - } - if (!m_stderr.isEmpty()) { - emit preReadyRead(); - emit readyReadStandardError(); - } - m_errorString = packet.errorString; - emit preFinished(); - emit finished(m_exitCode, packet.exitStatus); + m_handle->cancel(); } static ProcessInterface *newProcessInstance(QtcProcess::ProcessImpl processImpl)