Wake up QtcProcess when error occurred

Pipe the error packet through the caller's handle and flush
it immediately if the caller was awaiting for any signal.
Stop awaiting for any signal when we have flushed error signal.
In this case return false from waitForSignal() method
in order to mimic the QProcess behavior.

Fixes: QTCREATORBUG-26136
Change-Id: Ie80b4a63bd19a6309d4791ad39a698bd91bb8967
Reviewed-by: hjk <hjk@qt.io>
Reviewed-by: Christian Kandeler <christian.kandeler@qt.io>
This commit is contained in:
Jarek Kobus
2021-08-18 09:31:18 +02:00
parent 0f89b97b03
commit 43de2deebc
2 changed files with 47 additions and 30 deletions

View File

@@ -41,14 +41,8 @@ class CallerHandle : public QObject
public: public:
CallerHandle() : QObject() {} CallerHandle() : QObject() {}
// Always called in caller's thread. // Always called in caller's thread. Returns the list of flushed signals.
void flush() QList<LauncherHandle::SignalType> flush()
{
flushAndMatch(LauncherHandle::SignalType::NoSignal);
}
// Always called in caller's thread. Returns true is the matchingSignal was flushed.
bool flushAndMatch(LauncherHandle::SignalType matchingSignal)
{ {
QList<LauncherHandle::SignalType> oldSignals; QList<LauncherHandle::SignalType> oldSignals;
{ {
@@ -56,13 +50,13 @@ public:
oldSignals = m_signals; oldSignals = m_signals;
m_signals = {}; m_signals = {};
} }
bool signalMatched = false;
for (LauncherHandle::SignalType signalType : qAsConst(oldSignals)) { for (LauncherHandle::SignalType signalType : qAsConst(oldSignals)) {
if (matchingSignal == signalType)
signalMatched = true;
switch (signalType) { switch (signalType) {
case LauncherHandle::SignalType::NoSignal: case LauncherHandle::SignalType::NoSignal:
break; break;
case LauncherHandle::SignalType::Error:
emit errorOccurred();
break;
case LauncherHandle::SignalType::Started: case LauncherHandle::SignalType::Started:
emit started(); emit started();
break; break;
@@ -74,7 +68,7 @@ public:
break; break;
} }
} }
return signalMatched; return oldSignals;
} }
void appendSignal(LauncherHandle::SignalType signalType) void appendSignal(LauncherHandle::SignalType signalType)
{ {
@@ -89,14 +83,18 @@ public:
} }
bool shouldFlushFor(LauncherHandle::SignalType signalType) bool shouldFlushFor(LauncherHandle::SignalType signalType)
{ {
// TODO: Should we always flush when the list isn't empty?
QMutexLocker locker(&m_mutex); QMutexLocker locker(&m_mutex);
if (m_signals.contains(signalType)) if (m_signals.contains(signalType))
return true; return true;
if (m_signals.contains(LauncherHandle::SignalType::Error))
return true;
if (m_signals.contains(LauncherHandle::SignalType::Finished)) if (m_signals.contains(LauncherHandle::SignalType::Finished))
return true; return true;
return false; return false;
} }
signals: signals:
void errorOccurred();
void started(); void started();
void readyRead(); void readyRead();
void finished(); void finished();
@@ -131,33 +129,36 @@ void LauncherHandle::handlePacket(LauncherPacketType type, const QByteArray &pay
void LauncherHandle::handleErrorPacket(const QByteArray &packetData) void LauncherHandle::handleErrorPacket(const QByteArray &packetData)
{ {
QMutexLocker locker(&m_mutex); QMutexLocker locker(&m_mutex);
if (m_waitingFor != SignalType::NoSignal) wakeUpIfWaitingFor(SignalType::Error);
m_waitCondition.wakeOne();
m_failed = true;
const auto packet = LauncherPacket::extractPacket<ProcessErrorPacket>(m_token, packetData); const auto packet = LauncherPacket::extractPacket<ProcessErrorPacket>(m_token, packetData);
m_error = packet.error; m_error = packet.error;
m_errorString = packet.errorString; m_errorString = packet.errorString;
// TODO: pipe it through the callers handle? if (!m_callerHandle)
emit errorOccurred(m_error); return;
m_callerHandle->appendSignal(SignalType::Error);
flushCaller();
} }
// call me with mutex locked // call me with mutex locked
void LauncherHandle::wakeUpIfWaitingFor(SignalType wakeUpSignal) void LauncherHandle::wakeUpIfWaitingFor(SignalType newSignal)
{ {
// TODO: should we always wake up in case m_waitingFor != NoSignal?
// The matching signal came // The matching signal came
const bool signalMatched = (m_waitingFor == wakeUpSignal); const bool signalMatched = (m_waitingFor == newSignal);
// E.g. if we are waiting for ReadyRead and we got Finished signal instead -> wake it, too. // E.g. if we are waiting for ReadyRead and we got Finished or Error signal instead -> wake it, too.
const bool finishedSignalWhileWaiting = const bool finishedOrErrorWhileWaiting =
(m_waitingFor != SignalType::NoSignal) && (wakeUpSignal == SignalType::Finished); (m_waitingFor != SignalType::NoSignal)
&& ((newSignal == SignalType::Finished) || (newSignal == SignalType::Error));
// Wake up, flush and continue waiting. // Wake up, flush and continue waiting.
// E.g. when being in waitingForFinished() state and Started or ReadyRead signal came. // E.g. when being in waitingForFinished() state and Started or ReadyRead signal came.
const bool continueWaitingAfterFlushing = const bool continueWaitingAfterFlushing =
((m_waitingFor == SignalType::Finished) && (wakeUpSignal != SignalType::Finished)) ((m_waitingFor == SignalType::Finished) && (newSignal != SignalType::Finished))
|| ((m_waitingFor == SignalType::ReadyRead) && (wakeUpSignal == SignalType::Started)); || ((m_waitingFor == SignalType::ReadyRead) && (newSignal == SignalType::Started));
const bool shouldWake = signalMatched const bool shouldWake = signalMatched
|| continueWaitingAfterFlushing || finishedOrErrorWhileWaiting
|| finishedSignalWhileWaiting; || continueWaitingAfterFlushing;
if (shouldWake) if (shouldWake)
m_waitCondition.wakeOne(); m_waitCondition.wakeOne();
@@ -276,10 +277,14 @@ bool LauncherHandle::waitForSignal(int msecs, SignalType newSignal)
if (timedOut) if (timedOut)
break; break;
m_awaitingShouldContinue = true; // TODO: make it recursive? m_awaitingShouldContinue = true; // TODO: make it recursive?
const bool continueWaitingAfterFlushing = !m_callerHandle->flushAndMatch(newSignal); const QList<SignalType> flushedSignals = m_callerHandle->flush();
const bool wasCanceled = !m_awaitingShouldContinue; const bool wasCanceled = !m_awaitingShouldContinue;
m_awaitingShouldContinue = false; m_awaitingShouldContinue = false;
if (!continueWaitingAfterFlushing) const bool errorOccurred = flushedSignals.contains(SignalType::Error);
if (errorOccurred)
return false; // apparently QProcess behaves like this in case of error
const bool newSignalFlushed = flushedSignals.contains(newSignal);
if (newSignalFlushed) // so we don't continue waiting
return true; return true;
if (wasCanceled) if (wasCanceled)
return true; // or false? is false only in case of timeout? return true; // or false? is false only in case of timeout?
@@ -389,6 +394,7 @@ void LauncherHandle::createCallerHandle()
QMutexLocker locker(&m_mutex); // may be not needed, as we call it just after Launcher's c'tor QMutexLocker locker(&m_mutex); // may be not needed, as we call it just after Launcher's c'tor
QTC_CHECK(m_callerHandle == nullptr); QTC_CHECK(m_callerHandle == nullptr);
m_callerHandle = new CallerHandle(); m_callerHandle = new CallerHandle();
connect(m_callerHandle, &CallerHandle::errorOccurred, this, &LauncherHandle::slotErrorOccurred, Qt::DirectConnection);
connect(m_callerHandle, &CallerHandle::started, this, &LauncherHandle::slotStarted, Qt::DirectConnection); connect(m_callerHandle, &CallerHandle::started, this, &LauncherHandle::slotStarted, Qt::DirectConnection);
connect(m_callerHandle, &CallerHandle::readyRead, this, &LauncherHandle::slotReadyRead, Qt::DirectConnection); connect(m_callerHandle, &CallerHandle::readyRead, this, &LauncherHandle::slotReadyRead, Qt::DirectConnection);
connect(m_callerHandle, &CallerHandle::finished, this, &LauncherHandle::slotFinished, Qt::DirectConnection); connect(m_callerHandle, &CallerHandle::finished, this, &LauncherHandle::slotFinished, Qt::DirectConnection);
@@ -402,6 +408,16 @@ void LauncherHandle::destroyCallerHandle()
m_callerHandle = nullptr; m_callerHandle = nullptr;
} }
void LauncherHandle::slotErrorOccurred()
{
QProcess::ProcessError error = QProcess::UnknownError;
{
QMutexLocker locker(&m_mutex);
error = m_error;
}
emit errorOccurred(error);
}
void LauncherHandle::slotStarted() void LauncherHandle::slotStarted()
{ {
emit started(); emit started();

View File

@@ -117,6 +117,7 @@ signals:
private: private:
enum class SignalType { enum class SignalType {
NoSignal, NoSignal,
Error,
Started, Started,
ReadyRead, ReadyRead,
Finished Finished
@@ -128,6 +129,7 @@ private:
void doStart(); void doStart();
void slotErrorOccurred();
void slotStarted(); void slotStarted();
void slotReadyRead(); void slotReadyRead();
void slotFinished(); void slotFinished();
@@ -149,7 +151,7 @@ private:
void handleSocketReady(); void handleSocketReady();
void handleSocketError(const QString &message); void handleSocketError(const QString &message);
void wakeUpIfWaitingFor(SignalType wakeUpSignal); void wakeUpIfWaitingFor(SignalType newSignal);
QByteArray readAndClear(QByteArray &data) QByteArray readAndClear(QByteArray &data)
{ {
@@ -167,7 +169,6 @@ private:
SignalType m_waitingFor = SignalType::NoSignal; SignalType m_waitingFor = SignalType::NoSignal;
QProcess::ProcessState m_processState = QProcess::NotRunning; QProcess::ProcessState m_processState = QProcess::NotRunning;
std::atomic_bool m_failed = false;
bool m_awaitingShouldContinue = false; // cancel() sets it to false, modified only in caller's thread bool m_awaitingShouldContinue = false; // cancel() sets it to false, modified only in caller's thread
int m_processId = 0; int m_processId = 0;
int m_exitCode = 0; int m_exitCode = 0;