Make process reusable when killing the running one

Try to mimic the behavior of QProcess inside process launcher
as much as possible. After calling QtcProcess::kill() we send
StopProcessPacket to the process launcher - when we receive
it there we start reaping of the process itself and report
back the finished packet with a failure.

Remove canceled state from the LanucherHandle, as after
canceling the process we still need to handle the finished
(or others) signals - like in case of QProcess.

Change-Id: Id66b06449de06675a0ab6778e93e9782afea09d4
Reviewed-by: hjk <hjk@qt.io>
This commit is contained in:
Jarek Kobus
2021-08-04 09:58:20 +02:00
parent d09f2c52c6
commit e41b6a0a29
4 changed files with 15 additions and 36 deletions

View File

@@ -120,8 +120,6 @@ 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_canceled)
m_processState = QProcess::NotRunning;
if (m_waitingFor != SignalType::NoSignal) { if (m_waitingFor != SignalType::NoSignal) {
m_waitCondition.wakeOne(); m_waitCondition.wakeOne();
m_waitingFor = SignalType::NoSignal; m_waitingFor = SignalType::NoSignal;
@@ -166,8 +164,6 @@ void LauncherHandle::handleStartedPacket(const QByteArray &packetData)
{ {
QMutexLocker locker(&m_mutex); QMutexLocker locker(&m_mutex);
wakeUpIfWaitingFor(SignalType::Started); wakeUpIfWaitingFor(SignalType::Started);
if (m_canceled)
return;
m_processState = QProcess::Running; m_processState = QProcess::Running;
const auto packet = LauncherPacket::extractPacket<ProcessStartedPacket>(m_token, packetData); const auto packet = LauncherPacket::extractPacket<ProcessStartedPacket>(m_token, packetData);
m_processId = packet.processId; m_processId = packet.processId;
@@ -182,8 +178,6 @@ void LauncherHandle::handleReadyReadStandardOutput(const QByteArray &packetData)
{ {
QMutexLocker locker(&m_mutex); QMutexLocker locker(&m_mutex);
wakeUpIfWaitingFor(SignalType::ReadyRead); wakeUpIfWaitingFor(SignalType::ReadyRead);
if (m_canceled)
return;
const auto packet = LauncherPacket::extractPacket<ReadyReadStandardOutputPacket>(m_token, packetData); const auto packet = LauncherPacket::extractPacket<ReadyReadStandardOutputPacket>(m_token, packetData);
if (packet.standardChannel.isEmpty()) if (packet.standardChannel.isEmpty())
return; return;
@@ -200,8 +194,6 @@ void LauncherHandle::handleReadyReadStandardError(const QByteArray &packetData)
{ {
QMutexLocker locker(&m_mutex); QMutexLocker locker(&m_mutex);
wakeUpIfWaitingFor(SignalType::ReadyRead); wakeUpIfWaitingFor(SignalType::ReadyRead);
if (m_canceled)
return;
const auto packet = LauncherPacket::extractPacket<ReadyReadStandardErrorPacket>(m_token, packetData); const auto packet = LauncherPacket::extractPacket<ReadyReadStandardErrorPacket>(m_token, packetData);
if (packet.standardChannel.isEmpty()) if (packet.standardChannel.isEmpty())
return; return;
@@ -218,8 +210,6 @@ void LauncherHandle::handleFinishedPacket(const QByteArray &packetData)
{ {
QMutexLocker locker(&m_mutex); QMutexLocker locker(&m_mutex);
wakeUpIfWaitingFor(SignalType::Finished); wakeUpIfWaitingFor(SignalType::Finished);
if (m_canceled)
return;
m_processState = QProcess::NotRunning; m_processState = QProcess::NotRunning;
const auto packet = LauncherPacket::extractPacket<ProcessFinishedPacket>(m_token, packetData); const auto packet = LauncherPacket::extractPacket<ProcessFinishedPacket>(m_token, packetData);
m_exitCode = packet.exitCode; m_exitCode = packet.exitCode;
@@ -268,9 +258,6 @@ bool LauncherHandle::doWaitForSignal(int msecs, SignalType newSignal)
{ {
QMutexLocker locker(&m_mutex); QMutexLocker locker(&m_mutex);
QTC_ASSERT(m_waitingFor == SignalType::NoSignal, return false); QTC_ASSERT(m_waitingFor == SignalType::NoSignal, return false);
if (m_canceled) // we don't want to wait if we have canceled it before (ASSERT it?)
return false;
// It may happen, that after calling start() and before calling waitForStarted() we might have // It may happen, that after calling start() and before calling waitForStarted() we might have
// reached the Running (or even Finished) state already. In this case we should have // reached the Running (or even Finished) state already. In this case we should have
// collected Started (or even Finished) signal to be flushed - so we return true // collected Started (or even Finished) signal to be flushed - so we return true
@@ -325,7 +312,6 @@ void LauncherHandle::cancel()
} }
m_processState = QProcess::NotRunning; m_processState = QProcess::NotRunning;
m_canceled = true;
} }
void LauncherHandle::start(const QString &program, const QStringList &arguments, QIODevice::OpenMode mode) void LauncherHandle::start(const QString &program, const QStringList &arguments, QIODevice::OpenMode mode)

View File

@@ -72,7 +72,6 @@ public:
QProcess::ProcessState state() const QProcess::ProcessState state() const
{ QMutexLocker locker(&m_mutex); return m_processState; } { QMutexLocker locker(&m_mutex); return m_processState; }
void cancel(); void cancel();
bool isCanceled() const { return m_canceled; }
QByteArray readAllStandardOutput() QByteArray readAllStandardOutput()
{ QMutexLocker locker(&m_mutex); return readAndClear(m_stdout); } { QMutexLocker locker(&m_mutex); return readAndClear(m_stdout); }
@@ -163,7 +162,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_canceled = false;
std::atomic_bool m_failed = false; std::atomic_bool m_failed = false;
int m_processId = 0; int m_processId = 0;
int m_exitCode = 0; int m_exitCode = 0;

View File

@@ -48,6 +48,10 @@ public:
void cancel() void cancel()
{ {
if (state() == QProcess::NotRunning) {
deleteLater();
return;
}
switch (m_stopState) { switch (m_stopState) {
case StopState::Inactive: case StopState::Inactive:
m_stopState = StopState::Terminating; m_stopState = StopState::Terminating;
@@ -61,7 +65,7 @@ public:
break; break;
case StopState::Killing: case StopState::Killing:
m_stopState = StopState::Inactive; m_stopState = StopState::Inactive;
emit failedToStop(); deleteLater(); // TODO: employ something like Core::Reaper here
break; break;
} }
} }
@@ -74,9 +78,6 @@ public:
quintptr token() const { return m_token; } quintptr token() const { return m_token; }
signals:
void failedToStop();
private: private:
const quintptr m_token; const quintptr m_token;
QTimer * const m_stopTimer; QTimer * const m_stopTimer;
@@ -217,20 +218,6 @@ void LauncherSocketHandler::handleProcessFinished()
removeProcess(proc->token()); removeProcess(proc->token());
} }
void LauncherSocketHandler::handleStopFailure()
{
// Process did not react to a kill signal. Rare, but not unheard of.
// Forget about the associated Process object and report process exit to the client.
Process * proc = senderProcess();
ProcessFinishedPacket packet(proc->token());
packet.error = QProcess::Crashed;
packet.exitCode = -1;
packet.exitStatus = QProcess::CrashExit;
packet.stdErr = proc->readAllStandardError();
packet.stdOut = proc->readAllStandardOutput();
sendPacket(packet);
}
void LauncherSocketHandler::handleStartPacket() void LauncherSocketHandler::handleStartPacket()
{ {
Process *& process = m_processes[m_packetParser.token()]; Process *& process = m_processes[m_packetParser.token()];
@@ -262,6 +249,16 @@ void LauncherSocketHandler::handleStopPacket()
// This can happen if the process finishes on its own at about the same time the client // This can happen if the process finishes on its own at about the same time the client
// sends the request. // sends the request.
logDebug("got stop request when process was not running"); logDebug("got stop request when process was not running");
} else {
// We got the client request to stop the starting / running process.
// We report process exit to the client.
ProcessFinishedPacket packet(process->token());
packet.error = QProcess::Crashed;
packet.exitCode = -1;
packet.exitStatus = QProcess::CrashExit;
packet.stdErr = process->readAllStandardError();
packet.stdOut = process->readAllStandardOutput();
sendPacket(packet);
} }
removeProcess(process->token()); removeProcess(process->token());
} }
@@ -296,7 +293,6 @@ Process *LauncherSocketHandler::setupProcess(quintptr token)
this, &LauncherSocketHandler::handleReadyReadStandardError); this, &LauncherSocketHandler::handleReadyReadStandardError);
connect(p, static_cast<void (QProcess::*)(int, QProcess::ExitStatus)>(&QProcess::finished), connect(p, static_cast<void (QProcess::*)(int, QProcess::ExitStatus)>(&QProcess::finished),
this, &LauncherSocketHandler::handleProcessFinished); this, &LauncherSocketHandler::handleProcessFinished);
connect(p, &Process::failedToStop, this, &LauncherSocketHandler::handleStopFailure);
return p; return p;
} }

View File

@@ -57,7 +57,6 @@ private:
void handleReadyReadStandardOutput(); void handleReadyReadStandardOutput();
void handleReadyReadStandardError(); void handleReadyReadStandardError();
void handleProcessFinished(); void handleProcessFinished();
void handleStopFailure();
void handleStartPacket(); void handleStartPacket();
void handleStopPacket(); void handleStopPacket();