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:
Marco Bubke
2016-07-11 13:44:02 +02:00
committed by Tim Jenssen
parent 2b08faab83
commit cda6e3c15a
4 changed files with 162 additions and 103 deletions

View File

@@ -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

View File

@@ -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;

View File

@@ -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)

View File

@@ -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());
}