forked from qt-creator/qt-creator
Refactor process launcher
Move launcher process into a separate thread. Implement blocking API by using wait condition on the caller's side. Replay all collected signals from the launcher's thread when leaving waitingFor* state. In case we were not waiting for anything deliver the signals asynchronously from launcher's thread to the caller's thread. Change-Id: Id44fe5f7ceaac407004984a1dfb6ea65f197d297 Reviewed-by: hjk <hjk@qt.io>
This commit is contained in:
@@ -69,35 +69,51 @@ private:
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace Internal
|
|
||||||
|
|
||||||
using namespace Utils::Internal;
|
|
||||||
|
|
||||||
static QString launcherSocketName()
|
static QString launcherSocketName()
|
||||||
{
|
{
|
||||||
return QStringLiteral("qtcreator_processlauncher-%1")
|
return QStringLiteral("qtcreator_processlauncher-%1")
|
||||||
.arg(QString::number(qApp->applicationPid()));
|
.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))
|
: m_server(new QLocalServer(this)), m_socket(new LauncherSocket(this))
|
||||||
{
|
{
|
||||||
QObject::connect(m_server, &QLocalServer::newConnection,
|
QObject::connect(m_server, &QLocalServer::newConnection,
|
||||||
this, &LauncherInterface::handleNewConnection);
|
this, &LauncherInterfacePrivate::handleNewConnection);
|
||||||
}
|
}
|
||||||
|
|
||||||
LauncherInterface &LauncherInterface::instance()
|
LauncherInterfacePrivate::~LauncherInterfacePrivate()
|
||||||
{
|
|
||||||
static LauncherInterface p;
|
|
||||||
return p;
|
|
||||||
}
|
|
||||||
|
|
||||||
LauncherInterface::~LauncherInterface()
|
|
||||||
{
|
{
|
||||||
m_server->disconnect();
|
m_server->disconnect();
|
||||||
}
|
}
|
||||||
|
|
||||||
void LauncherInterface::doStart()
|
void LauncherInterfacePrivate::doStart()
|
||||||
{
|
{
|
||||||
if (++m_startRequests > 1)
|
if (++m_startRequests > 1)
|
||||||
return;
|
return;
|
||||||
@@ -108,19 +124,19 @@ void LauncherInterface::doStart()
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
m_process = new LauncherProcess(this);
|
m_process = new LauncherProcess(this);
|
||||||
connect(m_process, &QProcess::errorOccurred, this, &LauncherInterface::handleProcessError);
|
connect(m_process, &QProcess::errorOccurred, this, &LauncherInterfacePrivate::handleProcessError);
|
||||||
connect(m_process,
|
connect(m_process,
|
||||||
static_cast<void (QProcess::*)(int, QProcess::ExitStatus)>(&QProcess::finished),
|
static_cast<void (QProcess::*)(int, QProcess::ExitStatus)>(&QProcess::finished),
|
||||||
this, &LauncherInterface::handleProcessFinished);
|
this, &LauncherInterfacePrivate::handleProcessFinished);
|
||||||
connect(m_process, &QProcess::readyReadStandardError,
|
connect(m_process, &QProcess::readyReadStandardError,
|
||||||
this, &LauncherInterface::handleProcessStderr);
|
this, &LauncherInterfacePrivate::handleProcessStderr);
|
||||||
m_process->start(qApp->applicationDirPath() + QLatin1Char('/')
|
m_process->start(qApp->applicationDirPath() + QLatin1Char('/')
|
||||||
+ QLatin1String(RELATIVE_LIBEXEC_PATH)
|
+ QLatin1String(RELATIVE_LIBEXEC_PATH)
|
||||||
+ QLatin1String("/qtcreator_processlauncher"),
|
+ QLatin1String("/qtcreator_processlauncher"),
|
||||||
QStringList(m_server->fullServerName()));
|
QStringList(m_server->fullServerName()));
|
||||||
}
|
}
|
||||||
|
|
||||||
void LauncherInterface::doStop()
|
void LauncherInterfacePrivate::doStop()
|
||||||
{
|
{
|
||||||
if (--m_startRequests > 0)
|
if (--m_startRequests > 0)
|
||||||
return;
|
return;
|
||||||
@@ -134,7 +150,7 @@ void LauncherInterface::doStop()
|
|||||||
m_process = nullptr;
|
m_process = nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
void LauncherInterface::handleNewConnection()
|
void LauncherInterfacePrivate::handleNewConnection()
|
||||||
{
|
{
|
||||||
QLocalSocket * const socket = m_server->nextPendingConnection();
|
QLocalSocket * const socket = m_server->nextPendingConnection();
|
||||||
if (!socket)
|
if (!socket)
|
||||||
@@ -143,7 +159,7 @@ void LauncherInterface::handleNewConnection()
|
|||||||
m_socket->setSocket(socket);
|
m_socket->setSocket(socket);
|
||||||
}
|
}
|
||||||
|
|
||||||
void LauncherInterface::handleProcessError()
|
void LauncherInterfacePrivate::handleProcessError()
|
||||||
{
|
{
|
||||||
if (m_process->error() == QProcess::FailedToStart) {
|
if (m_process->error() == QProcess::FailedToStart) {
|
||||||
const QString launcherPathForUser
|
const QString launcherPathForUser
|
||||||
@@ -154,16 +170,60 @@ void LauncherInterface::handleProcessError()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void LauncherInterface::handleProcessFinished()
|
void LauncherInterfacePrivate::handleProcessFinished()
|
||||||
{
|
{
|
||||||
emit errorOccurred(QCoreApplication::translate("Utils::LauncherSocket",
|
emit errorOccurred(QCoreApplication::translate("Utils::LauncherSocket",
|
||||||
"Process launcher closed unexpectedly: %1")
|
"Process launcher closed unexpectedly: %1")
|
||||||
.arg(m_process->errorString()));
|
.arg(m_process->errorString()));
|
||||||
}
|
}
|
||||||
|
|
||||||
void LauncherInterface::handleProcessStderr()
|
void LauncherInterfacePrivate::handleProcessStderr()
|
||||||
{
|
{
|
||||||
qDebug() << "[launcher]" << m_process->readAllStandardError();
|
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
|
} // namespace Utils
|
||||||
|
|
||||||
|
#include "launcherinterface.moc"
|
||||||
|
@@ -27,7 +27,8 @@
|
|||||||
|
|
||||||
#include "utils_global.h"
|
#include "utils_global.h"
|
||||||
|
|
||||||
#include <QtCore/qobject.h>
|
#include <QObject>
|
||||||
|
#include <QThread>
|
||||||
|
|
||||||
QT_BEGIN_NAMESPACE
|
QT_BEGIN_NAMESPACE
|
||||||
class QLocalServer;
|
class QLocalServer;
|
||||||
@@ -37,6 +38,7 @@ namespace Utils {
|
|||||||
namespace Internal {
|
namespace Internal {
|
||||||
class LauncherProcess;
|
class LauncherProcess;
|
||||||
class LauncherSocket;
|
class LauncherSocket;
|
||||||
|
class LauncherInterfacePrivate;
|
||||||
}
|
}
|
||||||
|
|
||||||
class QTCREATOR_UTILS_EXPORT LauncherInterface : public QObject
|
class QTCREATOR_UTILS_EXPORT LauncherInterface : public QObject
|
||||||
@@ -46,9 +48,9 @@ public:
|
|||||||
static LauncherInterface &instance();
|
static LauncherInterface &instance();
|
||||||
~LauncherInterface() override;
|
~LauncherInterface() override;
|
||||||
|
|
||||||
static void startLauncher() { instance().doStart(); }
|
static void startLauncher();
|
||||||
static void stopLauncher() { instance().doStop(); }
|
static void stopLauncher();
|
||||||
static Internal::LauncherSocket *socket() { return instance().m_socket; }
|
static Internal::LauncherSocket *socket();
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void errorOccurred(const QString &error);
|
void errorOccurred(const QString &error);
|
||||||
@@ -56,17 +58,8 @@ signals:
|
|||||||
private:
|
private:
|
||||||
LauncherInterface();
|
LauncherInterface();
|
||||||
|
|
||||||
void doStart();
|
QThread m_thread;
|
||||||
void doStop();
|
Internal::LauncherInterfacePrivate *m_private;
|
||||||
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;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace Utils
|
} // namespace Utils
|
||||||
|
@@ -24,16 +24,293 @@
|
|||||||
****************************************************************************/
|
****************************************************************************/
|
||||||
|
|
||||||
#include "launchersocket.h"
|
#include "launchersocket.h"
|
||||||
|
#include "launcherinterface.h"
|
||||||
|
|
||||||
#include "qtcassert.h"
|
#include "qtcassert.h"
|
||||||
|
|
||||||
#include <QtCore/qcoreapplication.h>
|
#include <QtCore/qcoreapplication.h>
|
||||||
#include <QtCore/qtimer.h>
|
|
||||||
#include <QtNetwork/qlocalsocket.h>
|
#include <QtNetwork/qlocalsocket.h>
|
||||||
|
|
||||||
namespace Utils {
|
namespace Utils {
|
||||||
namespace Internal {
|
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<SignalType> 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<SignalType> 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<ProcessErrorPacket>(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<ProcessStartedPacket>(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<ProcessFinishedPacket>(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)
|
LauncherSocket::LauncherSocket(QObject *parent) : QObject(parent)
|
||||||
{
|
{
|
||||||
qRegisterMetaType<Utils::Internal::LauncherPacketType>();
|
qRegisterMetaType<Utils::Internal::LauncherPacketType>();
|
||||||
@@ -44,10 +321,49 @@ void LauncherSocket::sendData(const QByteArray &data)
|
|||||||
{
|
{
|
||||||
if (!isReady())
|
if (!isReady())
|
||||||
return;
|
return;
|
||||||
std::lock_guard<std::mutex> locker(m_requestsMutex);
|
QMutexLocker locker(&m_mutex);
|
||||||
m_requests.push_back(data);
|
m_requests.push_back(data);
|
||||||
if (m_requests.size() == 1)
|
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()
|
void LauncherSocket::shutdown()
|
||||||
@@ -58,7 +374,7 @@ void LauncherSocket::shutdown()
|
|||||||
socket->disconnect();
|
socket->disconnect();
|
||||||
socket->write(ShutdownPacket().serialize());
|
socket->write(ShutdownPacket().serialize());
|
||||||
socket->waitForBytesWritten(1000);
|
socket->waitForBytesWritten(1000);
|
||||||
socket->deleteLater();
|
socket->deleteLater(); // or schedule a queued call to delete later?
|
||||||
}
|
}
|
||||||
|
|
||||||
void LauncherSocket::setSocket(QLocalSocket *socket)
|
void LauncherSocket::setSocket(QLocalSocket *socket)
|
||||||
@@ -98,12 +414,13 @@ void LauncherSocket::handleSocketDataAvailable()
|
|||||||
"Internal protocol error: invalid packet size %1.").arg(e.size));
|
"Internal protocol error: invalid packet size %1.").arg(e.size));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
LauncherHandle *handle = handleForToken(m_packetParser.token());
|
||||||
|
if (handle) {
|
||||||
switch (m_packetParser.type()) {
|
switch (m_packetParser.type()) {
|
||||||
case LauncherPacketType::ProcessError:
|
case LauncherPacketType::ProcessError:
|
||||||
case LauncherPacketType::ProcessStarted:
|
case LauncherPacketType::ProcessStarted:
|
||||||
case LauncherPacketType::ProcessFinished:
|
case LauncherPacketType::ProcessFinished:
|
||||||
emit packetArrived(m_packetParser.type(), m_packetParser.token(),
|
handle->handlePacket(m_packetParser.type(), m_packetParser.packetData());
|
||||||
m_packetParser.packetData());
|
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
handleError(QCoreApplication::translate("Utils::LauncherSocket",
|
handleError(QCoreApplication::translate("Utils::LauncherSocket",
|
||||||
@@ -111,6 +428,10 @@ void LauncherSocket::handleSocketDataAvailable()
|
|||||||
.arg(static_cast<int>(m_packetParser.type())));
|
.arg(static_cast<int>(m_packetParser.type())));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
// qDebug() << "No handler for token" << m_packetParser.token() << m_handles;
|
||||||
|
// in this case the QtcProcess was canceled and deleted
|
||||||
|
}
|
||||||
handleSocketDataAvailable();
|
handleSocketDataAvailable();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -132,7 +453,7 @@ void LauncherSocket::handleRequests()
|
|||||||
{
|
{
|
||||||
const auto socket = m_socket.load();
|
const auto socket = m_socket.load();
|
||||||
QTC_ASSERT(socket, return);
|
QTC_ASSERT(socket, return);
|
||||||
std::lock_guard<std::mutex> locker(m_requestsMutex);
|
QMutexLocker locker(&m_mutex);
|
||||||
for (const QByteArray &request : qAsConst(m_requests))
|
for (const QByteArray &request : qAsConst(m_requests))
|
||||||
socket->write(request);
|
socket->write(request);
|
||||||
m_requests.clear();
|
m_requests.clear();
|
||||||
@@ -140,3 +461,5 @@ void LauncherSocket::handleRequests()
|
|||||||
|
|
||||||
} // namespace Internal
|
} // namespace Internal
|
||||||
} // namespace Utils
|
} // namespace Utils
|
||||||
|
|
||||||
|
#include "launchersocket.moc"
|
||||||
|
@@ -29,8 +29,14 @@
|
|||||||
|
|
||||||
#include <QtCore/qobject.h>
|
#include <QtCore/qobject.h>
|
||||||
|
|
||||||
#include <mutex>
|
#include <QHash>
|
||||||
|
#include <QMutex>
|
||||||
|
#include <QMutexLocker>
|
||||||
|
#include <QProcess>
|
||||||
|
#include <QWaitCondition>
|
||||||
|
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
#include <atomic>
|
||||||
|
|
||||||
QT_BEGIN_NAMESPACE
|
QT_BEGIN_NAMESPACE
|
||||||
class QLocalSocket;
|
class QLocalSocket;
|
||||||
@@ -41,22 +47,157 @@ class LauncherInterface;
|
|||||||
|
|
||||||
namespace Internal {
|
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
|
class LauncherSocket : public QObject
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
friend class Utils::LauncherInterface;
|
friend class LauncherInterfacePrivate;
|
||||||
public:
|
public:
|
||||||
bool isReady() const { return m_socket.load(); }
|
bool isReady() const { return m_socket.load(); }
|
||||||
void sendData(const QByteArray &data);
|
void sendData(const QByteArray &data);
|
||||||
|
|
||||||
|
LauncherHandle *registerHandle(quintptr token);
|
||||||
|
void unregisterHandle(quintptr token);
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void ready();
|
void ready();
|
||||||
void errorOccurred(const QString &error);
|
void errorOccurred(const QString &error);
|
||||||
void packetArrived(Utils::Internal::LauncherPacketType type, quintptr token,
|
|
||||||
const QByteArray &payload);
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
LauncherSocket(QObject *parent);
|
LauncherSocket(QObject *parent = nullptr);
|
||||||
|
|
||||||
|
LauncherHandle *handleForToken(quintptr token) const;
|
||||||
|
|
||||||
void setSocket(QLocalSocket *socket);
|
void setSocket(QLocalSocket *socket);
|
||||||
void shutdown();
|
void shutdown();
|
||||||
@@ -70,7 +211,8 @@ private:
|
|||||||
std::atomic<QLocalSocket *> m_socket{nullptr};
|
std::atomic<QLocalSocket *> m_socket{nullptr};
|
||||||
PacketParser m_packetParser;
|
PacketParser m_packetParser;
|
||||||
std::vector<QByteArray> m_requests;
|
std::vector<QByteArray> m_requests;
|
||||||
std::mutex m_requestsMutex;
|
mutable QMutex m_mutex;
|
||||||
|
QHash<quintptr, LauncherHandle *> m_handles;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace Internal
|
} // namespace Internal
|
||||||
|
@@ -281,49 +281,53 @@ class ProcessLauncherImpl : public ProcessInterface
|
|||||||
public:
|
public:
|
||||||
ProcessLauncherImpl() : ProcessInterface()
|
ProcessLauncherImpl() : ProcessInterface()
|
||||||
{
|
{
|
||||||
connect(LauncherInterface::socket(), &LauncherSocket::ready,
|
m_handle = LauncherInterface::socket()->registerHandle(token());
|
||||||
this, &ProcessLauncherImpl::handleSocketReady);
|
connect(m_handle, &LauncherHandle::errorOccurred,
|
||||||
connect(LauncherInterface::socket(), &LauncherSocket::errorOccurred,
|
this, &ProcessInterface::errorOccurred);
|
||||||
this, &ProcessLauncherImpl::handleSocketError);
|
connect(m_handle, &LauncherHandle::started,
|
||||||
connect(LauncherInterface::socket(), &LauncherSocket::packetArrived,
|
this, &ProcessInterface::started);
|
||||||
this, &ProcessLauncherImpl::handlePacket);
|
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 readAllStandardOutput() override { return m_handle->readAllStandardOutput(); }
|
||||||
QByteArray readAllStandardError() override { return readAndClear(m_stderr); }
|
QByteArray readAllStandardError() override { return m_handle->readAllStandardError(); }
|
||||||
|
|
||||||
void setProcessEnvironment(const QProcessEnvironment &environment) override
|
void setProcessEnvironment(const QProcessEnvironment &environment) override
|
||||||
{ m_environment = environment; }
|
{ m_handle->setProcessEnvironment(environment); }
|
||||||
void setWorkingDirectory(const QString &dir) override { m_workingDirectory = dir; }
|
void setWorkingDirectory(const QString &dir) override { m_handle->setWorkingDirectory(dir); }
|
||||||
void start(const QString &program, const QStringList &arguments, QIODevice::OpenMode mode) override;
|
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 terminate() override { cancel(); } // TODO: what are differences among terminate, kill and close?
|
||||||
void kill() override { cancel(); } // TODO: see above
|
void kill() override { cancel(); } // TODO: see above
|
||||||
void close() override { cancel(); } // TODO: see above
|
void close() override { cancel(); } // TODO: see above
|
||||||
qint64 write(const QByteArray &data) override { QTC_CHECK(false); return -1; }
|
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 setStandardInputFile(const QString &fileName) override { QTC_CHECK(false); }
|
||||||
void setProcessChannelMode(QProcess::ProcessChannelMode mode) override {
|
void setProcessChannelMode(QProcess::ProcessChannelMode mode) override { m_handle->setProcessChannelMode(mode); }
|
||||||
if (mode != QProcess::SeparateChannels && mode != QProcess::MergedChannels) {
|
|
||||||
qWarning("setProcessChannelMode: The only supported modes are SeparateChannels and MergedChannels.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
m_channelMode = mode;
|
|
||||||
}
|
|
||||||
|
|
||||||
qint64 bytesAvailable() const override { QTC_CHECK(false); return 0; }
|
qint64 bytesAvailable() const override { QTC_CHECK(false); return 0; }
|
||||||
QString program() const override { return m_command; }
|
QString program() const override { return m_handle->program(); }
|
||||||
QProcess::ProcessError error() const override { return m_error; }
|
QProcess::ProcessError error() const override { return m_handle->error(); }
|
||||||
QProcess::ProcessState state() const override { return m_state; }
|
QProcess::ProcessState state() const override { return m_handle->state(); }
|
||||||
qint64 processId() const override { return m_processId; }
|
qint64 processId() const override { return m_handle->processId(); }
|
||||||
QProcess::ExitStatus exitStatus() const override { QTC_CHECK(false); return QProcess::NormalExit; }
|
QProcess::ExitStatus exitStatus() const override { return m_handle->exitStatus(); }
|
||||||
QString errorString() const override { return m_errorString; }
|
QString errorString() const override { return m_handle->errorString(); }
|
||||||
void setErrorString(const QString &str) override { m_errorString = str; }
|
void setErrorString(const QString &str) override { m_handle->setErrorString(str); }
|
||||||
|
|
||||||
bool waitForStarted(int msecs) override;
|
bool waitForStarted(int msecs) override { return m_handle->waitForStarted(msecs); }
|
||||||
bool waitForReadyRead(int msecs) override;
|
bool waitForReadyRead(int msecs) override { QTC_CHECK(false); return false; }
|
||||||
bool waitForFinished(int msecs) override;
|
bool waitForFinished(int msecs) override { return m_handle->waitForFinished(msecs); }
|
||||||
|
|
||||||
void setLowPriority() override { QTC_CHECK(false); }
|
void setLowPriority() override { QTC_CHECK(false); }
|
||||||
bool lowPriority() const override { QTC_CHECK(false); return false; }
|
bool lowPriority() const override { QTC_CHECK(false); return false; }
|
||||||
@@ -337,223 +341,24 @@ public:
|
|||||||
void setNativeArguments(const QString &arguments) override { QTC_CHECK(false); }
|
void setNativeArguments(const QString &arguments) override { QTC_CHECK(false); }
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
signals:
|
|
||||||
void preStarted();
|
|
||||||
void preReadyRead();
|
|
||||||
void preFinished();
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
typedef void (ProcessLauncherImpl::*PreSignal)(void);
|
typedef void (ProcessLauncherImpl::*PreSignal)(void);
|
||||||
|
|
||||||
bool waitForSignal(int msecs, const PreSignal &preSignal);
|
|
||||||
void doStart();
|
|
||||||
void cancel();
|
void cancel();
|
||||||
void sendPacket(const Internal::LauncherPacket &packet)
|
void sendPacket(const Internal::LauncherPacket &packet)
|
||||||
{ LauncherInterface::socket()->sendData(packet.serialize()); }
|
{ LauncherInterface::socket()->sendData(packet.serialize()); }
|
||||||
QByteArray readAndClear(QByteArray &data)
|
|
||||||
{
|
|
||||||
const QByteArray tmp = data;
|
|
||||||
data.clear();
|
|
||||||
return tmp;
|
|
||||||
}
|
|
||||||
|
|
||||||
void handleSocketError(const QString &message);
|
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();
|
void handleSocketReady();
|
||||||
|
|
||||||
quintptr token() const { return reinterpret_cast<quintptr>(this); }
|
quintptr token() const { return reinterpret_cast<quintptr>(this); }
|
||||||
|
|
||||||
QString m_command;
|
LauncherHandle *m_handle = nullptr; // This object lives in a different thread!
|
||||||
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;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
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()
|
void ProcessLauncherImpl::cancel()
|
||||||
{
|
{
|
||||||
if (m_canceled)
|
m_handle->cancel();
|
||||||
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<ProcessErrorPacket>(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<ProcessStartedPacket>(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<ProcessFinishedPacket>(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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static ProcessInterface *newProcessInstance(QtcProcess::ProcessImpl processImpl)
|
static ProcessInterface *newProcessInstance(QtcProcess::ProcessImpl processImpl)
|
||||||
|
Reference in New Issue
Block a user