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:
Jarek Kobus
2021-07-14 16:02:25 +02:00
parent 92b5afc3de
commit f7cf48bc28
5 changed files with 614 additions and 291 deletions

View File

@@ -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<void (QProcess::*)(int, QProcess::ExitStatus)>(&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"

View File

@@ -27,7 +27,8 @@
#include "utils_global.h"
#include <QtCore/qobject.h>
#include <QObject>
#include <QThread>
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

View File

@@ -24,16 +24,293 @@
****************************************************************************/
#include "launchersocket.h"
#include "launcherinterface.h"
#include "qtcassert.h"
#include <QtCore/qcoreapplication.h>
#include <QtCore/qtimer.h>
#include <QtNetwork/qlocalsocket.h>
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<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)
{
qRegisterMetaType<Utils::Internal::LauncherPacketType>();
@@ -44,10 +321,49 @@ void LauncherSocket::sendData(const QByteArray &data)
{
if (!isReady())
return;
std::lock_guard<std::mutex> 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<int>(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<int>(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<std::mutex> 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"

View File

@@ -29,8 +29,14 @@
#include <QtCore/qobject.h>
#include <mutex>
#include <QHash>
#include <QMutex>
#include <QMutexLocker>
#include <QProcess>
#include <QWaitCondition>
#include <vector>
#include <atomic>
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<QLocalSocket *> m_socket{nullptr};
PacketParser m_packetParser;
std::vector<QByteArray> m_requests;
std::mutex m_requestsMutex;
mutable QMutex m_mutex;
QHash<quintptr, LauncherHandle *> m_handles;
};
} // namespace Internal

View File

@@ -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<quintptr>(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<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);
m_handle->cancel();
}
static ProcessInterface *newProcessInstance(QtcProcess::ProcessImpl processImpl)