forked from qt-creator/qt-creator
Clang: Start ConnectionClient asynchronously
The connection client can block main thread at start up. This patch is removing the wait functions and using signals instead. Change-Id: I847c98b095752f6a875c0365bc27361bc5bdd051 Reviewed-by: Tim Jenssen <tim.jenssen@theqtcompany.com> Reviewed-by: Nikolai Kosjar <nikolai.kosjar@qt.io>
This commit is contained in:
@@ -31,6 +31,7 @@
|
||||
#include "cmbunregistertranslationunitsforeditormessage.h"
|
||||
|
||||
#include <QCoreApplication>
|
||||
#include <QMetaMethod>
|
||||
#include <QProcess>
|
||||
#include <QTemporaryDir>
|
||||
#include <QThread>
|
||||
@@ -58,7 +59,6 @@ QString connectionName()
|
||||
|
||||
ConnectionClient::ConnectionClient(ClangCodeModelClientInterface *client)
|
||||
: serverProxy_(client, &localSocket),
|
||||
isAliveTimerResetted(false),
|
||||
stdErrPrefixer("clangbackend.stderr: "),
|
||||
stdOutPrefixer("clangbackend.stdout: ")
|
||||
{
|
||||
@@ -66,15 +66,11 @@ ConnectionClient::ConnectionClient(ClangCodeModelClientInterface *client)
|
||||
|
||||
const bool startAliveTimer = !qgetenv("QTC_CLANG_NO_ALIVE_TIMER").toInt();
|
||||
|
||||
if (startAliveTimer) {
|
||||
connect(&processAliveTimer, &QTimer::timeout,
|
||||
this, &ConnectionClient::restartProcessIfTimerIsNotResettedAndSocketIsEmpty);
|
||||
}
|
||||
if (startAliveTimer)
|
||||
connectAliveTimer();
|
||||
|
||||
connect(&localSocket,
|
||||
static_cast<void (QLocalSocket::*)(QLocalSocket::LocalSocketError)>(&QLocalSocket::error),
|
||||
this,
|
||||
&ConnectionClient::printLocalSocketError);
|
||||
connectLocalSocketError();
|
||||
connectLocalSocketConnected();
|
||||
}
|
||||
|
||||
ConnectionClient::~ConnectionClient()
|
||||
@@ -82,15 +78,9 @@ ConnectionClient::~ConnectionClient()
|
||||
finishProcess();
|
||||
}
|
||||
|
||||
bool ConnectionClient::connectToServer()
|
||||
void ConnectionClient::startProcessAndConnectToServerAsynchronously()
|
||||
{
|
||||
TIME_SCOPE_DURATION("ConnectionClient::connectToServer");
|
||||
|
||||
startProcess();
|
||||
resetProcessAliveTimer();
|
||||
const bool isConnected = connectToLocalSocket();
|
||||
|
||||
return isConnected;
|
||||
process_ = startProcess();
|
||||
}
|
||||
|
||||
bool ConnectionClient::disconnectFromServer()
|
||||
@@ -145,28 +135,28 @@ QProcessEnvironment ConnectionClient::processEnvironment() const
|
||||
return processEnvironment;
|
||||
}
|
||||
|
||||
void ConnectionClient::startProcess()
|
||||
std::unique_ptr<QProcess> ConnectionClient::startProcess()
|
||||
{
|
||||
TIME_SCOPE_DURATION("ConnectionClient::startProcess");
|
||||
processIsStarting = true;
|
||||
|
||||
if (!isProcessIsRunning()) {
|
||||
connectProcessFinished();
|
||||
connectStandardOutputAndError();
|
||||
process()->setProcessEnvironment(processEnvironment());
|
||||
process()->start(processPath(), {connectionName()});
|
||||
process()->waitForStarted();
|
||||
auto process = std::unique_ptr<QProcess>(new QProcess);
|
||||
connectProcessFinished(process.get());
|
||||
connectProcessStarted(process.get());
|
||||
connectStandardOutputAndError(process.get());
|
||||
process->setProcessEnvironment(processEnvironment());
|
||||
process->start(processPath(), {connectionName()});
|
||||
resetProcessAliveTimer();
|
||||
}
|
||||
|
||||
return process;
|
||||
}
|
||||
|
||||
void ConnectionClient::restartProcess()
|
||||
void ConnectionClient::restartProcessAsynchronously()
|
||||
{
|
||||
finishProcess();
|
||||
startProcess();
|
||||
if (!processIsStarting) {
|
||||
finishProcess(std::move(process_));
|
||||
|
||||
connectToServer();
|
||||
|
||||
emit processRestarted();
|
||||
startProcessAndConnectToServerAsynchronously();
|
||||
}
|
||||
}
|
||||
|
||||
void ConnectionClient::restartProcessIfTimerIsNotResettedAndSocketIsEmpty()
|
||||
@@ -179,52 +169,48 @@ void ConnectionClient::restartProcessIfTimerIsNotResettedAndSocketIsEmpty()
|
||||
if (localSocket.bytesAvailable() > 0)
|
||||
return; // We come first, the incoming data was not yet processed.
|
||||
|
||||
restartProcess();
|
||||
restartProcessAsynchronously();
|
||||
}
|
||||
|
||||
bool ConnectionClient::connectToLocalSocket()
|
||||
void ConnectionClient::connectToLocalSocket()
|
||||
{
|
||||
for (int counter = 0; counter < 1000; counter++) {
|
||||
if (!isConnected()) {
|
||||
localSocket.connectToServer(connectionName());
|
||||
bool isConnected = localSocket.waitForConnected(20);
|
||||
|
||||
if (isConnected)
|
||||
return isConnected;
|
||||
else
|
||||
QThread::msleep(30);
|
||||
QTimer::singleShot(20, this, &ConnectionClient::connectToLocalSocket);
|
||||
}
|
||||
|
||||
qDebug() << "Cannot connect:" <<localSocket.errorString();
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void ConnectionClient::endProcess()
|
||||
void ConnectionClient::endProcess(QProcess *process)
|
||||
{
|
||||
if (isProcessIsRunning()) {
|
||||
if (isProcessIsRunning() && isConnected()) {
|
||||
sendEndMessage();
|
||||
process()->waitForFinished();
|
||||
process->waitForFinished();
|
||||
}
|
||||
}
|
||||
|
||||
void ConnectionClient::terminateProcess()
|
||||
void ConnectionClient::terminateProcess(QProcess *process)
|
||||
{
|
||||
#ifndef Q_OS_WIN32
|
||||
if (isProcessIsRunning()) {
|
||||
process()->terminate();
|
||||
process()->waitForFinished();
|
||||
process->terminate();
|
||||
process->waitForFinished();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void ConnectionClient::killProcess()
|
||||
void ConnectionClient::killProcess(QProcess *process)
|
||||
{
|
||||
if (isProcessIsRunning()) {
|
||||
process()->kill();
|
||||
process()->waitForFinished();
|
||||
process->kill();
|
||||
process->waitForFinished();
|
||||
}
|
||||
}
|
||||
|
||||
void ConnectionClient::resetProcessIsStarting()
|
||||
{
|
||||
processIsStarting = false;
|
||||
}
|
||||
|
||||
void ConnectionClient::printLocalSocketError(QLocalSocket::LocalSocketError socketError)
|
||||
{
|
||||
if (socketError != QLocalSocket::ServerNotFoundError)
|
||||
@@ -241,21 +227,39 @@ void ConnectionClient::printStandardError()
|
||||
qDebug("%s", stdErrPrefixer.prefix(process_->readAllStandardError()).constData());
|
||||
}
|
||||
|
||||
void ConnectionClient::connectLocalSocketConnected()
|
||||
{
|
||||
connect(&localSocket,
|
||||
&QLocalSocket::connected,
|
||||
this,
|
||||
&ConnectionClient::connectedToLocalSocket);
|
||||
|
||||
connect(&localSocket,
|
||||
&QLocalSocket::connected,
|
||||
this,
|
||||
&ConnectionClient::resetProcessIsStarting);
|
||||
}
|
||||
|
||||
void ConnectionClient::finishProcess()
|
||||
{
|
||||
finishProcess(std::move(process_));
|
||||
}
|
||||
|
||||
void ConnectionClient::finishProcess(std::unique_ptr<QProcess> &&process)
|
||||
{
|
||||
TIME_SCOPE_DURATION("ConnectionClient::finishProcess");
|
||||
|
||||
if (process) {
|
||||
processAliveTimer.stop();
|
||||
|
||||
disconnectProcessFinished();
|
||||
endProcess();
|
||||
disconnectProcessFinished(process.get());
|
||||
endProcess(process.get());
|
||||
disconnectFromServer();
|
||||
terminateProcess();
|
||||
killProcess();
|
||||
|
||||
process_.reset();
|
||||
terminateProcess(process.get());
|
||||
killProcess(process.get());
|
||||
|
||||
serverProxy_.resetCounter();
|
||||
}
|
||||
}
|
||||
|
||||
bool ConnectionClient::waitForEcho()
|
||||
@@ -263,6 +267,25 @@ bool ConnectionClient::waitForEcho()
|
||||
return localSocket.waitForReadyRead();
|
||||
}
|
||||
|
||||
bool ConnectionClient::waitForConnected()
|
||||
{
|
||||
bool isConnected = false;
|
||||
|
||||
for (int counter = 0; counter < 100; counter++) {
|
||||
isConnected = localSocket.waitForConnected(20);
|
||||
if (isConnected)
|
||||
return isConnected;
|
||||
else {
|
||||
QThread::msleep(30);
|
||||
QCoreApplication::instance()->processEvents();
|
||||
}
|
||||
}
|
||||
|
||||
qWarning() << "Cannot connect:" << localSocket.errorString();
|
||||
|
||||
return isConnected;
|
||||
}
|
||||
|
||||
ClangCodeModelServerProxy &ConnectionClient::serverProxy()
|
||||
{
|
||||
return serverProxy_;
|
||||
@@ -278,37 +301,53 @@ bool ConnectionClient::isProcessIsRunning() const
|
||||
return process_ && process_->state() == QProcess::Running;
|
||||
}
|
||||
|
||||
QProcess *ConnectionClient::process() const
|
||||
void ConnectionClient::connectProcessFinished(QProcess *process) const
|
||||
{
|
||||
if (!process_)
|
||||
process_.reset(new QProcess);
|
||||
|
||||
return process_.get();
|
||||
}
|
||||
|
||||
void ConnectionClient::connectProcessFinished() const
|
||||
{
|
||||
connect(process(),
|
||||
connect(process,
|
||||
static_cast<void (QProcess::*)(int, QProcess::ExitStatus)>(&QProcess::finished),
|
||||
this,
|
||||
&ConnectionClient::restartProcess);
|
||||
&ConnectionClient::restartProcessAsynchronously);
|
||||
|
||||
}
|
||||
|
||||
void ConnectionClient::disconnectProcessFinished() const
|
||||
void ConnectionClient::connectProcessStarted(QProcess *process) const
|
||||
{
|
||||
if (process_) {
|
||||
disconnect(process_.get(),
|
||||
connect(process,
|
||||
&QProcess::started,
|
||||
this,
|
||||
&ConnectionClient::connectToLocalSocket);
|
||||
}
|
||||
|
||||
void ConnectionClient::disconnectProcessFinished(QProcess *process) const
|
||||
{
|
||||
if (process) {
|
||||
disconnect(process,
|
||||
static_cast<void (QProcess::*)(int, QProcess::ExitStatus)>(&QProcess::finished),
|
||||
this,
|
||||
&ConnectionClient::restartProcess);
|
||||
&ConnectionClient::restartProcessAsynchronously);
|
||||
}
|
||||
}
|
||||
|
||||
void ConnectionClient::connectStandardOutputAndError() const
|
||||
void ConnectionClient::connectStandardOutputAndError(QProcess *process) const
|
||||
{
|
||||
connect(process(), &QProcess::readyReadStandardOutput, this, &ConnectionClient::printStandardOutput);
|
||||
connect(process(), &QProcess::readyReadStandardError, this, &ConnectionClient::printStandardError);
|
||||
connect(process, &QProcess::readyReadStandardOutput, this, &ConnectionClient::printStandardOutput);
|
||||
connect(process, &QProcess::readyReadStandardError, this, &ConnectionClient::printStandardError);
|
||||
}
|
||||
|
||||
void ConnectionClient::connectLocalSocketError() const
|
||||
{
|
||||
connect(&localSocket,
|
||||
static_cast<void (QLocalSocket::*)(QLocalSocket::LocalSocketError)>(&QLocalSocket::error),
|
||||
this,
|
||||
&ConnectionClient::printLocalSocketError);
|
||||
}
|
||||
|
||||
void ConnectionClient::connectAliveTimer()
|
||||
{
|
||||
connect(&processAliveTimer,
|
||||
&QTimer::timeout,
|
||||
this,
|
||||
&ConnectionClient::restartProcessIfTimerIsNotResettedAndSocketIsEmpty);
|
||||
}
|
||||
|
||||
const QString &ConnectionClient::processPath() const
|
||||
|
||||
@@ -52,7 +52,7 @@ public:
|
||||
ConnectionClient(ClangCodeModelClientInterface *client);
|
||||
~ConnectionClient();
|
||||
|
||||
bool connectToServer();
|
||||
void startProcessAndConnectToServerAsynchronously();
|
||||
bool disconnectFromServer();
|
||||
bool isConnected() const;
|
||||
|
||||
@@ -64,34 +64,41 @@ public:
|
||||
const QString &processPath() const;
|
||||
void setProcessPath(const QString &processPath);
|
||||
|
||||
void startProcess();
|
||||
void restartProcess();
|
||||
void restartProcessAsynchronously();
|
||||
void restartProcessIfTimerIsNotResettedAndSocketIsEmpty();
|
||||
void finishProcess();
|
||||
bool isProcessIsRunning() const;
|
||||
|
||||
bool waitForEcho();
|
||||
bool waitForConnected();
|
||||
|
||||
ClangCodeModelServerProxy &serverProxy();
|
||||
|
||||
QProcess *processForTestOnly() const;
|
||||
|
||||
signals:
|
||||
void processRestarted();
|
||||
void connectedToLocalSocket();
|
||||
void processFinished();
|
||||
|
||||
private:
|
||||
bool connectToLocalSocket();
|
||||
void endProcess();
|
||||
void terminateProcess();
|
||||
void killProcess();
|
||||
std::unique_ptr<QProcess> startProcess();
|
||||
void finishProcess(std::unique_ptr<QProcess> &&process);
|
||||
void connectToLocalSocket();
|
||||
void endProcess(QProcess *process);
|
||||
void terminateProcess(QProcess *process);
|
||||
void killProcess(QProcess *process);
|
||||
void resetProcessIsStarting();
|
||||
void printLocalSocketError(QLocalSocket::LocalSocketError socketError);
|
||||
void printStandardOutput();
|
||||
void printStandardError();
|
||||
|
||||
QProcess *process() const;
|
||||
void connectProcessFinished() const;
|
||||
void disconnectProcessFinished() const;
|
||||
void connectStandardOutputAndError() const;
|
||||
void connectLocalSocketConnected();
|
||||
void connectProcessFinished(QProcess *process) const;
|
||||
void connectProcessStarted(QProcess *process) const;
|
||||
void disconnectProcessFinished(QProcess *process) const;
|
||||
void connectStandardOutputAndError(QProcess *process) const;
|
||||
void connectLocalSocketError() const;
|
||||
void connectAliveTimer();
|
||||
|
||||
void ensureMessageIsWritten();
|
||||
|
||||
@@ -103,7 +110,8 @@ private:
|
||||
ClangCodeModelServerProxy serverProxy_;
|
||||
QTimer processAliveTimer;
|
||||
QString processPath_;
|
||||
bool isAliveTimerResetted;
|
||||
bool isAliveTimerResetted = false;
|
||||
bool processIsStarting = false;
|
||||
|
||||
LinePrefixer stdErrPrefixer;
|
||||
LinePrefixer stdOutPrefixer;
|
||||
|
||||
@@ -331,12 +331,11 @@ void IpcCommunicator::initializeBackend()
|
||||
m_connection.setProcessAliveTimerInterval(30 * 1000);
|
||||
m_connection.setProcessPath(clangBackEndProcessPath);
|
||||
|
||||
connect(&m_connection, &ConnectionClient::processRestarted,
|
||||
connect(&m_connection, &ConnectionClient::connectedToLocalSocket,
|
||||
this, &IpcCommunicator::onBackendRestarted);
|
||||
|
||||
// TODO: Add a asynchron API to ConnectionClient, otherwise we might hang here
|
||||
if (m_connection.connectToServer())
|
||||
initializeBackendWithCurrentData();
|
||||
m_connection.startProcessAndConnectToServerAsynchronously();
|
||||
}
|
||||
|
||||
static QStringList projectPartOptions(const CppTools::ProjectPart::Ptr &projectPart)
|
||||
|
||||
@@ -60,6 +60,7 @@
|
||||
using namespace ClangBackEnd;
|
||||
|
||||
using ::testing::Eq;
|
||||
using ::testing::SizeIs;
|
||||
|
||||
class ClientServerOutsideProcess : public ::testing::Test
|
||||
{
|
||||
@@ -77,30 +78,35 @@ protected:
|
||||
MockClangCodeModelClient ClientServerOutsideProcess::mockClangCodeModelClient;
|
||||
ClangBackEnd::ConnectionClient ClientServerOutsideProcess::client(&ClientServerOutsideProcess::mockClangCodeModelClient);
|
||||
|
||||
TEST_F(ClientServerOutsideProcess, RestartProcess)
|
||||
TEST_F(ClientServerOutsideProcess, RestartProcessAsynchronously)
|
||||
{
|
||||
client.restartProcess();
|
||||
QSignalSpy clientSpy(&client, &ConnectionClient::connectedToLocalSocket);
|
||||
|
||||
client.restartProcessAsynchronously();
|
||||
|
||||
ASSERT_TRUE(clientSpy.wait(100000));
|
||||
ASSERT_TRUE(client.isProcessIsRunning());
|
||||
ASSERT_TRUE(client.isConnected());
|
||||
}
|
||||
|
||||
TEST_F(ClientServerOutsideProcess, RestartProcessAfterAliveTimeout)
|
||||
{
|
||||
QSignalSpy clientSpy(&client, SIGNAL(processRestarted()));
|
||||
QSignalSpy clientSpy(&client, &ConnectionClient::connectedToLocalSocket);
|
||||
|
||||
client.setProcessAliveTimerInterval(1);
|
||||
|
||||
ASSERT_TRUE(clientSpy.wait(100000));
|
||||
ASSERT_THAT(clientSpy, SizeIs(1));
|
||||
}
|
||||
|
||||
TEST_F(ClientServerOutsideProcess, RestartProcessAfterTermination)
|
||||
{
|
||||
QSignalSpy clientSpy(&client, SIGNAL(processRestarted()));
|
||||
QSignalSpy clientSpy(&client, &ConnectionClient::connectedToLocalSocket);
|
||||
|
||||
client.processForTestOnly()->kill();
|
||||
|
||||
ASSERT_TRUE(clientSpy.wait(100000));
|
||||
ASSERT_THAT(clientSpy, SizeIs(1));
|
||||
}
|
||||
|
||||
TEST_F(ClientServerOutsideProcess, SendRegisterTranslationUnitForEditorMessage)
|
||||
@@ -171,9 +177,13 @@ TEST_F(ClientServerOutsideProcess, SendUnregisterProjectPartsForEditorMessage)
|
||||
|
||||
void ClientServerOutsideProcess::SetUpTestCase()
|
||||
{
|
||||
QSignalSpy clientSpy(&client, &ConnectionClient::connectedToLocalSocket);
|
||||
client.setProcessPath(Utils::HostOsInfo::withExecutableSuffix(QStringLiteral(ECHOSERVER)));
|
||||
|
||||
ASSERT_TRUE(client.connectToServer());
|
||||
client.startProcessAndConnectToServerAsynchronously();
|
||||
|
||||
ASSERT_TRUE(clientSpy.wait(100000));
|
||||
ASSERT_THAT(clientSpy, SizeIs(1));
|
||||
}
|
||||
|
||||
void ClientServerOutsideProcess::TearDownTestCase()
|
||||
@@ -189,6 +199,9 @@ void ClientServerOutsideProcess::SetUp()
|
||||
|
||||
void ClientServerOutsideProcess::TearDown()
|
||||
{
|
||||
client.setProcessAliveTimerInterval(1000000);
|
||||
client.waitForConnected();
|
||||
|
||||
ASSERT_TRUE(client.isProcessIsRunning());
|
||||
ASSERT_TRUE(client.isConnected());
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user