QtcProcess: Implement waitFor...() in general way

Task-number: QTCREATORBUG-27430
Change-Id: I34aff44258d2c7cabae0b63fe4e9ec55aa7b3b7d
Reviewed-by: <github-actions-qt-creator@cristianadam.eu>
Reviewed-by: Marcus Tillmanns <marcus.tillmanns@qt.io>
Reviewed-by: Qt CI Bot <qt_ci_bot@qt-project.org>
Reviewed-by: hjk <hjk@qt.io>
This commit is contained in:
Jarek Kobus
2022-04-25 20:12:43 +02:00
parent 15611ee822
commit 221414c5e8
3 changed files with 312 additions and 34 deletions

View File

@@ -47,10 +47,10 @@ private:
const CallerHandle::SignalType m_signalType; const CallerHandle::SignalType m_signalType;
}; };
class StartedSignal : public LauncherSignal class LauncherStartedSignal : public LauncherSignal
{ {
public: public:
StartedSignal(int processId) LauncherStartedSignal(int processId)
: LauncherSignal(CallerHandle::SignalType::Started) : LauncherSignal(CallerHandle::SignalType::Started)
, m_processId(processId) {} , m_processId(processId) {}
int processId() const { return m_processId; } int processId() const { return m_processId; }
@@ -58,16 +58,16 @@ private:
const int m_processId; const int m_processId;
}; };
class ReadyReadSignal : public LauncherSignal class LauncherReadyReadSignal : public LauncherSignal
{ {
public: public:
ReadyReadSignal(const QByteArray &stdOut, const QByteArray &stdErr) LauncherReadyReadSignal(const QByteArray &stdOut, const QByteArray &stdErr)
: LauncherSignal(CallerHandle::SignalType::ReadyRead) : LauncherSignal(CallerHandle::SignalType::ReadyRead)
, m_stdOut(stdOut) , m_stdOut(stdOut)
, m_stdErr(stdErr) {} , m_stdErr(stdErr) {}
QByteArray stdOut() const { return m_stdOut; } QByteArray stdOut() const { return m_stdOut; }
QByteArray stdErr() const { return m_stdErr; } QByteArray stdErr() const { return m_stdErr; }
void mergeWith(ReadyReadSignal *newSignal) { void mergeWith(LauncherReadyReadSignal *newSignal) {
m_stdOut += newSignal->stdOut(); m_stdOut += newSignal->stdOut();
m_stdErr += newSignal->stdErr(); m_stdErr += newSignal->stdErr();
} }
@@ -76,10 +76,10 @@ private:
QByteArray m_stdErr; QByteArray m_stdErr;
}; };
class DoneSignal : public LauncherSignal class LauncherDoneSignal : public LauncherSignal
{ {
public: public:
DoneSignal(const ProcessResultData &resultData) LauncherDoneSignal(const ProcessResultData &resultData)
: LauncherSignal(CallerHandle::SignalType::Done) : LauncherSignal(CallerHandle::SignalType::Done)
, m_resultData(resultData) {} , m_resultData(resultData) {}
ProcessResultData resultData() const { return m_resultData; } ProcessResultData resultData() const { return m_resultData; }
@@ -150,14 +150,14 @@ bool CallerHandle::flushFor(SignalType signalType)
case SignalType::NoSignal: case SignalType::NoSignal:
break; break;
case SignalType::Started: case SignalType::Started:
handleStarted(static_cast<const StartedSignal *>(storedSignal)); handleStarted(static_cast<const LauncherStartedSignal *>(storedSignal));
break; break;
case SignalType::ReadyRead: case SignalType::ReadyRead:
handleReadyRead(static_cast<const ReadyReadSignal *>(storedSignal)); handleReadyRead(static_cast<const LauncherReadyReadSignal *>(storedSignal));
break; break;
case SignalType::Done: case SignalType::Done:
signalMatched = true; signalMatched = true;
handleDone(static_cast<const DoneSignal *>(storedSignal)); handleDone(static_cast<const LauncherDoneSignal *>(storedSignal));
break; break;
} }
delete storedSignal; delete storedSignal;
@@ -173,7 +173,7 @@ bool CallerHandle::shouldFlush() const
return !m_signals.isEmpty(); return !m_signals.isEmpty();
} }
void CallerHandle::handleStarted(const StartedSignal *launcherSignal) void CallerHandle::handleStarted(const LauncherStartedSignal *launcherSignal)
{ {
QTC_ASSERT(isCalledFromCallersThread(), return); QTC_ASSERT(isCalledFromCallersThread(), return);
m_processState = QProcess::Running; m_processState = QProcess::Running;
@@ -181,13 +181,13 @@ void CallerHandle::handleStarted(const StartedSignal *launcherSignal)
emit started(m_processId); emit started(m_processId);
} }
void CallerHandle::handleReadyRead(const ReadyReadSignal *launcherSignal) void CallerHandle::handleReadyRead(const LauncherReadyReadSignal *launcherSignal)
{ {
QTC_ASSERT(isCalledFromCallersThread(), return); QTC_ASSERT(isCalledFromCallersThread(), return);
emit readyRead(launcherSignal->stdOut(), launcherSignal->stdErr()); emit readyRead(launcherSignal->stdOut(), launcherSignal->stdErr());
} }
void CallerHandle::handleDone(const DoneSignal *launcherSignal) void CallerHandle::handleDone(const LauncherDoneSignal *launcherSignal)
{ {
QTC_ASSERT(isCalledFromCallersThread(), return); QTC_ASSERT(isCalledFromCallersThread(), return);
m_processState = QProcess::NotRunning; m_processState = QProcess::NotRunning;
@@ -221,8 +221,8 @@ void CallerHandle::appendSignal(LauncherSignal *newSignal)
// Merge ReadyRead signals into one. // Merge ReadyRead signals into one.
if (lastSignal->signalType() == SignalType::ReadyRead if (lastSignal->signalType() == SignalType::ReadyRead
&& newSignal->signalType() == SignalType::ReadyRead) { && newSignal->signalType() == SignalType::ReadyRead) {
ReadyReadSignal *lastRead = static_cast<ReadyReadSignal *>(lastSignal); LauncherReadyReadSignal *lastRead = static_cast<LauncherReadyReadSignal *>(lastSignal);
ReadyReadSignal *newRead = static_cast<ReadyReadSignal *>(newSignal); LauncherReadyReadSignal *newRead = static_cast<LauncherReadyReadSignal *>(newSignal);
lastRead->mergeWith(newRead); lastRead->mergeWith(newRead);
delete newRead; delete newRead;
return; return;
@@ -446,7 +446,7 @@ void LauncherHandle::handleStartedPacket(const QByteArray &packetData)
return; return;
const auto packet = LauncherPacket::extractPacket<ProcessStartedPacket>(m_token, packetData); const auto packet = LauncherPacket::extractPacket<ProcessStartedPacket>(m_token, packetData);
m_callerHandle->appendSignal(new StartedSignal(packet.processId)); m_callerHandle->appendSignal(new LauncherStartedSignal(packet.processId));
flushCaller(); flushCaller();
} }
@@ -461,7 +461,7 @@ void LauncherHandle::handleReadyReadStandardOutput(const QByteArray &packetData)
if (packet.standardChannel.isEmpty()) if (packet.standardChannel.isEmpty())
return; return;
m_callerHandle->appendSignal(new ReadyReadSignal(packet.standardChannel, {})); m_callerHandle->appendSignal(new LauncherReadyReadSignal(packet.standardChannel, {}));
flushCaller(); flushCaller();
} }
@@ -476,7 +476,7 @@ void LauncherHandle::handleReadyReadStandardError(const QByteArray &packetData)
if (packet.standardChannel.isEmpty()) if (packet.standardChannel.isEmpty())
return; return;
m_callerHandle->appendSignal(new ReadyReadSignal({}, packet.standardChannel)); m_callerHandle->appendSignal(new LauncherReadyReadSignal({}, packet.standardChannel));
flushCaller(); flushCaller();
} }
@@ -494,8 +494,8 @@ void LauncherHandle::handleDonePacket(const QByteArray &packetData)
packet.error, packet.errorString }; packet.error, packet.errorString };
if (!stdOut.isEmpty() || !stdErr.isEmpty()) if (!stdOut.isEmpty() || !stdErr.isEmpty())
m_callerHandle->appendSignal(new ReadyReadSignal(stdOut, stdErr)); m_callerHandle->appendSignal(new LauncherReadyReadSignal(stdOut, stdErr));
m_callerHandle->appendSignal(new DoneSignal(result)); m_callerHandle->appendSignal(new LauncherDoneSignal(result));
flushCaller(); flushCaller();
} }
@@ -512,7 +512,7 @@ void LauncherHandle::handleSocketError(const QString &message)
"Internal socket error: %1").arg(message); "Internal socket error: %1").arg(message);
const ProcessResultData result = { 0, QProcess::NormalExit, QProcess::FailedToStart, const ProcessResultData result = { 0, QProcess::NormalExit, QProcess::FailedToStart,
errorString }; errorString };
m_callerHandle->appendSignal(new DoneSignal(result)); m_callerHandle->appendSignal(new LauncherDoneSignal(result));
flushCaller(); flushCaller();
} }

View File

@@ -51,9 +51,9 @@ namespace Internal {
class LauncherInterfacePrivate; class LauncherInterfacePrivate;
class LauncherHandle; class LauncherHandle;
class LauncherSignal; class LauncherSignal;
class StartedSignal; class LauncherStartedSignal;
class ReadyReadSignal; class LauncherReadyReadSignal;
class DoneSignal; class LauncherDoneSignal;
// All the methods and data fields in this class are called / accessed from the caller's thread. // All the methods and data fields in this class are called / accessed from the caller's thread.
// Exceptions are explicitly marked. // Exceptions are explicitly marked.
@@ -126,9 +126,9 @@ private:
return tmp; return tmp;
} }
void handleStarted(const StartedSignal *launcherSignal); void handleStarted(const LauncherStartedSignal *launcherSignal);
void handleReadyRead(const ReadyReadSignal *launcherSignal); void handleReadyRead(const LauncherReadyReadSignal *launcherSignal);
void handleDone(const DoneSignal *launcherSignal); void handleDone(const LauncherDoneSignal *launcherSignal);
// Lives in launcher's thread. Modified from caller's thread. // Lives in launcher's thread. Modified from caller's thread.
LauncherHandle *m_launcherHandle = nullptr; LauncherHandle *m_launcherHandle = nullptr;

View File

@@ -25,6 +25,7 @@
#include "qtcprocess.h" #include "qtcprocess.h"
#include "algorithm.h"
#include "commandline.h" #include "commandline.h"
#include "executeondestruction.h" #include "executeondestruction.h"
#include "hostosinfo.h" #include "hostosinfo.h"
@@ -485,6 +486,91 @@ static ProcessImpl defaultProcessImpl()
return ProcessImpl::ProcessLauncher; return ProcessImpl::ProcessLauncher;
} }
enum class SignalType {
NoSignal,
Started,
ReadyRead,
Done
};
class ProcessInterfaceSignal
{
public:
SignalType signalType() const { return m_signalType; }
virtual ~ProcessInterfaceSignal() = default;
protected:
ProcessInterfaceSignal(SignalType signalType) : m_signalType(signalType) {}
private:
const SignalType m_signalType;
};
class StartedSignal : public ProcessInterfaceSignal
{
public:
StartedSignal(qint64 processId, qint64 applicationMainThreadId)
: ProcessInterfaceSignal(SignalType::Started)
, m_processId(processId)
, m_applicationMainThreadId(applicationMainThreadId) {}
qint64 processId() const { return m_processId; }
qint64 applicationMainThreadId() const { return m_applicationMainThreadId; }
private:
const qint64 m_processId;
const qint64 m_applicationMainThreadId;
};
class ReadyReadSignal : public ProcessInterfaceSignal
{
public:
ReadyReadSignal(const QByteArray &stdOut, const QByteArray &stdErr)
: ProcessInterfaceSignal(SignalType::ReadyRead)
, m_stdOut(stdOut)
, m_stdErr(stdErr) {}
QByteArray stdOut() const { return m_stdOut; }
QByteArray stdErr() const { return m_stdErr; }
void mergeWith(ReadyReadSignal *newSignal) {
m_stdOut += newSignal->stdOut();
m_stdErr += newSignal->stdErr();
}
private:
QByteArray m_stdOut;
QByteArray m_stdErr;
};
class DoneSignal : public ProcessInterfaceSignal
{
public:
DoneSignal(const ProcessResultData &resultData)
: ProcessInterfaceSignal(SignalType::Done)
, m_resultData(resultData) {}
ProcessResultData resultData() const { return m_resultData; }
private:
const ProcessResultData m_resultData;
};
class ProcessInterfaceHandler : public QObject
{
public:
ProcessInterfaceHandler(QtcProcessPrivate *caller, ProcessInterface *process);
// Called from caller's thread exclusively.
bool waitForSignal(int msecs, SignalType newSignal);
private:
// Called from caller's thread exclusively.
bool doWaitForSignal(QDeadlineTimer deadline);
// Called from caller's thread when not waiting for signal,
// otherwise called from temporary thread.
void handleStarted(qint64 processId, qint64 applicationMainThreadId);
void handleReadyRead(const QByteArray &outputData, const QByteArray &errorData);
void handleDone(const ProcessResultData &data);
void appendSignal(ProcessInterfaceSignal *newSignal);
QtcProcessPrivate *m_caller = nullptr;
QMutex m_mutex;
QWaitCondition m_waitCondition;
};
class QtcProcessPrivate : public QObject class QtcProcessPrivate : public QObject
{ {
public: public:
@@ -508,14 +594,11 @@ public:
void setProcessInterface(ProcessInterface *process) void setProcessInterface(ProcessInterface *process)
{ {
m_process.reset(process); m_process.reset(process);
m_process->setParent(this); m_processHandler.reset(new ProcessInterfaceHandler(this, process));
connect(m_process.get(), &ProcessInterface::started, // In order to move the process into another thread together with handle
this, &QtcProcessPrivate::handleStarted); m_process->setParent(m_processHandler.get());
connect(m_process.get(), &ProcessInterface::readyRead, m_processHandler->setParent(this);
this, &QtcProcessPrivate::handleReadyRead);
connect(m_process.get(), &ProcessInterface::done,
this, &QtcProcessPrivate::handleDone);
} }
CommandLine fullCommandLine() const CommandLine fullCommandLine() const
@@ -549,10 +632,14 @@ public:
} }
QtcProcess *q; QtcProcess *q;
std::unique_ptr<ProcessInterfaceHandler> m_processHandler;
std::unique_ptr<ProcessInterface> m_process; std::unique_ptr<ProcessInterface> m_process;
ProcessSetupData m_setup; ProcessSetupData m_setup;
void slotTimeout(); void slotTimeout();
void handleStartedSignal(const StartedSignal *launcherSignal);
void handleReadyReadSignal(const ReadyReadSignal *launcherSignal);
void handleDoneSignal(const DoneSignal *launcherSignal);
void handleStarted(qint64 processId, qint64 applicationMainThreadId); void handleStarted(qint64 processId, qint64 applicationMainThreadId);
void handleReadyRead(const QByteArray &outputData, const QByteArray &errorData); void handleReadyRead(const QByteArray &outputData, const QByteArray &errorData);
void handleDone(const ProcessResultData &data); void handleDone(const ProcessResultData &data);
@@ -567,6 +654,19 @@ public:
ProcessResult interpretExitCode(int exitCode); ProcessResult interpretExitCode(int exitCode);
// === ProcessInterfaceHandler related ===
// Called from caller's thread exclusively
void flush() { flushFor(SignalType::NoSignal); }
bool flushFor(SignalType signalType);
bool shouldFlush() const { QMutexLocker locker(&m_mutex); return !m_signals.isEmpty(); }
// Called from ProcessInterfaceHandler thread exclusively.
void appendSignal(ProcessInterfaceSignal *launcherSignal);
mutable QMutex m_mutex;
QList<ProcessInterfaceSignal *> m_signals;
// =======================================
QProcess::ProcessState m_state = QProcess::NotRunning; QProcess::ProcessState m_state = QProcess::NotRunning;
qint64 m_processId = 0; qint64 m_processId = 0;
qint64 m_applicationMainThreadId = 0; qint64 m_applicationMainThreadId = 0;
@@ -596,6 +696,165 @@ public:
#define CALL_STACK_GUARD() Guard guard(m_callStackGuard) #define CALL_STACK_GUARD() Guard guard(m_callStackGuard)
ProcessInterfaceHandler::ProcessInterfaceHandler(QtcProcessPrivate *caller,
ProcessInterface *process)
: m_caller(caller)
{
connect(process, &ProcessInterface::started,
this, &ProcessInterfaceHandler::handleStarted);
connect(process, &ProcessInterface::readyRead,
this, &ProcessInterfaceHandler::handleReadyRead);
connect(process, &ProcessInterface::done,
this, &ProcessInterfaceHandler::handleDone);
}
// Called from caller's thread exclusively.
bool ProcessInterfaceHandler::waitForSignal(int msecs, SignalType newSignal)
{
QDeadlineTimer deadline(msecs);
while (true) {
if (deadline.hasExpired())
break;
if (!doWaitForSignal(deadline))
break;
// Matching (or Done) signal was flushed
if (m_caller->flushFor(newSignal))
return true;
// Otherwise continue awaiting (e.g. when ReadyRead came while waitForFinished())
}
return false;
}
// Called from caller's thread exclusively.
bool ProcessInterfaceHandler::doWaitForSignal(QDeadlineTimer deadline)
{
QMutexLocker locker(&m_mutex);
// Flush, if we have any stored signals.
// This must be called when holding laucher's mutex locked prior to the call to wait,
// so that it's done atomically.
if (m_caller->shouldFlush())
return true;
return m_waitCondition.wait(&m_mutex, deadline);
}
// Called from ProcessInterfaceHandler thread exclusively
void ProcessInterfaceHandler::handleStarted(qint64 processId, qint64 applicationMainThreadId)
{
appendSignal(new StartedSignal(processId, applicationMainThreadId));
}
// Called from ProcessInterfaceHandler thread exclusively
void ProcessInterfaceHandler::handleReadyRead(const QByteArray &outputData, const QByteArray &errorData)
{
appendSignal(new ReadyReadSignal(outputData, errorData));
}
// Called from ProcessInterfaceHandler thread exclusively
void ProcessInterfaceHandler::handleDone(const ProcessResultData &data)
{
appendSignal(new DoneSignal(data));
}
void ProcessInterfaceHandler::appendSignal(ProcessInterfaceSignal *newSignal)
{
{
QMutexLocker locker(&m_mutex);
m_caller->appendSignal(newSignal);
}
m_waitCondition.wakeOne();
// call in callers thread
QMetaObject::invokeMethod(m_caller, &QtcProcessPrivate::flush);
}
// Called from caller's thread exclusively
bool QtcProcessPrivate::flushFor(SignalType signalType)
{
QList<ProcessInterfaceSignal *> oldSignals;
{
QMutexLocker locker(&m_mutex);
const QList<SignalType> storedSignals =
Utils::transform(qAsConst(m_signals), [](const ProcessInterfaceSignal *aSignal) {
return aSignal->signalType();
});
// If we are flushing for ReadyRead or Done - flush all.
// If we are flushing for Started:
// - if Started was buffered - flush Started only.
// - otherwise if Done signal was buffered - flush all.
const bool flushAll = (signalType != SignalType::Started)
|| (!storedSignals.contains(SignalType::Started)
&& storedSignals.contains(SignalType::Done));
if (flushAll) {
oldSignals = m_signals;
m_signals = {};
} else {
auto matchingIndex = storedSignals.lastIndexOf(signalType);
if (matchingIndex >= 0) {
oldSignals = m_signals.mid(0, matchingIndex + 1);
m_signals = m_signals.mid(matchingIndex + 1);
}
}
}
bool signalMatched = false;
for (const ProcessInterfaceSignal *storedSignal : qAsConst(oldSignals)) {
const SignalType storedSignalType = storedSignal->signalType();
if (storedSignalType == signalType)
signalMatched = true;
switch (storedSignalType) {
case SignalType::NoSignal:
break;
case SignalType::Started:
handleStartedSignal(static_cast<const StartedSignal *>(storedSignal));
break;
case SignalType::ReadyRead:
handleReadyReadSignal(static_cast<const ReadyReadSignal *>(storedSignal));
break;
case SignalType::Done:
signalMatched = true;
handleDoneSignal(static_cast<const DoneSignal *>(storedSignal));
break;
}
delete storedSignal;
}
return signalMatched;
}
// Called from ProcessInterfaceHandler thread exclusively.
void QtcProcessPrivate::appendSignal(ProcessInterfaceSignal *newSignal)
{
QTC_ASSERT(newSignal->signalType() != SignalType::NoSignal, delete newSignal; return);
QMutexLocker locker(&m_mutex);
// TODO: we might assert if the caller's state is proper, e.g.
// start signal can't appear if we are in Running or NotRunning state,
// or finish signal can't appear if we are in NotRunning or Starting state,
// or readyRead signal can't appear if we are in NotRunning or Starting state,
// or error signal can't appear if we are in NotRunning state
// or FailedToStart error signal can't appear if we are in Running state
// or other than FailedToStart error signal can't appear if we are in Starting state.
if (!m_signals.isEmpty()) {
ProcessInterfaceSignal *lastSignal = m_signals.last();
QTC_ASSERT(lastSignal->signalType() != SignalType::Done,
qWarning() << "Buffering new signal for process" << m_setup.m_commandLine
<< "while the last done() signal wasn't flushed yet.");
// Merge ReadyRead signals into one.
if (lastSignal->signalType() == SignalType::ReadyRead
&& newSignal->signalType() == SignalType::ReadyRead) {
ReadyReadSignal *lastRead = static_cast<ReadyReadSignal *>(lastSignal);
ReadyReadSignal *newRead = static_cast<ReadyReadSignal *>(newSignal);
lastRead->mergeWith(newRead);
delete newRead;
return;
}
}
m_signals.append(newSignal);
}
void QtcProcessPrivate::clearForRun() void QtcProcessPrivate::clearForRun()
{ {
m_hangTimerCount = 0; m_hangTimerCount = 0;
@@ -1237,6 +1496,10 @@ void QtcProcess::close()
d->m_process->disconnect(); d->m_process->disconnect();
d->m_process.release()->deleteLater(); d->m_process.release()->deleteLater();
} }
if (d->m_processHandler) {
d->m_processHandler->disconnect();
d->m_processHandler.release()->deleteLater();
}
d->clearForRun(); d->clearForRun();
} }
@@ -1571,6 +1834,21 @@ void QtcProcessPrivate::slotTimeout()
} }
} }
void QtcProcessPrivate::handleStartedSignal(const StartedSignal *aSignal)
{
handleStarted(aSignal->processId(), aSignal->applicationMainThreadId());
}
void QtcProcessPrivate::handleReadyReadSignal(const ReadyReadSignal *aSignal)
{
handleReadyRead(aSignal->stdOut(), aSignal->stdErr());
}
void QtcProcessPrivate::handleDoneSignal(const DoneSignal *aSignal)
{
handleDone(aSignal->resultData());
}
void QtcProcessPrivate::handleStarted(qint64 processId, qint64 applicationMainThreadId) void QtcProcessPrivate::handleStarted(qint64 processId, qint64 applicationMainThreadId)
{ {
QTC_CHECK(m_state == QProcess::Starting); QTC_CHECK(m_state == QProcess::Starting);