2021-07-07 11:36:03 +02:00
|
|
|
/****************************************************************************
|
|
|
|
|
**
|
|
|
|
|
** Copyright (C) 2021 The Qt Company Ltd.
|
|
|
|
|
** Contact: https://www.qt.io/licensing/
|
|
|
|
|
**
|
|
|
|
|
** This file is part of Qt Creator.
|
|
|
|
|
**
|
|
|
|
|
** Commercial License Usage
|
|
|
|
|
** Licensees holding valid commercial Qt licenses may use this file in
|
|
|
|
|
** accordance with the commercial license agreement provided with the
|
|
|
|
|
** Software or, alternatively, in accordance with the terms contained in
|
|
|
|
|
** a written agreement between you and The Qt Company. For licensing terms
|
|
|
|
|
** and conditions see https://www.qt.io/terms-conditions. For further
|
|
|
|
|
** information use the contact form at https://www.qt.io/contact-us.
|
|
|
|
|
**
|
|
|
|
|
** GNU General Public License Usage
|
|
|
|
|
** Alternatively, this file may be used under the terms of the GNU
|
|
|
|
|
** General Public License version 3 as published by the Free Software
|
|
|
|
|
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
|
|
|
|
|
** included in the packaging of this file. Please review the following
|
|
|
|
|
** information to ensure the GNU General Public License requirements will
|
|
|
|
|
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
|
|
|
|
|
**
|
|
|
|
|
****************************************************************************/
|
|
|
|
|
|
2021-08-23 17:58:44 +02:00
|
|
|
#include "algorithm.h"
|
2021-07-07 11:36:03 +02:00
|
|
|
#include "launchersocket.h"
|
2021-07-14 16:02:25 +02:00
|
|
|
#include "launcherinterface.h"
|
2021-07-07 11:36:03 +02:00
|
|
|
|
|
|
|
|
#include "qtcassert.h"
|
|
|
|
|
|
2021-08-19 16:39:21 +02:00
|
|
|
#include <QCoreApplication>
|
|
|
|
|
#include <QLocalSocket>
|
|
|
|
|
#include <QMutexLocker>
|
2021-07-07 11:36:03 +02:00
|
|
|
|
2021-09-02 12:58:33 +02:00
|
|
|
#include <iostream>
|
|
|
|
|
|
2021-07-07 11:36:03 +02:00
|
|
|
namespace Utils {
|
|
|
|
|
namespace Internal {
|
|
|
|
|
|
2021-08-23 17:58:44 +02:00
|
|
|
class LauncherSignal
|
2021-07-14 16:02:25 +02:00
|
|
|
{
|
|
|
|
|
public:
|
2021-08-23 17:58:44 +02:00
|
|
|
CallerHandle::SignalType signalType() const { return m_signalType; }
|
2021-08-27 17:56:02 +02:00
|
|
|
virtual ~LauncherSignal() = default;
|
2021-08-23 17:58:44 +02:00
|
|
|
protected:
|
|
|
|
|
LauncherSignal(CallerHandle::SignalType signalType) : m_signalType(signalType) {}
|
2021-07-14 16:02:25 +02:00
|
|
|
private:
|
2021-08-23 17:58:44 +02:00
|
|
|
const CallerHandle::SignalType m_signalType;
|
2021-07-14 16:02:25 +02:00
|
|
|
};
|
|
|
|
|
|
2021-08-23 17:58:44 +02:00
|
|
|
class ErrorSignal : public LauncherSignal
|
2021-07-14 16:02:25 +02:00
|
|
|
{
|
2021-08-23 17:58:44 +02:00
|
|
|
public:
|
|
|
|
|
ErrorSignal(QProcess::ProcessError error, const QString &errorString)
|
|
|
|
|
: LauncherSignal(CallerHandle::SignalType::Error)
|
|
|
|
|
, m_error(error)
|
|
|
|
|
, m_errorString(errorString) {}
|
|
|
|
|
QProcess::ProcessError error() const { return m_error; }
|
|
|
|
|
QString errorString() const { return m_errorString; }
|
|
|
|
|
private:
|
|
|
|
|
const QProcess::ProcessError m_error;
|
|
|
|
|
const QString m_errorString;
|
|
|
|
|
};
|
2021-07-14 16:02:25 +02:00
|
|
|
|
2021-08-23 17:58:44 +02:00
|
|
|
class StartedSignal : public LauncherSignal
|
2021-07-14 16:02:25 +02:00
|
|
|
{
|
2021-08-23 17:58:44 +02:00
|
|
|
public:
|
|
|
|
|
StartedSignal(int processId)
|
|
|
|
|
: LauncherSignal(CallerHandle::SignalType::Started)
|
|
|
|
|
, m_processId(processId) {}
|
|
|
|
|
int processId() const { return m_processId; }
|
|
|
|
|
private:
|
|
|
|
|
const int m_processId;
|
|
|
|
|
};
|
2021-07-14 16:02:25 +02:00
|
|
|
|
2021-08-23 17:58:44 +02:00
|
|
|
class ReadyReadSignal : public LauncherSignal
|
2021-07-14 16:02:25 +02:00
|
|
|
{
|
2021-08-23 17:58:44 +02:00
|
|
|
public:
|
|
|
|
|
ReadyReadSignal(const QByteArray &stdOut, const QByteArray &stdErr)
|
|
|
|
|
: LauncherSignal(CallerHandle::SignalType::ReadyRead)
|
|
|
|
|
, m_stdOut(stdOut)
|
|
|
|
|
, m_stdErr(stdErr) {}
|
|
|
|
|
QByteArray stdOut() const { return m_stdOut; }
|
|
|
|
|
QByteArray stdErr() const { return m_stdErr; }
|
2021-08-25 08:20:44 +02:00
|
|
|
void mergeWith(ReadyReadSignal *newSignal) {
|
|
|
|
|
m_stdOut += newSignal->stdOut();
|
|
|
|
|
m_stdErr += newSignal->stdErr();
|
|
|
|
|
}
|
2021-08-23 17:58:44 +02:00
|
|
|
private:
|
2021-08-25 08:20:44 +02:00
|
|
|
QByteArray m_stdOut;
|
|
|
|
|
QByteArray m_stdErr;
|
2021-08-23 17:58:44 +02:00
|
|
|
};
|
2021-07-14 16:02:25 +02:00
|
|
|
|
2021-08-23 17:58:44 +02:00
|
|
|
class FinishedSignal : public LauncherSignal
|
2021-07-14 16:02:25 +02:00
|
|
|
{
|
2021-08-23 17:58:44 +02:00
|
|
|
public:
|
|
|
|
|
FinishedSignal(QProcess::ExitStatus exitStatus,
|
|
|
|
|
int exitCode)
|
|
|
|
|
: LauncherSignal(CallerHandle::SignalType::Finished)
|
|
|
|
|
, m_exitStatus(exitStatus)
|
|
|
|
|
, m_exitCode(exitCode) {}
|
|
|
|
|
QProcess::ExitStatus exitStatus() const { return m_exitStatus; }
|
|
|
|
|
int exitCode() const { return m_exitCode; }
|
|
|
|
|
private:
|
|
|
|
|
const QProcess::ExitStatus m_exitStatus;
|
|
|
|
|
const int m_exitCode;
|
|
|
|
|
};
|
2021-07-14 16:02:25 +02:00
|
|
|
|
2021-08-27 17:56:02 +02:00
|
|
|
CallerHandle::~CallerHandle()
|
|
|
|
|
{
|
|
|
|
|
qDeleteAll(m_signals);
|
|
|
|
|
}
|
|
|
|
|
|
2021-08-23 17:58:44 +02:00
|
|
|
bool CallerHandle::waitForStarted(int msecs)
|
2021-08-18 13:40:43 +02:00
|
|
|
{
|
2021-08-23 17:58:44 +02:00
|
|
|
return waitForSignal(msecs, SignalType::Started);
|
2021-08-18 13:40:43 +02:00
|
|
|
}
|
|
|
|
|
|
2021-08-23 17:58:44 +02:00
|
|
|
bool CallerHandle::waitForReadyRead(int msces)
|
2021-08-18 13:40:43 +02:00
|
|
|
{
|
2021-08-23 17:58:44 +02:00
|
|
|
return waitForSignal(msces, SignalType::ReadyRead);
|
2021-08-18 13:40:43 +02:00
|
|
|
}
|
|
|
|
|
|
2021-08-23 17:58:44 +02:00
|
|
|
bool CallerHandle::waitForFinished(int msecs)
|
2021-07-14 16:02:25 +02:00
|
|
|
{
|
2021-08-23 17:58:44 +02:00
|
|
|
return waitForSignal(msecs, SignalType::Finished);
|
2021-07-14 16:02:25 +02:00
|
|
|
}
|
|
|
|
|
|
2021-08-23 17:58:44 +02:00
|
|
|
QList<CallerHandle::SignalType> CallerHandle::flush()
|
2021-07-14 16:02:25 +02:00
|
|
|
{
|
2022-03-21 11:55:52 +01:00
|
|
|
return flushFor(SignalType::NoSignal);
|
2021-08-02 13:26:33 +02:00
|
|
|
}
|
|
|
|
|
|
2022-03-21 11:55:52 +01:00
|
|
|
QList<CallerHandle::SignalType> CallerHandle::flushFor(SignalType signalType)
|
2021-08-02 13:26:33 +02:00
|
|
|
{
|
2021-08-23 17:58:44 +02:00
|
|
|
QTC_ASSERT(isCalledFromCallersThread(), return {});
|
|
|
|
|
QList<LauncherSignal *> oldSignals;
|
2022-03-21 11:55:52 +01:00
|
|
|
QList<SignalType> flushedSignals;
|
2021-08-23 17:58:44 +02:00
|
|
|
{
|
|
|
|
|
// 1. If signalType is no signal - flush all
|
|
|
|
|
// 2. Flush all if we have any error
|
2021-11-04 10:27:08 +01:00
|
|
|
// 3. If we are flushing for Finished or ReadyRead, flush all, too
|
|
|
|
|
// 4. If we are flushing for Started, flush Started only
|
2021-08-02 13:26:33 +02:00
|
|
|
|
2022-03-21 11:55:52 +01:00
|
|
|
// In short: only when we are flushing for Started, flush started only signal,
|
|
|
|
|
// otherwise flush always all. (note: we can't flush for Error, since we don't have
|
|
|
|
|
// waitForError() method).
|
|
|
|
|
|
2021-08-23 17:58:44 +02:00
|
|
|
QMutexLocker locker(&m_mutex);
|
2021-08-02 13:26:33 +02:00
|
|
|
|
2022-03-21 11:55:52 +01:00
|
|
|
const QList<SignalType> storedSignals =
|
2021-08-23 17:58:44 +02:00
|
|
|
Utils::transform(qAsConst(m_signals), [](const LauncherSignal *launcherSignal) {
|
|
|
|
|
return launcherSignal->signalType();
|
|
|
|
|
});
|
2021-08-02 13:26:33 +02:00
|
|
|
|
2022-03-21 11:55:52 +01:00
|
|
|
const bool flushAll = (signalType != SignalType::Started)
|
|
|
|
|
|| storedSignals.contains(SignalType::Error);
|
2021-08-23 17:58:44 +02:00
|
|
|
if (flushAll) {
|
|
|
|
|
oldSignals = m_signals;
|
|
|
|
|
m_signals = {};
|
|
|
|
|
flushedSignals = storedSignals;
|
|
|
|
|
} else {
|
|
|
|
|
auto matchingIndex = storedSignals.lastIndexOf(signalType);
|
|
|
|
|
if (matchingIndex >= 0) {
|
|
|
|
|
oldSignals = m_signals.mid(0, matchingIndex + 1);
|
|
|
|
|
m_signals = m_signals.mid(matchingIndex + 1);
|
|
|
|
|
flushedSignals = storedSignals.mid(0, matchingIndex + 1);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
for (const LauncherSignal *storedSignal : qAsConst(oldSignals)) {
|
2022-03-21 11:55:52 +01:00
|
|
|
const SignalType storedSignalType = storedSignal->signalType();
|
2021-08-23 17:58:44 +02:00
|
|
|
switch (storedSignalType) {
|
|
|
|
|
case SignalType::NoSignal:
|
|
|
|
|
break;
|
|
|
|
|
case SignalType::Error:
|
|
|
|
|
handleError(static_cast<const ErrorSignal *>(storedSignal));
|
|
|
|
|
break;
|
|
|
|
|
case SignalType::Started:
|
|
|
|
|
handleStarted(static_cast<const StartedSignal *>(storedSignal));
|
|
|
|
|
break;
|
|
|
|
|
case SignalType::ReadyRead:
|
|
|
|
|
handleReadyRead(static_cast<const ReadyReadSignal *>(storedSignal));
|
|
|
|
|
break;
|
|
|
|
|
case SignalType::Finished:
|
|
|
|
|
handleFinished(static_cast<const FinishedSignal *>(storedSignal));
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
delete storedSignal;
|
|
|
|
|
}
|
|
|
|
|
return flushedSignals;
|
2021-07-14 16:02:25 +02:00
|
|
|
}
|
|
|
|
|
|
2021-08-23 17:58:44 +02:00
|
|
|
// Called from caller's thread exclusively.
|
2022-03-21 11:55:52 +01:00
|
|
|
bool CallerHandle::shouldFlush() const
|
2021-07-14 16:02:25 +02:00
|
|
|
{
|
2021-08-23 17:58:44 +02:00
|
|
|
QTC_ASSERT(isCalledFromCallersThread(), return false);
|
2021-07-14 16:02:25 +02:00
|
|
|
QMutexLocker locker(&m_mutex);
|
2022-03-21 11:55:52 +01:00
|
|
|
return !m_signals.isEmpty();
|
2021-07-14 16:02:25 +02:00
|
|
|
}
|
|
|
|
|
|
2021-08-23 17:58:44 +02:00
|
|
|
void CallerHandle::handleError(const ErrorSignal *launcherSignal)
|
2021-07-14 16:02:25 +02:00
|
|
|
{
|
2021-08-23 17:58:44 +02:00
|
|
|
QTC_ASSERT(isCalledFromCallersThread(), return);
|
2021-08-30 16:29:08 +02:00
|
|
|
m_processState = QProcess::NotRunning;
|
2022-04-08 17:24:59 +02:00
|
|
|
m_result.m_error = launcherSignal->error();
|
2022-04-04 10:24:37 +02:00
|
|
|
if (!launcherSignal->errorString().isEmpty())
|
2022-04-08 17:24:59 +02:00
|
|
|
m_result.m_errorString = launcherSignal->errorString();
|
|
|
|
|
if (m_result.m_error == QProcess::FailedToStart)
|
|
|
|
|
m_result.m_exitCode = 255; // This code is being returned by QProcess when FailedToStart error occurred
|
|
|
|
|
emit errorOccurred(m_result.m_error);
|
2021-07-14 16:02:25 +02:00
|
|
|
}
|
|
|
|
|
|
2021-08-23 17:58:44 +02:00
|
|
|
void CallerHandle::handleStarted(const StartedSignal *launcherSignal)
|
2021-07-14 16:02:25 +02:00
|
|
|
{
|
2021-08-23 17:58:44 +02:00
|
|
|
QTC_ASSERT(isCalledFromCallersThread(), return);
|
|
|
|
|
m_processState = QProcess::Running;
|
|
|
|
|
m_processId = launcherSignal->processId();
|
|
|
|
|
emit started();
|
2021-07-14 16:02:25 +02:00
|
|
|
}
|
|
|
|
|
|
2021-08-23 17:58:44 +02:00
|
|
|
void CallerHandle::handleReadyRead(const ReadyReadSignal *launcherSignal)
|
2021-07-14 16:02:25 +02:00
|
|
|
{
|
2021-08-23 17:58:44 +02:00
|
|
|
QTC_ASSERT(isCalledFromCallersThread(), return);
|
2022-03-29 17:32:43 +02:00
|
|
|
if (m_setup->m_processChannelMode == QProcess::MergedChannels) {
|
2021-09-02 12:58:33 +02:00
|
|
|
m_stdout += launcherSignal->stdOut();
|
2022-03-29 17:32:43 +02:00
|
|
|
m_stdout += launcherSignal->stdErr();
|
2021-09-02 12:58:33 +02:00
|
|
|
if (!m_stdout.isEmpty())
|
|
|
|
|
emit readyReadStandardOutput();
|
|
|
|
|
} else {
|
2022-03-29 17:32:43 +02:00
|
|
|
if (m_setup->m_processChannelMode == QProcess::ForwardedOutputChannel
|
|
|
|
|
|| m_setup->m_processChannelMode == QProcess::ForwardedChannels) {
|
|
|
|
|
std::cout << launcherSignal->stdOut().constData() << std::flush;
|
|
|
|
|
} else {
|
|
|
|
|
m_stdout += launcherSignal->stdOut();
|
|
|
|
|
if (!m_stdout.isEmpty())
|
|
|
|
|
emit readyReadStandardOutput();
|
|
|
|
|
}
|
|
|
|
|
if (m_setup->m_processChannelMode == QProcess::ForwardedErrorChannel
|
|
|
|
|
|| m_setup->m_processChannelMode == QProcess::ForwardedChannels) {
|
|
|
|
|
std::cerr << launcherSignal->stdErr().constData() << std::flush;
|
|
|
|
|
} else {
|
|
|
|
|
m_stderr += launcherSignal->stdErr();
|
|
|
|
|
if (!m_stderr.isEmpty())
|
|
|
|
|
emit readyReadStandardError();
|
|
|
|
|
}
|
2021-09-02 12:58:33 +02:00
|
|
|
}
|
2021-07-14 16:02:25 +02:00
|
|
|
}
|
|
|
|
|
|
2021-08-23 17:58:44 +02:00
|
|
|
void CallerHandle::handleFinished(const FinishedSignal *launcherSignal)
|
2021-08-18 13:40:43 +02:00
|
|
|
{
|
2021-08-23 17:58:44 +02:00
|
|
|
QTC_ASSERT(isCalledFromCallersThread(), return);
|
|
|
|
|
m_processState = QProcess::NotRunning;
|
2022-04-08 17:24:59 +02:00
|
|
|
m_result.m_exitStatus = launcherSignal->exitStatus();
|
|
|
|
|
m_result.m_exitCode = launcherSignal->exitCode();
|
2022-02-17 17:28:08 +01:00
|
|
|
emit finished();
|
2021-08-18 13:40:43 +02:00
|
|
|
}
|
|
|
|
|
|
2021-08-23 17:58:44 +02:00
|
|
|
// Called from launcher's thread exclusively.
|
2022-03-21 11:55:52 +01:00
|
|
|
void CallerHandle::appendSignal(LauncherSignal *newSignal)
|
2021-07-16 13:05:29 +02:00
|
|
|
{
|
2021-08-23 17:58:44 +02:00
|
|
|
QTC_ASSERT(!isCalledFromCallersThread(), return);
|
2022-03-21 11:55:52 +01:00
|
|
|
QTC_ASSERT(newSignal->signalType() != SignalType::NoSignal, delete newSignal; return);
|
2021-08-03 13:29:28 +02:00
|
|
|
|
2021-08-23 17:58:44 +02:00
|
|
|
QMutexLocker locker(&m_mutex);
|
|
|
|
|
QTC_ASSERT(isCalledFromLaunchersThread(), return);
|
2022-03-21 11:55:52 +01:00
|
|
|
|
|
|
|
|
// 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()) {
|
|
|
|
|
LauncherSignal *lastSignal = m_signals.last();
|
|
|
|
|
|
|
|
|
|
QTC_ASSERT(lastSignal->signalType() != SignalType::Finished,
|
|
|
|
|
qWarning() << "Buffering new signal for process" << m_command
|
|
|
|
|
<< "while the last finished() signal wasn't flushed yet.");
|
|
|
|
|
|
|
|
|
|
if (lastSignal->signalType() == SignalType::Error) {
|
|
|
|
|
ErrorSignal *lastError = static_cast<ErrorSignal *>(lastSignal);
|
|
|
|
|
QTC_ASSERT(lastError->error() != QProcess::FailedToStart,
|
|
|
|
|
qWarning() << "Buffering new signal for process" << m_command
|
|
|
|
|
<< "while the last FailedToStart error signal wasn't flushed yet.");
|
|
|
|
|
QTC_ASSERT(newSignal->signalType() == SignalType::Finished,
|
|
|
|
|
qWarning() << "Buffering non finished signal for process" << m_command
|
|
|
|
|
<< "while the last buffered signal was an error.");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 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;
|
|
|
|
|
}
|
2021-08-25 08:20:44 +02:00
|
|
|
}
|
2022-03-21 11:55:52 +01:00
|
|
|
m_signals.append(newSignal);
|
2021-07-16 13:05:29 +02:00
|
|
|
}
|
|
|
|
|
|
2021-08-23 17:58:44 +02:00
|
|
|
QProcess::ProcessState CallerHandle::state() const
|
2021-08-18 13:40:43 +02:00
|
|
|
{
|
|
|
|
|
return m_processState;
|
|
|
|
|
}
|
|
|
|
|
|
2022-04-01 15:14:05 +02:00
|
|
|
bool CallerHandle::isStartPacketAwaitingAndClear()
|
2021-07-14 16:02:25 +02:00
|
|
|
{
|
2022-04-01 15:14:05 +02:00
|
|
|
QMutexLocker locker(&m_mutex);
|
|
|
|
|
const bool startPacketExisted = m_startPacket.get();
|
|
|
|
|
m_startPacket.reset();
|
|
|
|
|
return startPacketExisted;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void CallerHandle::sendStopPacket(StopProcessPacket::SignalType signalType)
|
|
|
|
|
{
|
|
|
|
|
if (m_processState == QProcess::NotRunning)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
if (m_processState == QProcess::Running || !isStartPacketAwaitingAndClear()) {
|
|
|
|
|
StopProcessPacket packet(m_token);
|
|
|
|
|
packet.signalType = signalType;
|
|
|
|
|
sendPacket(packet);
|
|
|
|
|
return;
|
2021-07-14 16:02:25 +02:00
|
|
|
}
|
2022-04-01 15:14:05 +02:00
|
|
|
|
|
|
|
|
m_processState.store(QProcess::NotRunning);
|
|
|
|
|
m_result.m_errorString = QCoreApplication::translate("Utils::LauncherHandle",
|
|
|
|
|
"Process was canceled before it was started.");
|
|
|
|
|
m_result.m_error = QProcess::FailedToStart;
|
|
|
|
|
emit errorOccurred(m_result.m_error);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
void CallerHandle::terminate()
|
|
|
|
|
{
|
|
|
|
|
QTC_ASSERT(isCalledFromCallersThread(), return);
|
|
|
|
|
sendStopPacket(StopProcessPacket::SignalType::Terminate);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void CallerHandle::kill()
|
|
|
|
|
{
|
|
|
|
|
QTC_ASSERT(isCalledFromCallersThread(), return);
|
|
|
|
|
sendStopPacket(StopProcessPacket::SignalType::Kill);
|
2021-07-14 16:02:25 +02:00
|
|
|
}
|
|
|
|
|
|
2021-08-23 17:58:44 +02:00
|
|
|
QByteArray CallerHandle::readAllStandardOutput()
|
2021-08-18 13:40:43 +02:00
|
|
|
{
|
|
|
|
|
QTC_ASSERT(isCalledFromCallersThread(), return {});
|
|
|
|
|
return readAndClear(m_stdout);
|
|
|
|
|
}
|
|
|
|
|
|
2021-08-23 17:58:44 +02:00
|
|
|
QByteArray CallerHandle::readAllStandardError()
|
2021-08-18 13:40:43 +02:00
|
|
|
{
|
|
|
|
|
QTC_ASSERT(isCalledFromCallersThread(), return {});
|
|
|
|
|
return readAndClear(m_stderr);
|
|
|
|
|
}
|
|
|
|
|
|
2021-08-23 17:58:44 +02:00
|
|
|
qint64 CallerHandle::processId() const
|
2021-08-18 13:40:43 +02:00
|
|
|
{
|
|
|
|
|
QTC_ASSERT(isCalledFromCallersThread(), return 0);
|
|
|
|
|
return m_processId;
|
|
|
|
|
}
|
|
|
|
|
|
2022-04-08 17:24:59 +02:00
|
|
|
ProcessResultData CallerHandle::resultData() const
|
2021-08-18 13:40:43 +02:00
|
|
|
{
|
|
|
|
|
QTC_ASSERT(isCalledFromCallersThread(), return {});
|
2022-04-08 17:24:59 +02:00
|
|
|
return m_result;
|
2021-08-18 13:40:43 +02:00
|
|
|
}
|
|
|
|
|
|
2021-08-23 17:58:44 +02:00
|
|
|
void CallerHandle::setErrorString(const QString &str)
|
2021-08-18 13:40:43 +02:00
|
|
|
{
|
|
|
|
|
QTC_ASSERT(isCalledFromCallersThread(), return);
|
2022-04-08 17:24:59 +02:00
|
|
|
m_result.m_errorString = str;
|
2021-08-18 13:40:43 +02:00
|
|
|
}
|
|
|
|
|
|
2022-02-16 03:21:51 +01:00
|
|
|
void CallerHandle::start(const QString &program, const QStringList &arguments)
|
2021-07-14 16:02:25 +02:00
|
|
|
{
|
2021-08-18 13:40:43 +02:00
|
|
|
QTC_ASSERT(isCalledFromCallersThread(), return);
|
2021-08-23 17:58:44 +02:00
|
|
|
if (!m_launcherHandle || m_launcherHandle->isSocketError()) {
|
2022-04-08 17:24:59 +02:00
|
|
|
m_result.m_error = QProcess::FailedToStart;
|
|
|
|
|
emit errorOccurred(m_result.m_error);
|
2021-07-14 16:02:25 +02:00
|
|
|
return;
|
|
|
|
|
}
|
2021-08-26 12:02:39 +02:00
|
|
|
|
2021-08-25 09:28:05 +02:00
|
|
|
auto startWhenRunning = [&program, &oldProgram = m_command] {
|
|
|
|
|
qWarning() << "Trying to start" << program << "while" << oldProgram
|
|
|
|
|
<< "is still running for the same QtcProcess instance."
|
|
|
|
|
<< "The current call will be ignored.";
|
|
|
|
|
};
|
|
|
|
|
QTC_ASSERT(m_processState == QProcess::NotRunning, startWhenRunning(); return);
|
2021-08-26 12:02:39 +02:00
|
|
|
|
2021-08-19 14:34:46 +02:00
|
|
|
auto processLauncherNotStarted = [&program] {
|
|
|
|
|
qWarning() << "Trying to start" << program << "while process launcher wasn't started yet.";
|
|
|
|
|
};
|
|
|
|
|
QTC_ASSERT(LauncherInterface::isStarted(), processLauncherNotStarted());
|
2021-08-23 17:58:44 +02:00
|
|
|
|
|
|
|
|
QMutexLocker locker(&m_mutex);
|
2021-08-26 12:02:39 +02:00
|
|
|
m_command = program;
|
|
|
|
|
m_arguments = arguments;
|
2021-08-23 17:58:44 +02:00
|
|
|
m_processState = QProcess::Starting;
|
|
|
|
|
StartProcessPacket *p = new StartProcessPacket(m_token);
|
|
|
|
|
p->command = m_command;
|
|
|
|
|
p->arguments = m_arguments;
|
2022-03-02 14:39:38 +01:00
|
|
|
p->env = m_setup->m_environment.toStringList();
|
|
|
|
|
p->workingDir = m_setup->m_workingDirectory.path();
|
|
|
|
|
p->processMode = m_setup->m_processMode;
|
|
|
|
|
p->writeData = m_setup->m_writeData;
|
|
|
|
|
p->standardInputFile = m_setup->m_standardInputFile;
|
|
|
|
|
p->belowNormalPriority = m_setup->m_belowNormalPriority;
|
|
|
|
|
p->nativeArguments = m_setup->m_nativeArguments;
|
|
|
|
|
p->lowPriority = m_setup->m_lowPriority;
|
|
|
|
|
p->unixTerminalDisabled = m_setup->m_unixTerminalDisabled;
|
2022-03-14 17:02:11 +01:00
|
|
|
p->useCtrlCStub = m_setup->m_useCtrlCStub;
|
2021-08-23 17:58:44 +02:00
|
|
|
m_startPacket.reset(p);
|
2021-08-30 18:21:41 +02:00
|
|
|
if (LauncherInterface::isReady())
|
2021-07-14 16:02:25 +02:00
|
|
|
doStart();
|
|
|
|
|
}
|
|
|
|
|
|
2021-08-23 17:58:44 +02:00
|
|
|
// Called from caller's or launcher's thread.
|
|
|
|
|
void CallerHandle::startIfNeeded()
|
2021-08-06 14:43:03 +02:00
|
|
|
{
|
|
|
|
|
QMutexLocker locker(&m_mutex);
|
2021-08-23 17:58:44 +02:00
|
|
|
if (m_processState == QProcess::Starting)
|
|
|
|
|
doStart();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Called from caller's or launcher's thread. Call me with mutex locked.
|
|
|
|
|
void CallerHandle::doStart()
|
|
|
|
|
{
|
|
|
|
|
if (!m_startPacket)
|
|
|
|
|
return;
|
|
|
|
|
sendPacket(*m_startPacket);
|
2022-04-01 15:14:05 +02:00
|
|
|
m_startPacket.reset();
|
2021-08-23 17:58:44 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Called from caller's or launcher's thread.
|
|
|
|
|
void CallerHandle::sendPacket(const Internal::LauncherPacket &packet)
|
|
|
|
|
{
|
2021-08-30 18:21:41 +02:00
|
|
|
LauncherInterface::sendData(packet.serialize());
|
2021-08-23 17:58:44 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
qint64 CallerHandle::write(const QByteArray &data)
|
|
|
|
|
{
|
2021-08-18 13:40:43 +02:00
|
|
|
QTC_ASSERT(isCalledFromCallersThread(), return -1);
|
2021-08-06 14:43:03 +02:00
|
|
|
|
|
|
|
|
if (m_processState != QProcess::Running)
|
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
|
|
WritePacket p(m_token);
|
|
|
|
|
p.inputData = data;
|
|
|
|
|
sendPacket(p);
|
2021-08-06 17:34:21 +02:00
|
|
|
return data.size();
|
2021-08-06 14:43:03 +02:00
|
|
|
}
|
|
|
|
|
|
2021-08-23 17:58:44 +02:00
|
|
|
QString CallerHandle::program() const
|
2021-08-18 13:40:43 +02:00
|
|
|
{
|
2021-08-26 12:02:39 +02:00
|
|
|
QMutexLocker locker(&m_mutex);
|
2021-08-18 13:40:43 +02:00
|
|
|
return m_command;
|
|
|
|
|
}
|
|
|
|
|
|
2021-08-26 12:02:39 +02:00
|
|
|
QStringList CallerHandle::arguments() const
|
|
|
|
|
{
|
|
|
|
|
QMutexLocker locker(&m_mutex);
|
|
|
|
|
return m_arguments;
|
|
|
|
|
}
|
|
|
|
|
|
2022-03-02 14:39:38 +01:00
|
|
|
void CallerHandle::setProcessSetupData(const ProcessSetupData::Ptr &setup)
|
2021-08-18 13:40:43 +02:00
|
|
|
{
|
|
|
|
|
QTC_ASSERT(isCalledFromCallersThread(), return);
|
2022-02-16 03:21:51 +01:00
|
|
|
m_setup = setup;
|
2021-08-18 13:40:43 +02:00
|
|
|
}
|
|
|
|
|
|
2022-03-21 11:55:52 +01:00
|
|
|
bool CallerHandle::waitForSignal(int msecs, SignalType newSignal)
|
2021-07-14 16:02:25 +02:00
|
|
|
{
|
2021-08-23 17:58:44 +02:00
|
|
|
QTC_ASSERT(isCalledFromCallersThread(), return false);
|
|
|
|
|
if (!canWaitFor(newSignal))
|
|
|
|
|
return false;
|
|
|
|
|
if (!m_launcherHandle)
|
|
|
|
|
return false;
|
|
|
|
|
return m_launcherHandle->waitForSignal(msecs, newSignal);
|
2021-07-14 16:02:25 +02:00
|
|
|
}
|
|
|
|
|
|
2021-08-23 17:58:44 +02:00
|
|
|
bool CallerHandle::canWaitFor(SignalType newSignal) const
|
2021-08-18 09:31:18 +02:00
|
|
|
{
|
2021-08-23 17:58:44 +02:00
|
|
|
QTC_ASSERT(isCalledFromCallersThread(), return false);
|
|
|
|
|
switch (newSignal) {
|
|
|
|
|
case SignalType::Started:
|
|
|
|
|
return m_processState == QProcess::Starting;
|
|
|
|
|
case SignalType::ReadyRead:
|
|
|
|
|
case SignalType::Finished:
|
|
|
|
|
return m_processState != QProcess::NotRunning;
|
|
|
|
|
default:
|
|
|
|
|
break;
|
2021-08-18 09:31:18 +02:00
|
|
|
}
|
2021-08-23 17:58:44 +02:00
|
|
|
return false;
|
2021-08-18 09:31:18 +02:00
|
|
|
}
|
|
|
|
|
|
2021-08-23 17:58:44 +02:00
|
|
|
// Called from caller's or launcher's thread.
|
|
|
|
|
bool CallerHandle::isCalledFromCallersThread() const
|
2021-07-14 16:02:25 +02:00
|
|
|
{
|
2021-08-23 17:58:44 +02:00
|
|
|
return QThread::currentThread() == thread();
|
2021-07-14 16:02:25 +02:00
|
|
|
}
|
|
|
|
|
|
2021-08-23 17:58:44 +02:00
|
|
|
// Called from caller's or launcher's thread. Call me with mutex locked.
|
|
|
|
|
bool CallerHandle::isCalledFromLaunchersThread() const
|
2021-07-16 11:38:34 +02:00
|
|
|
{
|
2021-08-23 17:58:44 +02:00
|
|
|
if (!m_launcherHandle)
|
|
|
|
|
return false;
|
|
|
|
|
return QThread::currentThread() == m_launcherHandle->thread();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Called from caller's thread exclusively.
|
|
|
|
|
bool LauncherHandle::waitForSignal(int msecs, CallerHandle::SignalType newSignal)
|
|
|
|
|
{
|
|
|
|
|
QTC_ASSERT(!isCalledFromLaunchersThread(), return false);
|
2021-11-05 14:55:10 +01:00
|
|
|
QDeadlineTimer deadline(msecs);
|
2021-08-23 17:58:44 +02:00
|
|
|
while (true) {
|
2021-11-05 14:55:10 +01:00
|
|
|
if (deadline.hasExpired())
|
2021-08-23 17:58:44 +02:00
|
|
|
break;
|
2021-11-05 14:55:10 +01:00
|
|
|
if (!doWaitForSignal(deadline, newSignal))
|
2021-08-23 17:58:44 +02:00
|
|
|
break;
|
|
|
|
|
const QList<CallerHandle::SignalType> flushedSignals = m_callerHandle->flushFor(newSignal);
|
|
|
|
|
const bool errorOccurred = flushedSignals.contains(CallerHandle::SignalType::Error);
|
|
|
|
|
if (errorOccurred)
|
2022-03-07 18:20:02 +01:00
|
|
|
return true; // apparently QProcess behaves like this in case of error
|
2021-08-23 17:58:44 +02:00
|
|
|
const bool newSignalFlushed = flushedSignals.contains(newSignal);
|
|
|
|
|
if (newSignalFlushed) // so we don't continue waiting
|
|
|
|
|
return true;
|
2022-01-13 11:44:08 +01:00
|
|
|
const bool finishedSignalFlushed = flushedSignals.contains(CallerHandle::SignalType::Finished);
|
|
|
|
|
if (finishedSignalFlushed)
|
2022-03-07 18:20:02 +01:00
|
|
|
return true; // finish has appeared but we were waiting for other signal
|
2021-07-16 11:38:34 +02:00
|
|
|
}
|
2021-08-23 17:58:44 +02:00
|
|
|
return false;
|
2021-07-16 11:38:34 +02:00
|
|
|
}
|
|
|
|
|
|
2021-08-23 17:58:44 +02:00
|
|
|
// Called from caller's thread exclusively.
|
2021-11-05 14:55:10 +01:00
|
|
|
bool LauncherHandle::doWaitForSignal(QDeadlineTimer deadline, CallerHandle::SignalType newSignal)
|
2021-07-14 16:02:25 +02:00
|
|
|
{
|
2021-08-23 17:58:44 +02:00
|
|
|
QMutexLocker locker(&m_mutex);
|
|
|
|
|
QTC_ASSERT(isCalledFromCallersThread(), return false);
|
|
|
|
|
QTC_ASSERT(m_waitingFor == CallerHandle::SignalType::NoSignal, return false);
|
2022-03-21 11:55:52 +01:00
|
|
|
|
|
|
|
|
// 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_callerHandle->shouldFlush())
|
2021-08-23 17:58:44 +02:00
|
|
|
return true;
|
|
|
|
|
|
|
|
|
|
m_waitingFor = newSignal;
|
2021-11-05 14:55:10 +01:00
|
|
|
const bool ret = m_waitCondition.wait(&m_mutex, deadline);
|
2021-08-23 17:58:44 +02:00
|
|
|
m_waitingFor = CallerHandle::SignalType::NoSignal;
|
|
|
|
|
return ret;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Called from launcher's thread exclusively. Call me with mutex locked.
|
|
|
|
|
void LauncherHandle::wakeUpIfWaitingFor(CallerHandle::SignalType newSignal)
|
|
|
|
|
{
|
|
|
|
|
QTC_ASSERT(isCalledFromLaunchersThread(), return);
|
|
|
|
|
// TODO: should we always wake up in case m_waitingFor != NoSignal?
|
|
|
|
|
// The matching signal came
|
|
|
|
|
const bool signalMatched = (m_waitingFor == newSignal);
|
|
|
|
|
// E.g. if we are waiting for ReadyRead and we got Finished or Error signal instead -> wake it, too.
|
|
|
|
|
const bool finishedOrErrorWhileWaiting =
|
|
|
|
|
(m_waitingFor != CallerHandle::SignalType::NoSignal)
|
|
|
|
|
&& ((newSignal == CallerHandle::SignalType::Finished) || (newSignal == CallerHandle::SignalType::Error));
|
|
|
|
|
// Wake up, flush and continue waiting.
|
|
|
|
|
// E.g. when being in waitingForFinished() state and Started or ReadyRead signal came.
|
|
|
|
|
const bool continueWaitingAfterFlushing =
|
|
|
|
|
((m_waitingFor == CallerHandle::SignalType::Finished) && (newSignal != CallerHandle::SignalType::Finished))
|
|
|
|
|
|| ((m_waitingFor == CallerHandle::SignalType::ReadyRead) && (newSignal == CallerHandle::SignalType::Started));
|
|
|
|
|
const bool shouldWake = signalMatched
|
|
|
|
|
|| finishedOrErrorWhileWaiting
|
|
|
|
|
|| continueWaitingAfterFlushing;
|
|
|
|
|
|
|
|
|
|
if (shouldWake)
|
|
|
|
|
m_waitCondition.wakeOne();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Called from launcher's thread exclusively. Call me with mutex locked.
|
|
|
|
|
void LauncherHandle::flushCaller()
|
|
|
|
|
{
|
|
|
|
|
QTC_ASSERT(isCalledFromLaunchersThread(), return);
|
|
|
|
|
if (!m_callerHandle)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
// call in callers thread
|
|
|
|
|
QMetaObject::invokeMethod(m_callerHandle, &CallerHandle::flush);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void LauncherHandle::handlePacket(LauncherPacketType type, const QByteArray &payload)
|
|
|
|
|
{
|
|
|
|
|
QTC_ASSERT(isCalledFromLaunchersThread(), return);
|
|
|
|
|
switch (type) {
|
|
|
|
|
case LauncherPacketType::ProcessError:
|
|
|
|
|
handleErrorPacket(payload);
|
|
|
|
|
break;
|
|
|
|
|
case LauncherPacketType::ProcessStarted:
|
|
|
|
|
handleStartedPacket(payload);
|
|
|
|
|
break;
|
|
|
|
|
case LauncherPacketType::ReadyReadStandardOutput:
|
|
|
|
|
handleReadyReadStandardOutput(payload);
|
|
|
|
|
break;
|
|
|
|
|
case LauncherPacketType::ReadyReadStandardError:
|
|
|
|
|
handleReadyReadStandardError(payload);
|
|
|
|
|
break;
|
|
|
|
|
case LauncherPacketType::ProcessFinished:
|
|
|
|
|
handleFinishedPacket(payload);
|
|
|
|
|
break;
|
|
|
|
|
default:
|
|
|
|
|
QTC_ASSERT(false, break);
|
2021-07-14 16:02:25 +02:00
|
|
|
}
|
2021-08-23 17:58:44 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void LauncherHandle::handleErrorPacket(const QByteArray &packetData)
|
|
|
|
|
{
|
|
|
|
|
QTC_ASSERT(isCalledFromLaunchersThread(), return);
|
|
|
|
|
QMutexLocker locker(&m_mutex);
|
|
|
|
|
wakeUpIfWaitingFor(CallerHandle::SignalType::Error);
|
|
|
|
|
if (!m_callerHandle)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
const auto packet = LauncherPacket::extractPacket<ProcessErrorPacket>(m_token, packetData);
|
|
|
|
|
m_callerHandle->appendSignal(new ErrorSignal(packet.error, packet.errorString));
|
|
|
|
|
flushCaller();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void LauncherHandle::handleStartedPacket(const QByteArray &packetData)
|
|
|
|
|
{
|
|
|
|
|
QTC_ASSERT(isCalledFromLaunchersThread(), return);
|
|
|
|
|
QMutexLocker locker(&m_mutex);
|
|
|
|
|
wakeUpIfWaitingFor(CallerHandle::SignalType::Started);
|
|
|
|
|
if (!m_callerHandle)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
const auto packet = LauncherPacket::extractPacket<ProcessStartedPacket>(m_token, packetData);
|
|
|
|
|
m_callerHandle->appendSignal(new StartedSignal(packet.processId));
|
|
|
|
|
flushCaller();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void LauncherHandle::handleReadyReadStandardOutput(const QByteArray &packetData)
|
|
|
|
|
{
|
|
|
|
|
QTC_ASSERT(isCalledFromLaunchersThread(), return);
|
|
|
|
|
QMutexLocker locker(&m_mutex);
|
|
|
|
|
wakeUpIfWaitingFor(CallerHandle::SignalType::ReadyRead);
|
|
|
|
|
if (!m_callerHandle)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
const auto packet = LauncherPacket::extractPacket<ReadyReadStandardOutputPacket>(m_token, packetData);
|
|
|
|
|
if (packet.standardChannel.isEmpty())
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
m_callerHandle->appendSignal(new ReadyReadSignal(packet.standardChannel, {}));
|
|
|
|
|
flushCaller();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void LauncherHandle::handleReadyReadStandardError(const QByteArray &packetData)
|
|
|
|
|
{
|
|
|
|
|
QTC_ASSERT(isCalledFromLaunchersThread(), return);
|
|
|
|
|
QMutexLocker locker(&m_mutex);
|
|
|
|
|
wakeUpIfWaitingFor(CallerHandle::SignalType::ReadyRead);
|
|
|
|
|
if (!m_callerHandle)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
const auto packet = LauncherPacket::extractPacket<ReadyReadStandardErrorPacket>(m_token, packetData);
|
|
|
|
|
if (packet.standardChannel.isEmpty())
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
m_callerHandle->appendSignal(new ReadyReadSignal({}, packet.standardChannel));
|
|
|
|
|
flushCaller();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void LauncherHandle::handleFinishedPacket(const QByteArray &packetData)
|
|
|
|
|
{
|
|
|
|
|
QTC_ASSERT(isCalledFromLaunchersThread(), return);
|
|
|
|
|
QMutexLocker locker(&m_mutex);
|
|
|
|
|
wakeUpIfWaitingFor(CallerHandle::SignalType::Finished);
|
|
|
|
|
if (!m_callerHandle)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
const auto packet = LauncherPacket::extractPacket<ProcessFinishedPacket>(m_token, packetData);
|
|
|
|
|
const QByteArray stdOut = packet.stdOut;
|
|
|
|
|
const QByteArray stdErr = packet.stdErr;
|
|
|
|
|
const QProcess::ProcessError error = packet.error;
|
|
|
|
|
const QString errorString = packet.errorString;
|
|
|
|
|
|
|
|
|
|
// We assume that if error is UnknownError, everything went fine.
|
|
|
|
|
// By default QProcess returns "Unknown error" for errorString()
|
|
|
|
|
if (error != QProcess::UnknownError)
|
|
|
|
|
m_callerHandle->appendSignal(new ErrorSignal(error, errorString));
|
|
|
|
|
if (!stdOut.isEmpty() || !stdErr.isEmpty())
|
|
|
|
|
m_callerHandle->appendSignal(new ReadyReadSignal(stdOut, stdErr));
|
|
|
|
|
m_callerHandle->appendSignal(new FinishedSignal(packet.exitStatus, packet.exitCode));
|
|
|
|
|
flushCaller();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void LauncherHandle::handleSocketReady()
|
|
|
|
|
{
|
|
|
|
|
QTC_ASSERT(isCalledFromLaunchersThread(), return);
|
|
|
|
|
m_socketError = false;
|
|
|
|
|
QMutexLocker locker(&m_mutex);
|
|
|
|
|
if (m_callerHandle)
|
|
|
|
|
m_callerHandle->startIfNeeded();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void LauncherHandle::handleSocketError(const QString &message)
|
|
|
|
|
{
|
|
|
|
|
QTC_ASSERT(isCalledFromLaunchersThread(), return);
|
|
|
|
|
m_socketError = true; // TODO: ???
|
|
|
|
|
QMutexLocker locker(&m_mutex);
|
|
|
|
|
wakeUpIfWaitingFor(CallerHandle::SignalType::Error);
|
|
|
|
|
if (!m_callerHandle)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
const QString errorString = QCoreApplication::translate("Utils::QtcProcess",
|
|
|
|
|
"Internal socket error: %1").arg(message);
|
|
|
|
|
m_callerHandle->appendSignal(new ErrorSignal(QProcess::FailedToStart, errorString));
|
|
|
|
|
flushCaller();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool LauncherHandle::isCalledFromLaunchersThread() const
|
|
|
|
|
{
|
|
|
|
|
return QThread::currentThread() == thread();
|
2021-07-14 16:02:25 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// call me with mutex locked
|
2021-08-23 17:58:44 +02:00
|
|
|
bool LauncherHandle::isCalledFromCallersThread() const
|
|
|
|
|
{
|
|
|
|
|
if (!m_callerHandle)
|
|
|
|
|
return false;
|
|
|
|
|
return QThread::currentThread() == m_callerHandle->thread();
|
2021-07-14 16:02:25 +02:00
|
|
|
}
|
|
|
|
|
|
2021-07-07 11:36:03 +02:00
|
|
|
LauncherSocket::LauncherSocket(QObject *parent) : QObject(parent)
|
|
|
|
|
{
|
|
|
|
|
qRegisterMetaType<Utils::Internal::LauncherPacketType>();
|
|
|
|
|
qRegisterMetaType<quintptr>("quintptr");
|
|
|
|
|
}
|
|
|
|
|
|
2021-08-26 12:02:39 +02:00
|
|
|
LauncherSocket::~LauncherSocket()
|
|
|
|
|
{
|
|
|
|
|
QMutexLocker locker(&m_mutex);
|
|
|
|
|
auto displayHandles = [&handles = m_handles] {
|
|
|
|
|
qWarning() << "Destroying process launcher while" << handles.count()
|
|
|
|
|
<< "processes are still alive. The following processes are still alive:";
|
|
|
|
|
for (LauncherHandle *handle : handles) {
|
|
|
|
|
CallerHandle *callerHandle = handle->callerHandle();
|
|
|
|
|
if (callerHandle->state() != QProcess::NotRunning) {
|
|
|
|
|
qWarning() << " " << callerHandle->program() << callerHandle->arguments()
|
|
|
|
|
<< "in thread" << (void *)callerHandle->thread();
|
|
|
|
|
} else {
|
|
|
|
|
qWarning() << " Not running process in thread" << (void *)callerHandle->thread();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
QTC_ASSERT(m_handles.isEmpty(), displayHandles());
|
|
|
|
|
}
|
|
|
|
|
|
2021-07-07 11:36:03 +02:00
|
|
|
void LauncherSocket::sendData(const QByteArray &data)
|
|
|
|
|
{
|
|
|
|
|
if (!isReady())
|
|
|
|
|
return;
|
2021-08-13 13:15:09 +02:00
|
|
|
|
|
|
|
|
auto storeRequest = [this](const QByteArray &data)
|
|
|
|
|
{
|
|
|
|
|
QMutexLocker locker(&m_mutex);
|
|
|
|
|
m_requests.push_back(data);
|
|
|
|
|
return m_requests.size() == 1; // Returns true if requests handling should be triggered.
|
|
|
|
|
};
|
|
|
|
|
|
2021-08-18 10:45:57 +02:00
|
|
|
if (storeRequest(data)) // Call handleRequests() in launcher's thread.
|
2021-07-14 16:02:25 +02:00
|
|
|
QMetaObject::invokeMethod(this, &LauncherSocket::handleRequests);
|
|
|
|
|
}
|
|
|
|
|
|
2022-02-17 18:31:57 +01:00
|
|
|
CallerHandle *LauncherSocket::registerHandle(QObject *parent, quintptr token)
|
2021-07-14 16:02:25 +02:00
|
|
|
{
|
2021-08-18 13:40:43 +02:00
|
|
|
QTC_ASSERT(!isCalledFromLaunchersThread(), return nullptr);
|
2021-07-14 16:02:25 +02:00
|
|
|
QMutexLocker locker(&m_mutex);
|
|
|
|
|
if (m_handles.contains(token))
|
|
|
|
|
return nullptr; // TODO: issue a warning
|
|
|
|
|
|
2022-02-16 03:21:51 +01:00
|
|
|
CallerHandle *callerHandle = new CallerHandle(parent, token);
|
|
|
|
|
LauncherHandle *launcherHandle = new LauncherHandle(token);
|
2021-08-23 17:58:44 +02:00
|
|
|
callerHandle->setLauncherHandle(launcherHandle);
|
|
|
|
|
launcherHandle->setCallerHandle(callerHandle);
|
|
|
|
|
launcherHandle->moveToThread(thread());
|
2021-07-14 16:02:25 +02:00
|
|
|
// 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.
|
2021-08-23 17:58:44 +02:00
|
|
|
m_handles.insert(token, launcherHandle);
|
2021-07-14 16:02:25 +02:00
|
|
|
connect(this, &LauncherSocket::ready,
|
2021-08-23 17:58:44 +02:00
|
|
|
launcherHandle, &LauncherHandle::handleSocketReady);
|
2021-07-14 16:02:25 +02:00
|
|
|
connect(this, &LauncherSocket::errorOccurred,
|
2021-08-23 17:58:44 +02:00
|
|
|
launcherHandle, &LauncherHandle::handleSocketError);
|
2021-07-14 16:02:25 +02:00
|
|
|
|
2021-08-23 17:58:44 +02:00
|
|
|
return callerHandle;
|
2021-07-14 16:02:25 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void LauncherSocket::unregisterHandle(quintptr token)
|
|
|
|
|
{
|
2021-08-18 13:40:43 +02:00
|
|
|
QTC_ASSERT(!isCalledFromLaunchersThread(), return);
|
2021-07-14 16:02:25 +02:00
|
|
|
QMutexLocker locker(&m_mutex);
|
|
|
|
|
auto it = m_handles.find(token);
|
|
|
|
|
if (it == m_handles.end())
|
|
|
|
|
return; // TODO: issue a warning
|
|
|
|
|
|
2021-08-23 17:58:44 +02:00
|
|
|
LauncherHandle *launcherHandle = it.value();
|
|
|
|
|
CallerHandle *callerHandle = launcherHandle->callerHandle();
|
|
|
|
|
launcherHandle->setCallerHandle(nullptr);
|
|
|
|
|
callerHandle->setLauncherHandle(nullptr);
|
|
|
|
|
launcherHandle->deleteLater();
|
|
|
|
|
callerHandle->deleteLater();
|
2021-07-14 16:02:25 +02:00
|
|
|
m_handles.erase(it);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
LauncherHandle *LauncherSocket::handleForToken(quintptr token) const
|
|
|
|
|
{
|
2021-08-18 13:40:43 +02:00
|
|
|
QTC_ASSERT(isCalledFromLaunchersThread(), return nullptr);
|
2021-07-14 16:02:25 +02:00
|
|
|
QMutexLocker locker(&m_mutex);
|
|
|
|
|
return m_handles.value(token);
|
2021-07-07 11:36:03 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void LauncherSocket::setSocket(QLocalSocket *socket)
|
|
|
|
|
{
|
2021-08-18 13:40:43 +02:00
|
|
|
QTC_ASSERT(isCalledFromLaunchersThread(), return);
|
2021-07-07 11:36:03 +02:00
|
|
|
QTC_ASSERT(!m_socket, return);
|
|
|
|
|
m_socket.store(socket);
|
|
|
|
|
m_packetParser.setDevice(m_socket);
|
|
|
|
|
connect(m_socket,
|
|
|
|
|
&QLocalSocket::errorOccurred,
|
|
|
|
|
this, &LauncherSocket::handleSocketError);
|
|
|
|
|
connect(m_socket, &QLocalSocket::readyRead,
|
|
|
|
|
this, &LauncherSocket::handleSocketDataAvailable);
|
|
|
|
|
connect(m_socket, &QLocalSocket::disconnected,
|
|
|
|
|
this, &LauncherSocket::handleSocketDisconnected);
|
|
|
|
|
emit ready();
|
|
|
|
|
}
|
|
|
|
|
|
2021-08-18 13:40:43 +02:00
|
|
|
void LauncherSocket::shutdown()
|
|
|
|
|
{
|
|
|
|
|
QTC_ASSERT(isCalledFromLaunchersThread(), return);
|
|
|
|
|
const auto socket = m_socket.exchange(nullptr);
|
|
|
|
|
if (!socket)
|
|
|
|
|
return;
|
|
|
|
|
socket->disconnect();
|
|
|
|
|
socket->write(ShutdownPacket().serialize());
|
|
|
|
|
socket->waitForBytesWritten(1000);
|
|
|
|
|
socket->deleteLater(); // or schedule a queued call to delete later?
|
|
|
|
|
}
|
|
|
|
|
|
2021-07-07 11:36:03 +02:00
|
|
|
void LauncherSocket::handleSocketError()
|
|
|
|
|
{
|
2021-08-18 13:40:43 +02:00
|
|
|
QTC_ASSERT(isCalledFromLaunchersThread(), return);
|
2021-07-07 11:36:03 +02:00
|
|
|
auto socket = m_socket.load();
|
|
|
|
|
if (socket->error() != QLocalSocket::PeerClosedError)
|
|
|
|
|
handleError(QCoreApplication::translate("Utils::LauncherSocket",
|
|
|
|
|
"Socket error: %1").arg(socket->errorString()));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void LauncherSocket::handleSocketDataAvailable()
|
|
|
|
|
{
|
2021-08-18 13:40:43 +02:00
|
|
|
QTC_ASSERT(isCalledFromLaunchersThread(), return);
|
2021-07-07 11:36:03 +02:00
|
|
|
try {
|
|
|
|
|
if (!m_packetParser.parse())
|
|
|
|
|
return;
|
|
|
|
|
} catch (const PacketParser::InvalidPacketSizeException &e) {
|
|
|
|
|
handleError(QCoreApplication::translate("Utils::LauncherSocket",
|
|
|
|
|
"Internal protocol error: invalid packet size %1.").arg(e.size));
|
|
|
|
|
return;
|
|
|
|
|
}
|
2021-07-14 16:02:25 +02:00
|
|
|
LauncherHandle *handle = handleForToken(m_packetParser.token());
|
|
|
|
|
if (handle) {
|
|
|
|
|
switch (m_packetParser.type()) {
|
|
|
|
|
case LauncherPacketType::ProcessError:
|
|
|
|
|
case LauncherPacketType::ProcessStarted:
|
2021-08-02 13:26:33 +02:00
|
|
|
case LauncherPacketType::ReadyReadStandardOutput:
|
|
|
|
|
case LauncherPacketType::ReadyReadStandardError:
|
2021-07-14 16:02:25 +02:00
|
|
|
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
|
2021-07-07 11:36:03 +02:00
|
|
|
}
|
|
|
|
|
handleSocketDataAvailable();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void LauncherSocket::handleSocketDisconnected()
|
|
|
|
|
{
|
2021-08-18 13:40:43 +02:00
|
|
|
QTC_ASSERT(isCalledFromLaunchersThread(), return);
|
2021-07-07 11:36:03 +02:00
|
|
|
handleError(QCoreApplication::translate("Utils::LauncherSocket",
|
2022-02-16 17:51:35 +01:00
|
|
|
"Launcher socket closed unexpectedly."));
|
2021-07-07 11:36:03 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void LauncherSocket::handleError(const QString &error)
|
|
|
|
|
{
|
2021-08-18 13:40:43 +02:00
|
|
|
QTC_ASSERT(isCalledFromLaunchersThread(), return);
|
2021-07-07 11:36:03 +02:00
|
|
|
const auto socket = m_socket.exchange(nullptr);
|
|
|
|
|
socket->disconnect();
|
|
|
|
|
socket->deleteLater();
|
|
|
|
|
emit errorOccurred(error);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void LauncherSocket::handleRequests()
|
|
|
|
|
{
|
2021-08-18 13:40:43 +02:00
|
|
|
QTC_ASSERT(isCalledFromLaunchersThread(), return);
|
2021-07-07 11:36:03 +02:00
|
|
|
const auto socket = m_socket.load();
|
|
|
|
|
QTC_ASSERT(socket, return);
|
2021-07-14 16:02:25 +02:00
|
|
|
QMutexLocker locker(&m_mutex);
|
2021-07-07 11:36:03 +02:00
|
|
|
for (const QByteArray &request : qAsConst(m_requests))
|
|
|
|
|
socket->write(request);
|
|
|
|
|
m_requests.clear();
|
|
|
|
|
}
|
|
|
|
|
|
2021-08-18 13:40:43 +02:00
|
|
|
bool LauncherSocket::isCalledFromLaunchersThread() const
|
|
|
|
|
{
|
|
|
|
|
return QThread::currentThread() == thread();
|
|
|
|
|
}
|
|
|
|
|
|
2021-07-07 11:36:03 +02:00
|
|
|
} // namespace Internal
|
|
|
|
|
} // namespace Utils
|