Introduce ProcessBlockingInterface

This replaces the ProcessInterface::waitFor...() methods.
It's not obligatory to provide this interface when
implementing ProcessInterface subclass. In this case
generic blocking implementation will be used.

The generic implementation usually isn't as efficient as
the custom one, however, for some sophisticated implementations
of process interface, like e.g. SshProcessInterface, providing
custom implementation is really difficult and error prone.

That's why we try to keep a balance: we provide two custom
implementations for QProcessImpl and for ProcessLauncherImpl,
as they are relatively easy to implement, and rely on
generic implementation for others.

Change-Id: Ifc8bd354479ec67b2e8f74f1510f8de8883e9b94
Reviewed-by: <github-actions-qt-creator@cristianadam.eu>
Reviewed-by: hjk <hjk@qt.io>
This commit is contained in:
Jarek Kobus
2022-06-08 14:24:40 +02:00
parent 0efb8d3346
commit 0b2899215f
8 changed files with 190 additions and 181 deletions

View File

@@ -322,10 +322,34 @@ bool DefaultImpl::ensureProgramExists(const QString &program)
return false;
}
class QProcessBlockingImpl : public ProcessBlockingInterface
{
public:
QProcessBlockingImpl(QProcess *process) : m_process(process) {}
private:
bool waitForSignal(ProcessSignalType signalType, int msecs) final
{
switch (signalType) {
case ProcessSignalType::Started:
return m_process->waitForStarted(msecs);
case ProcessSignalType::ReadyRead:
return m_process->waitForReadyRead(msecs);
case ProcessSignalType::Done:
return m_process->waitForFinished(msecs);
}
return false;
}
QProcess *m_process = nullptr;
};
class QProcessImpl final : public DefaultImpl
{
public:
QProcessImpl() : m_process(new ProcessHelper(this))
QProcessImpl()
: m_process(new ProcessHelper(this))
, m_blockingImpl(new QProcessBlockingImpl(m_process))
{
connect(m_process, &QProcess::started,
this, &QProcessImpl::handleStarted);
@@ -361,9 +385,7 @@ private:
}
}
bool waitForStarted(int msecs) final { return m_process->waitForStarted(msecs); }
bool waitForReadyRead(int msecs) final { return m_process->waitForReadyRead(msecs); }
bool waitForFinished(int msecs) final { return m_process->waitForFinished(msecs); }
virtual ProcessBlockingInterface *processBlockingInterface() const { return m_blockingImpl; }
void doDefaultStart(const QString &program, const QStringList &arguments) final
{
@@ -411,7 +433,8 @@ private:
emit done(result);
}
ProcessHelper *m_process;
ProcessHelper *m_process = nullptr;
QProcessBlockingImpl *m_blockingImpl = nullptr;
};
static uint uniqueToken()
@@ -420,6 +443,33 @@ static uint uniqueToken()
return ++globalUniqueToken;
}
class ProcessLauncherBlockingImpl : public ProcessBlockingInterface
{
public:
ProcessLauncherBlockingImpl(CallerHandle *caller) : m_caller(caller) {}
private:
bool waitForSignal(ProcessSignalType signalType, int msecs) final
{
// TODO: Remove CallerHandle::SignalType
const CallerHandle::SignalType type = [signalType] {
switch (signalType) {
case ProcessSignalType::Started:
return CallerHandle::SignalType::Started;
case ProcessSignalType::ReadyRead:
return CallerHandle::SignalType::ReadyRead;
case ProcessSignalType::Done:
return CallerHandle::SignalType::Done;
}
QTC_CHECK(false);
return CallerHandle::SignalType::NoSignal;
}();
return m_caller->waitForSignal(type, msecs);
}
CallerHandle *m_caller = nullptr;
};
class ProcessLauncherImpl final : public DefaultImpl
{
Q_OBJECT
@@ -434,6 +484,7 @@ public:
this, &ProcessInterface::readyRead);
connect(m_handle, &CallerHandle::done,
this, &ProcessInterface::done);
m_blockingImpl = new ProcessLauncherBlockingImpl(m_handle);
}
~ProcessLauncherImpl() final
{
@@ -462,9 +513,7 @@ private:
}
}
bool waitForStarted(int msecs) final { return m_handle->waitForStarted(msecs); }
bool waitForReadyRead(int msecs) final { return m_handle->waitForReadyRead(msecs); }
bool waitForFinished(int msecs) final { return m_handle->waitForFinished(msecs); }
virtual ProcessBlockingInterface *processBlockingInterface() const { return m_blockingImpl; }
void doDefaultStart(const QString &program, const QStringList &arguments) final
{
@@ -476,6 +525,7 @@ private:
const uint m_token = 0;
// Lives in caller's thread.
CallerHandle *m_handle = nullptr;
ProcessLauncherBlockingImpl *m_blockingImpl = nullptr;
};
static ProcessImpl defaultProcessImpl()
@@ -535,13 +585,15 @@ private:
const ProcessResultData m_resultData;
};
class GeneralProcessBlockingImpl;
class ProcessInterfaceHandler : public QObject
{
public:
ProcessInterfaceHandler(QtcProcessPrivate *caller, ProcessInterface *process);
ProcessInterfaceHandler(GeneralProcessBlockingImpl *caller, ProcessInterface *process);
// Called from caller's thread exclusively.
bool waitForSignal(int msecs, ProcessSignalType newSignal);
bool waitForSignal(ProcessSignalType newSignal, int msecs);
void moveToCallerThread();
private:
@@ -555,11 +607,44 @@ private:
void handleDone(const ProcessResultData &data);
void appendSignal(ProcessInterfaceSignal *newSignal);
QtcProcessPrivate *m_caller = nullptr;
GeneralProcessBlockingImpl *m_caller = nullptr;
QMutex m_mutex;
QWaitCondition m_waitCondition;
};
class GeneralProcessBlockingImpl : public ProcessBlockingInterface
{
public:
GeneralProcessBlockingImpl(QtcProcessPrivate *parent);
void flush() { flushSignals(takeAllSignals()); }
bool flushFor(ProcessSignalType signalType) {
return flushSignals(takeSignalsFor(signalType), &signalType);
}
bool shouldFlush() const { QMutexLocker locker(&m_mutex); return !m_signals.isEmpty(); }
// Called from ProcessInterfaceHandler thread exclusively.
void appendSignal(ProcessInterfaceSignal *launcherSignal);
private:
// Called from caller's thread exclusively
bool waitForSignal(ProcessSignalType newSignal, int msecs) final;
QList<ProcessInterfaceSignal *> takeAllSignals();
QList<ProcessInterfaceSignal *> takeSignalsFor(ProcessSignalType signalType);
bool flushSignals(const QList<ProcessInterfaceSignal *> &signalList,
ProcessSignalType *signalType = nullptr);
void handleStartedSignal(const StartedSignal *launcherSignal);
void handleReadyReadSignal(const ReadyReadSignal *launcherSignal);
void handleDoneSignal(const DoneSignal *launcherSignal);
QtcProcessPrivate *m_caller = nullptr;
std::unique_ptr<ProcessInterfaceHandler> m_processHandler;
mutable QMutex m_mutex;
QList<ProcessInterfaceSignal *> m_signals;
};
class QtcProcessPrivate : public QObject
{
public:
@@ -591,11 +676,18 @@ public:
void setProcessInterface(ProcessInterface *process)
{
m_process.reset(process);
m_processHandler.reset(new ProcessInterfaceHandler(this, process));
m_process->setParent(this);
connect(m_process.get(), &ProcessInterface::started,
this, &QtcProcessPrivate::handleStarted);
connect(m_process.get(), &ProcessInterface::readyRead,
this, &QtcProcessPrivate::handleReadyRead);
connect(m_process.get(), &ProcessInterface::done,
this, &QtcProcessPrivate::handleDone);
// In order to move the process into another thread together with handle
m_process->setParent(m_processHandler.get());
m_processHandler->setParent(this);
m_blockingInterface.reset(process->processBlockingInterface());
if (!m_blockingInterface)
m_blockingInterface.reset(new GeneralProcessBlockingImpl(this));
m_blockingInterface->setParent(this);
}
CommandLine fullCommandLine() const
@@ -624,14 +716,11 @@ public:
}
QtcProcess *q;
std::unique_ptr<ProcessInterfaceHandler> m_processHandler;
std::unique_ptr<ProcessBlockingInterface> m_blockingInterface;
std::unique_ptr<ProcessInterface> m_process;
ProcessSetupData m_setup;
void slotTimeout();
void handleStartedSignal(const StartedSignal *launcherSignal);
void handleReadyReadSignal(const ReadyReadSignal *launcherSignal);
void handleDoneSignal(const DoneSignal *launcherSignal);
void handleStarted(qint64 processId, qint64 applicationMainThreadId);
void handleReadyRead(const QByteArray &outputData, const QByteArray &errorData);
void handleDone(const ProcessResultData &data);
@@ -647,31 +736,11 @@ public:
ProcessResult interpretExitCode(int exitCode);
// === ProcessInterfaceHandler related ===
// Called from caller's thread exclusively
bool waitForSignal(int msecs, ProcessSignalType newSignal);
void flush() { flushSignals(takeAllSignals()); }
bool flushFor(ProcessSignalType signalType) {
return flushSignals(takeSignalsFor(signalType), &signalType);
}
QList<ProcessInterfaceSignal *> takeAllSignals();
QList<ProcessInterfaceSignal *> takeSignalsFor(ProcessSignalType signalType);
bool flushSignals(const QList<ProcessInterfaceSignal *> &signalList,
ProcessSignalType *signalType = nullptr);
bool shouldFlush() const { QMutexLocker locker(&m_mutex); return !m_signals.isEmpty(); }
bool waitForSignal(ProcessSignalType signalType, int msecs);
Qt::ConnectionType connectionType() const;
void sendControlSignal(ControlSignal controlSignal);
// Called from ProcessInterfaceHandler thread exclusively.
void appendSignal(ProcessInterfaceSignal *launcherSignal);
mutable QMutex m_mutex;
QList<ProcessInterfaceSignal *> m_signals;
QTimer m_killTimer;
// =======================================
QProcess::ProcessState m_state = QProcess::NotRunning;
qint64 m_processId = 0;
qint64 m_applicationMainThreadId = 0;
@@ -692,10 +761,11 @@ public:
Guard m_guard;
};
ProcessInterfaceHandler::ProcessInterfaceHandler(QtcProcessPrivate *caller,
ProcessInterfaceHandler::ProcessInterfaceHandler(GeneralProcessBlockingImpl *caller,
ProcessInterface *process)
: m_caller(caller)
{
process->disconnect();
connect(process, &ProcessInterface::started,
this, &ProcessInterfaceHandler::handleStarted);
connect(process, &ProcessInterface::readyRead,
@@ -705,7 +775,7 @@ ProcessInterfaceHandler::ProcessInterfaceHandler(QtcProcessPrivate *caller,
}
// Called from caller's thread exclusively.
bool ProcessInterfaceHandler::waitForSignal(int msecs, ProcessSignalType newSignal)
bool ProcessInterfaceHandler::waitForSignal(ProcessSignalType newSignal, int msecs)
{
QDeadlineTimer deadline(msecs);
while (true) {
@@ -769,17 +839,20 @@ void ProcessInterfaceHandler::appendSignal(ProcessInterfaceSignal *newSignal)
}
m_waitCondition.wakeOne();
// call in callers thread
QMetaObject::invokeMethod(m_caller, &QtcProcessPrivate::flush);
QMetaObject::invokeMethod(m_caller, &GeneralProcessBlockingImpl::flush);
}
// Called from caller's thread exclusively
bool QtcProcessPrivate::waitForSignal(int msecs, ProcessSignalType newSignal)
GeneralProcessBlockingImpl::GeneralProcessBlockingImpl(QtcProcessPrivate *parent)
: m_caller(parent)
, m_processHandler(new ProcessInterfaceHandler(this, parent->m_process.get()))
{
const QDeadlineTimer timeout(msecs);
const QDeadlineTimer currentKillTimeout(m_killTimer.remainingTime());
const bool needsSplit = m_killTimer.isActive() ? timeout > currentKillTimeout : false;
const QDeadlineTimer mainTimeout = needsSplit ? currentKillTimeout : timeout;
// In order to move the process interface into another thread together with handle
parent->m_process.get()->setParent(m_processHandler.get());
m_processHandler->setParent(this);
}
bool GeneralProcessBlockingImpl::waitForSignal(ProcessSignalType newSignal, int msecs)
{
m_processHandler->setParent(nullptr);
QThread thread;
@@ -789,12 +862,7 @@ bool QtcProcessPrivate::waitForSignal(int msecs, ProcessSignalType newSignal)
// the caller here is blocked, so all signals should be buffered and we are going
// to flush them from inside waitForSignal().
m_processHandler->moveToThread(&thread);
bool result = m_processHandler->waitForSignal(mainTimeout.remainingTime(), newSignal);
if (!result && needsSplit) {
m_killTimer.stop();
sendControlSignal(ControlSignal::Kill);
result = m_processHandler->waitForSignal(timeout.remainingTime(), newSignal);
}
const bool result = m_processHandler->waitForSignal(newSignal, msecs);
m_processHandler->moveToCallerThread();
m_processHandler->setParent(this);
thread.quit();
@@ -803,14 +871,14 @@ bool QtcProcessPrivate::waitForSignal(int msecs, ProcessSignalType newSignal)
}
// Called from caller's thread exclusively
QList<ProcessInterfaceSignal *> QtcProcessPrivate::takeAllSignals()
QList<ProcessInterfaceSignal *> GeneralProcessBlockingImpl::takeAllSignals()
{
QMutexLocker locker(&m_mutex);
return std::exchange(m_signals, {});
}
// Called from caller's thread exclusively
QList<ProcessInterfaceSignal *> QtcProcessPrivate::takeSignalsFor(ProcessSignalType signalType)
QList<ProcessInterfaceSignal *> GeneralProcessBlockingImpl::takeSignalsFor(ProcessSignalType signalType)
{
// If we are flushing for ReadyRead or Done - flush all.
if (signalType != ProcessSignalType::Started)
@@ -840,7 +908,7 @@ QList<ProcessInterfaceSignal *> QtcProcessPrivate::takeSignalsFor(ProcessSignalT
}
// Called from caller's thread exclusively
bool QtcProcessPrivate::flushSignals(const QList<ProcessInterfaceSignal *> &signalList,
bool GeneralProcessBlockingImpl::flushSignals(const QList<ProcessInterfaceSignal *> &signalList,
ProcessSignalType *signalType)
{
bool signalMatched = false;
@@ -866,14 +934,50 @@ bool QtcProcessPrivate::flushSignals(const QList<ProcessInterfaceSignal *> &sign
return signalMatched;
}
// Called from caller's thread exclusively
void GeneralProcessBlockingImpl::handleStartedSignal(const StartedSignal *aSignal)
{
m_caller->handleStarted(aSignal->processId(), aSignal->applicationMainThreadId());
}
void GeneralProcessBlockingImpl::handleReadyReadSignal(const ReadyReadSignal *aSignal)
{
m_caller->handleReadyRead(aSignal->stdOut(), aSignal->stdErr());
}
void GeneralProcessBlockingImpl::handleDoneSignal(const DoneSignal *aSignal)
{
m_caller->handleDone(aSignal->resultData());
}
// Called from ProcessInterfaceHandler thread exclusively.
void GeneralProcessBlockingImpl::appendSignal(ProcessInterfaceSignal *newSignal)
{
QMutexLocker locker(&m_mutex);
m_signals.append(newSignal);
}
bool QtcProcessPrivate::waitForSignal(ProcessSignalType newSignal, int msecs)
{
const QDeadlineTimer timeout(msecs);
const QDeadlineTimer currentKillTimeout(m_killTimer.remainingTime());
const bool needsSplit = m_killTimer.isActive() ? timeout > currentKillTimeout : false;
const QDeadlineTimer mainTimeout = needsSplit ? currentKillTimeout : timeout;
bool result = m_blockingInterface->waitForSignal(newSignal, mainTimeout.remainingTime());
if (!result && needsSplit) {
m_killTimer.stop();
sendControlSignal(ControlSignal::Kill);
result = m_blockingInterface->waitForSignal(newSignal, timeout.remainingTime());
}
return result;
}
Qt::ConnectionType QtcProcessPrivate::connectionType() const
{
return (m_process->thread() == thread()) ? Qt::DirectConnection
: Qt::BlockingQueuedConnection;
}
// Called from caller's thread exclusively
void QtcProcessPrivate::sendControlSignal(ControlSignal controlSignal)
{
QTC_ASSERT(QThread::currentThread() == thread(), return);
@@ -885,13 +989,6 @@ void QtcProcessPrivate::sendControlSignal(ControlSignal controlSignal)
}, connectionType());
}
// Called from ProcessInterfaceHandler thread exclusively.
void QtcProcessPrivate::appendSignal(ProcessInterfaceSignal *newSignal)
{
QMutexLocker locker(&m_mutex);
m_signals.append(newSignal);
}
void QtcProcessPrivate::clearForRun()
{
m_hangTimerCount = 0;
@@ -1444,8 +1541,8 @@ bool QtcProcess::waitForStarted(int msecs)
return true;
if (d->m_state == QProcess::NotRunning)
return false;
return s_waitForStarted.measureAndRun(&QtcProcessPrivate::waitForSignal, d, msecs,
ProcessSignalType::Started);
return s_waitForStarted.measureAndRun(&QtcProcessPrivate::waitForSignal, d,
ProcessSignalType::Started, msecs);
}
bool QtcProcess::waitForReadyRead(int msecs)
@@ -1453,7 +1550,7 @@ bool QtcProcess::waitForReadyRead(int msecs)
QTC_ASSERT(d->m_process, return false);
if (d->m_state == QProcess::NotRunning)
return false;
return d->waitForSignal(msecs, ProcessSignalType::ReadyRead);
return d->waitForSignal(ProcessSignalType::ReadyRead, msecs);
}
bool QtcProcess::waitForFinished(int msecs)
@@ -1461,7 +1558,7 @@ bool QtcProcess::waitForFinished(int msecs)
QTC_ASSERT(d->m_process, return false);
if (d->m_state == QProcess::NotRunning)
return false;
return d->waitForSignal(msecs, ProcessSignalType::Done);
return d->waitForSignal(ProcessSignalType::Done, msecs);
}
QByteArray QtcProcess::readAllStandardOutput()
@@ -1511,9 +1608,9 @@ void QtcProcess::close()
d->m_process->disconnect();
d->m_process.release()->deleteLater();
}
if (d->m_processHandler) {
d->m_processHandler->disconnect();
d->m_processHandler.release()->deleteLater();
if (d->m_blockingInterface) {
d->m_blockingInterface->disconnect();
d->m_blockingInterface.release()->deleteLater();
}
d->clearForRun();
}
@@ -1854,22 +1951,6 @@ 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)
{
m_killTimer.stop();
handleDone(aSignal->resultData());
}
void QtcProcessPrivate::handleStarted(qint64 processId, qint64 applicationMainThreadId)
{
QTC_CHECK(m_state == QProcess::Starting);
@@ -1908,6 +1989,7 @@ void QtcProcessPrivate::handleReadyRead(const QByteArray &outputData, const QByt
void QtcProcessPrivate::handleDone(const ProcessResultData &data)
{
m_killTimer.stop();
m_resultData = data;
switch (m_state) {