diff --git a/src/libs/clangbackendipc/connectionclient.cpp b/src/libs/clangbackendipc/connectionclient.cpp index 7f30a6da0b8..2270c7c3311 100644 --- a/src/libs/clangbackendipc/connectionclient.cpp +++ b/src/libs/clangbackendipc/connectionclient.cpp @@ -31,6 +31,7 @@ #include "cmbunregistertranslationunitsforeditormessage.h" #include +#include #include #include #include @@ -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(&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 ConnectionClient::startProcess() { - TIME_SCOPE_DURATION("ConnectionClient::startProcess"); + processIsStarting = true; - if (!isProcessIsRunning()) { - connectProcessFinished(); - connectStandardOutputAndError(); - process()->setProcessEnvironment(processEnvironment()); - process()->start(processPath(), {connectionName()}); - process()->waitForStarted(); - resetProcessAliveTimer(); - } + auto process = std::unique_ptr(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:" <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 &&process) { TIME_SCOPE_DURATION("ConnectionClient::finishProcess"); - processAliveTimer.stop(); + if (process) { + processAliveTimer.stop(); - disconnectProcessFinished(); - endProcess(); - disconnectFromServer(); - terminateProcess(); - killProcess(); + disconnectProcessFinished(process.get()); + endProcess(process.get()); + disconnectFromServer(); + terminateProcess(process.get()); + killProcess(process.get()); - process_.reset(); - - serverProxy_.resetCounter(); + 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(&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(&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(&QLocalSocket::error), + this, + &ConnectionClient::printLocalSocketError); +} + +void ConnectionClient::connectAliveTimer() +{ + connect(&processAliveTimer, + &QTimer::timeout, + this, + &ConnectionClient::restartProcessIfTimerIsNotResettedAndSocketIsEmpty); } const QString &ConnectionClient::processPath() const diff --git a/src/libs/clangbackendipc/connectionclient.h b/src/libs/clangbackendipc/connectionclient.h index 3a302795d46..76b669764e0 100644 --- a/src/libs/clangbackendipc/connectionclient.h +++ b/src/libs/clangbackendipc/connectionclient.h @@ -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 startProcess(); + void finishProcess(std::unique_ptr &&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; diff --git a/src/plugins/clangcodemodel/clangbackendipcintegration.cpp b/src/plugins/clangcodemodel/clangbackendipcintegration.cpp index 2fc53500cc2..80d1cfa69ea 100644 --- a/src/plugins/clangcodemodel/clangbackendipcintegration.cpp +++ b/src/plugins/clangcodemodel/clangbackendipcintegration.cpp @@ -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) diff --git a/tests/unit/unittest/clientserveroutsideprocess.cpp b/tests/unit/unittest/clientserveroutsideprocess.cpp index 58da7b5f77b..deba8d6964f 100644 --- a/tests/unit/unittest/clientserveroutsideprocess.cpp +++ b/tests/unit/unittest/clientserveroutsideprocess.cpp @@ -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()); }