ValgrindRunner: Employ task tree internally

Change-Id: I32f674d7ea93e5825eba0912c98cc0280802c483
Reviewed-by: hjk <hjk@qt.io>
This commit is contained in:
Jarek Kobus
2023-08-16 16:41:44 +02:00
parent 9335cca249
commit b1304de6cf
4 changed files with 137 additions and 95 deletions

View File

@@ -514,8 +514,7 @@ void ValgrindMemcheckParserTest::testRealValgrind()
runner.setValgrindCommand({"valgrind", {}});
runner.setDebuggee(debuggee);
RunnerDumper dumper(&runner);
runner.start();
runner.waitForFinished();
runner.runBlocking();
}
void ValgrindMemcheckParserTest::testValgrindStartError_data()
@@ -551,8 +550,7 @@ void ValgrindMemcheckParserTest::testValgrindStartError()
runner.setValgrindCommand({FilePath::fromString(valgrindExe), valgrindArgs});
runner.setDebuggee(debuggeeExecutable);
RunnerDumper dumper(&runner);
runner.start();
runner.waitForFinished();
runner.runBlocking();
QVERIFY(dumper.m_errorReceived);
// just finish without deadlock and we are fine
}

View File

@@ -8,15 +8,18 @@
#include <projectexplorer/runcontrol.h>
#include <utils/hostosinfo.h>
#include <solutions/tasking/barrier.h>
#include <utils/process.h>
#include <utils/qtcassert.h>
#include <QEventLoop>
#include <QTcpServer>
#include <QTcpSocket>
#include <QTimer>
using namespace ProjectExplorer;
using namespace Tasking;
using namespace Utils;
using namespace Valgrind::XmlProtocol;
@@ -56,33 +59,12 @@ static CommandLine valgrindCommand(const CommandLine &command,
return cmd;
}
class ValgrindRunner::Private : public QObject
class ValgrindRunnerPrivate : public QObject
{
public:
Private(ValgrindRunner *owner) : q(owner) {
connect(&m_xmlServer, &QTcpServer::newConnection, this, [this] {
QTcpSocket *socket = m_xmlServer.nextPendingConnection();
QTC_ASSERT(socket, return);
m_xmlServer.close();
m_parser.setSocket(socket);
m_parser.start();
});
connect(&m_logServer, &QTcpServer::newConnection, this, [this] {
QTcpSocket *socket = m_logServer.nextPendingConnection();
QTC_ASSERT(socket, return);
connect(socket, &QIODevice::readyRead, this, [this, socket] {
emit q->logMessageReceived(socket->readAll());
});
m_logServer.close();
});
connect(&m_parser, &Parser::status, q, &ValgrindRunner::status);
connect(&m_parser, &Parser::error, q, &ValgrindRunner::error);
connect(&m_parser, &Parser::done, this, [this](bool success, const QString &err) {
if (!success)
emit q->internalError(err);
});
}
ValgrindRunnerPrivate(ValgrindRunner *owner)
: q(owner)
{}
void setupValgrindProcess(Process *process, const CommandLine &command) const {
CommandLine cmd = command;
@@ -123,7 +105,8 @@ public:
});
}
bool startServers();
Group runRecipe() const;
bool run();
ValgrindRunner *q = nullptr;
@@ -134,65 +117,120 @@ public:
QHostAddress m_localServerAddress;
bool m_useTerminal = false;
Process m_process;
QTcpServer m_xmlServer;
QTcpServer m_logServer;
Parser m_parser;
std::unique_ptr<TaskTree> m_taskTree;
};
bool ValgrindRunner::Private::startServers()
Group ValgrindRunnerPrivate::runRecipe() const
{
const bool xmlOK = m_xmlServer.listen(m_localServerAddress);
const QString ip = m_localServerAddress.toString();
if (!xmlOK) {
emit q->processErrorReceived(Tr::tr("XmlServer on %1:").arg(ip) + ' '
+ m_xmlServer.errorString(), QProcess::FailedToStart );
return false;
}
m_xmlServer.setMaxPendingConnections(1);
const bool logOK = m_logServer.listen(m_localServerAddress);
if (!logOK) {
emit q->processErrorReceived(Tr::tr("LogServer on %1:").arg(ip) + ' '
+ m_logServer.errorString(), QProcess::FailedToStart );
return false;
}
m_logServer.setMaxPendingConnections(1);
return true;
struct ValgrindStorage {
CommandLine m_valgrindCommand;
std::unique_ptr<QTcpServer> m_xmlServer;
std::unique_ptr<QTcpServer> m_logServer;
std::unique_ptr<QTcpSocket> m_xmlSocket;
};
TreeStorage<ValgrindStorage> storage;
SingleBarrier xmlBarrier;
const auto onSetup = [this, storage, xmlBarrier] {
ValgrindStorage *storagePtr = storage.activeStorage();
storagePtr->m_valgrindCommand.setExecutable(m_valgrindCommand.executable());
if (!m_localServerAddress.isNull()) {
Barrier *barrier = xmlBarrier->barrier();
const QString ip = m_localServerAddress.toString();
QTcpServer *xmlServer = new QTcpServer;
storagePtr->m_xmlServer.reset(xmlServer);
connect(xmlServer, &QTcpServer::newConnection, this, [xmlServer, storagePtr, barrier] {
QTcpSocket *socket = xmlServer->nextPendingConnection();
QTC_ASSERT(socket, return);
xmlServer->close();
storagePtr->m_xmlSocket.reset(socket);
barrier->advance(); // Release Parser task
});
if (!xmlServer->listen(m_localServerAddress)) {
emit q->processErrorReceived(Tr::tr("XmlServer on %1:").arg(ip) + ' '
+ xmlServer->errorString(), QProcess::FailedToStart);
return SetupResult::StopWithError;
}
xmlServer->setMaxPendingConnections(1);
QTcpServer *logServer = new QTcpServer;
storagePtr->m_logServer.reset(logServer);
connect(logServer, &QTcpServer::newConnection, this, [this, logServer] {
QTcpSocket *socket = logServer->nextPendingConnection();
QTC_ASSERT(socket, return);
connect(socket, &QIODevice::readyRead, this, [this, socket] {
emit q->logMessageReceived(socket->readAll());
});
logServer->close();
});
if (!logServer->listen(m_localServerAddress)) {
emit q->processErrorReceived(Tr::tr("LogServer on %1:").arg(ip) + ' '
+ logServer->errorString(), QProcess::FailedToStart);
return SetupResult::StopWithError;
}
logServer->setMaxPendingConnections(1);
storagePtr->m_valgrindCommand = valgrindCommand(storagePtr->m_valgrindCommand,
*xmlServer, *logServer);
}
return SetupResult::Continue;
};
const auto onProcessSetup = [this, storage](Process &process) {
setupValgrindProcess(&process, storage->m_valgrindCommand);
};
const auto onParserGroupSetup = [this] {
return m_localServerAddress.isNull() ? SetupResult::StopWithDone : SetupResult::Continue;
};
const auto onParserSetup = [this, storage](Parser &parser) {
connect(&parser, &Parser::status, q, &ValgrindRunner::status);
connect(&parser, &Parser::error, q, &ValgrindRunner::error);
parser.setSocket(storage->m_xmlSocket.release());
};
const auto onParserError = [this](const Parser &parser) {
emit q->internalError(parser.errorString());
};
const Group root {
parallel,
Storage(storage),
Storage(xmlBarrier),
onGroupSetup(onSetup),
ProcessTask(onProcessSetup),
Group {
onGroupSetup(onParserGroupSetup),
waitForBarrierTask(xmlBarrier),
ParserTask(onParserSetup, {}, onParserError)
}
};
return root;
}
bool ValgrindRunner::Private::run()
bool ValgrindRunnerPrivate::run()
{
CommandLine cmd;
cmd.setExecutable(m_valgrindCommand.executable());
if (!m_localServerAddress.isNull()) {
if (!startServers())
return false;
cmd = valgrindCommand(cmd, m_xmlServer, m_logServer);
}
setupValgrindProcess(&m_process, cmd);
m_process.start();
return true;
m_taskTree.reset(new TaskTree);
m_taskTree->setRecipe(runRecipe());
const auto finalize = [this](bool success) {
m_taskTree.release()->deleteLater();
emit q->done(success);
};
connect(m_taskTree.get(), &TaskTree::done, this, [finalize] { finalize(true); });
connect(m_taskTree.get(), &TaskTree::errorOccurred, this, [finalize] { finalize(false); });
m_taskTree->start();
return bool(m_taskTree);
}
ValgrindRunner::ValgrindRunner(QObject *parent)
: QObject(parent)
, d(new Private(this))
, d(new ValgrindRunnerPrivate(this))
{}
ValgrindRunner::~ValgrindRunner()
{
if (d->m_process.isRunning()) {
// make sure we don't delete the thread while it's still running
waitForFinished();
}
if (d->m_parser.isRunning()) {
// make sure we don't delete the thread while it's still running
waitForFinished();
}
delete d;
d = nullptr;
}
ValgrindRunner::~ValgrindRunner() = default;
void ValgrindRunner::setValgrindCommand(const CommandLine &command)
{
@@ -219,16 +257,6 @@ void ValgrindRunner::setUseTerminal(bool on)
d->m_useTerminal = on;
}
void ValgrindRunner::waitForFinished() const
{
if (d->m_process.state() == QProcess::NotRunning)
return;
QEventLoop loop;
connect(this, &ValgrindRunner::done, &loop, &QEventLoop::quit);
loop.exec();
}
bool ValgrindRunner::start()
{
return d->run();
@@ -236,7 +264,24 @@ bool ValgrindRunner::start()
void ValgrindRunner::stop()
{
d->m_process.stop();
d->m_taskTree.reset();
}
bool ValgrindRunner::runBlocking()
{
bool ok = false;
QEventLoop loop;
const auto finalize = [&loop, &ok](bool success) {
ok = success;
// Refer to the QObject::deleteLater() docs.
QMetaObject::invokeMethod(&loop, [&loop] { loop.quit(); }, Qt::QueuedConnection);
};
connect(this, &ValgrindRunner::done, &loop, finalize);
QTimer::singleShot(0, this, &ValgrindRunner::start);
loop.exec(QEventLoop::ExcludeUserInputEvents);
return ok;
}
} // namespace Valgrind

View File

@@ -21,6 +21,8 @@ class Error;
class Status;
}
class ValgrindRunnerPrivate;
class ValgrindRunner : public QObject
{
Q_OBJECT
@@ -35,10 +37,9 @@ public:
void setLocalServerAddress(const QHostAddress &localServerAddress);
void setUseTerminal(bool on);
void waitForFinished() const;
bool start();
void stop();
bool runBlocking();
signals:
void appendMessage(const QString &, Utils::OutputFormat);
@@ -53,8 +54,7 @@ signals:
void internalError(const QString &errorString);
private:
class Private;
Private *d;
std::unique_ptr<ValgrindRunnerPrivate> d;
};
} // namespace Valgrind

View File

@@ -66,8 +66,7 @@ QString ValgrindTestRunnerTest::runTestBinary(const QString &binary, const QStri
m_runner->setLocalServerAddress(QHostAddress::LocalHost);
m_runner->setValgrindCommand(valgrind);
m_runner->setDebuggee(debuggee);
m_runner->start();
m_runner->waitForFinished();
m_runner->runBlocking();
return binPath;
}