From 35f0e9e6e44c940a41df0438d5eaacf95ec4fb2d Mon Sep 17 00:00:00 2001 From: Nick Bolton Date: Fri, 18 Apr 2025 13:56:37 +0100 Subject: [PATCH] feat(gui): Retry mechanism for Windows Daemon IPC client --- src/lib/gui/MainWindow.cpp | 6 +- src/lib/gui/MainWindow.h | 2 +- src/lib/gui/core/CoreProcess.cpp | 4 +- src/lib/gui/core/CoreProcess.h | 2 +- src/lib/gui/ipc/DaemonIpcClient.cpp | 100 ++++++++++++++++++------- src/lib/gui/ipc/DaemonIpcClient.h | 17 ++++- src/lib/platform/MSWindowsWatchdog.cpp | 2 +- 7 files changed, 94 insertions(+), 39 deletions(-) diff --git a/src/lib/gui/MainWindow.cpp b/src/lib/gui/MainWindow.cpp index 6634d697d..b5313742b 100644 --- a/src/lib/gui/MainWindow.cpp +++ b/src/lib/gui/MainWindow.cpp @@ -289,7 +289,9 @@ void MainWindow::connectSlots() connect(&m_coreProcess, &CoreProcess::processStateChanged, this, &MainWindow::coreProcessStateChanged); connect(&m_coreProcess, &CoreProcess::connectionStateChanged, this, &MainWindow::coreConnectionStateChanged); connect(&m_coreProcess, &CoreProcess::secureSocket, this, &MainWindow::secureSocket); - connect(&m_coreProcess, &CoreProcess::daemonIpcClientConnectFailed, this, &MainWindow::daemonIpcClientConnectFailed); + connect( + &m_coreProcess, &CoreProcess::daemonIpcClientConnectionFailed, this, &MainWindow::daemonIpcClientConnectionFailed + ); connect(m_actionAbout, &QAction::triggered, this, &MainWindow::openAboutDialog); connect(m_actionClearSettings, &QAction::triggered, this, &MainWindow::clearSettings); @@ -1145,7 +1147,7 @@ bool MainWindow::regenerateLocalFingerprints() return true; } -void MainWindow::daemonIpcClientConnectFailed() +void MainWindow::daemonIpcClientConnectionFailed() { if (deskflow::gui::messages::showDaemonOffline(this)) { m_coreProcess.retryDaemon(); diff --git a/src/lib/gui/MainWindow.h b/src/lib/gui/MainWindow.h index 0eca5ae9f..b4fdcb43b 100644 --- a/src/lib/gui/MainWindow.h +++ b/src/lib/gui/MainWindow.h @@ -158,7 +158,7 @@ private: void showAndActivate(); void showHostNameEditor(); void setHostName(); - void daemonIpcClientConnectFailed(); + void daemonIpcClientConnectionFailed(); void toggleCanRunCore(bool enableButtons); void remoteHostChanged(const QString newRemoteHost); diff --git a/src/lib/gui/core/CoreProcess.cpp b/src/lib/gui/core/CoreProcess.cpp index 84748e65c..8240be238 100644 --- a/src/lib/gui/core/CoreProcess.cpp +++ b/src/lib/gui/core/CoreProcess.cpp @@ -140,7 +140,9 @@ CoreProcess::CoreProcess(const IServerConfig &serverConfig, std::shared_ptrprocess(), &QProcessProxy::finished, this, &CoreProcess::onProcessFinished); diff --git a/src/lib/gui/core/CoreProcess.h b/src/lib/gui/core/CoreProcess.h index 03b0ed110..2388a512f 100644 --- a/src/lib/gui/core/CoreProcess.h +++ b/src/lib/gui/core/CoreProcess.h @@ -120,7 +120,7 @@ signals: void connectionStateChanged(ConnectionState state); void processStateChanged(ProcessState state); void secureSocket(bool enabled); - void daemonIpcClientConnectFailed(); + void daemonIpcClientConnectionFailed(); private slots: void onProcessFinished(int exitCode, QProcess::ExitStatus); diff --git a/src/lib/gui/ipc/DaemonIpcClient.cpp b/src/lib/gui/ipc/DaemonIpcClient.cpp index 20b570620..98b734156 100644 --- a/src/lib/gui/ipc/DaemonIpcClient.cpp +++ b/src/lib/gui/ipc/DaemonIpcClient.cpp @@ -16,6 +16,7 @@ namespace deskflow::gui::ipc { const auto kTimeout = 1000; +const auto kRetryLimit = 3; DaemonIpcClient::DaemonIpcClient(QObject *parent) : QObject(parent), @@ -27,54 +28,95 @@ DaemonIpcClient::DaemonIpcClient(QObject *parent) bool DaemonIpcClient::connectToServer() { - if (m_connecting) { - qDebug() << "daemon ipc client already connecting to server"; + if (m_state == State::Connecting) { + qWarning() << "daemon ipc client already connecting to server"; return false; } - qDebug() << "daemon ipc client connecting to server:" << kDaemonIpcName; - m_connecting = true; - m_socket->connectToServer(kDaemonIpcName); - - if (!m_socket->waitForConnected(kTimeout)) { - qWarning() << "daemon ipc client failed to connect"; - m_connecting = false; - Q_EMIT connectFailed(); - return false; + if (m_state != State::Unconnected) { + qDebug() << "daemon ipc client not in unconnected state, disconnecting"; + disconnectFromServer(); } - if (!sendMessage("hello", "hello", false)) { - qWarning() << "daemon ipc client failed to send hello"; - m_connecting = false; - Q_EMIT connectFailed(); - return false; + if (m_socket->state() != QLocalSocket::UnconnectedState) { + qWarning() << "daemon ipc client socket not in unconnected state, disconnecting"; + disconnectFromServer(); } - m_connecting = false; - m_connected = true; + for (int i = 0; i < kRetryLimit; ++i) { + if (i == 0) { + qDebug() << "daemon ipc client connecting to server:" << kDaemonIpcName; + } else { + qDebug() << "daemon ipc client retrying connection, attempt:" << i + 1; + } - qDebug() << "daemon ipc client connected"; - Q_EMIT connected(); + m_state = State::Connecting; + m_socket->connectToServer(kDaemonIpcName); - return true; + if (!m_socket->waitForConnected(kTimeout)) { + qWarning() << "daemon ipc client failed to connect"; + disconnectFromServer(); + continue; + } + + if (!sendMessage("hello", "hello", false)) { + qWarning() << "daemon ipc client failed to send hello"; + disconnectFromServer(); + continue; + } + + m_state = State::Connected; + qDebug() << "daemon ipc client connected"; + Q_EMIT connected(); + return true; + } + + qWarning() << "daemon ipc client failed to connect after" << kRetryLimit << "attempts"; + disconnectFromServer(); + Q_EMIT connectionFailed(); + return false; +} + +void DaemonIpcClient::disconnectFromServer() +{ + m_state = State::Disconnecting; + qDebug() << "daemon ipc client disconnecting from server"; + m_socket->disconnectFromServer(); + + if (m_socket->state() != QLocalSocket::UnconnectedState) { + qDebug() << "daemon ipc client waiting for socket to disconnect"; + m_socket->waitForDisconnected(kTimeout); + qDebug() << "daemon ipc client disconnected from server"; + } else { + qDebug() << "daemon ipc client socket already disconnected"; + } + + m_state = State::Unconnected; } void DaemonIpcClient::handleDisconnected() { - qWarning() << "daemon ipc client disconnected from server"; - m_connected = false; - Q_EMIT connectFailed(); + qDebug() << "daemon ipc client disconnected from server"; + if (m_state == State::Connected) { + Q_EMIT connectionFailed(); + } + + m_state = State::Unconnected; } void DaemonIpcClient::handleErrorOccurred() { qWarning() << "daemon ipc client error:" << m_socket->errorString(); - m_connected = false; + disconnectFromServer(); + + if (m_state == State::Connected) { + Q_EMIT connectionFailed(); + } } bool DaemonIpcClient::sendMessage(const QString &message, const QString &expectAck, const bool expectConnected) { - if (expectConnected && !m_connected) { + if (expectConnected && !isConnected()) { qWarning() << "cannot send command, ipc client not connected"; return false; } @@ -90,7 +132,7 @@ bool DaemonIpcClient::sendMessage(const QString &message, const QString &expectA qDebug() << "daemon ipc client waiting for ack: " << expectAck; if (!m_socket->waitForReadyRead(kTimeout)) { - qWarning() << "daemon ipc client failed to read response"; + qWarning() << "daemon ipc client socket ready read timed out"; return false; } @@ -124,8 +166,8 @@ bool DaemonIpcClient::keepAlive() } if (!sendMessage("noop")) { - qWarning() << "daemon ipc client keep alive ping failed"; - m_connected = false; + qWarning() << "daemon ipc client keep alive ping failed, reconnecting"; + connectToServer(); return false; } diff --git a/src/lib/gui/ipc/DaemonIpcClient.h b/src/lib/gui/ipc/DaemonIpcClient.h index 6b52b9d47..2d1cae0a4 100644 --- a/src/lib/gui/ipc/DaemonIpcClient.h +++ b/src/lib/gui/ipc/DaemonIpcClient.h @@ -16,9 +16,19 @@ class DaemonIpcClient : public QObject { Q_OBJECT + // Represents underlying socket state and whether the server responded to the hello message. + enum class State + { + Unconnected, + Connecting, + Connected, + Disconnecting, + }; + public: explicit DaemonIpcClient(QObject *parent = nullptr); bool connectToServer(); + void disconnectFromServer(); bool sendLogLevel(const QString &logLevel); bool sendStartProcess(const QString &command, bool elevate); bool sendStopProcess(); @@ -27,12 +37,12 @@ public: bool isConnected() const { - return m_connected; + return m_state == State::Connected; } signals: void connected(); - void connectFailed(); + void connectionFailed(); private slots: void handleDisconnected(); @@ -44,8 +54,7 @@ private: private: QLocalSocket *m_socket; - bool m_connected{false}; - bool m_connecting{false}; + State m_state{State::Unconnected}; }; } // namespace deskflow::gui::ipc diff --git a/src/lib/platform/MSWindowsWatchdog.cpp b/src/lib/platform/MSWindowsWatchdog.cpp index 01952b990..61cd28102 100644 --- a/src/lib/platform/MSWindowsWatchdog.cpp +++ b/src/lib/platform/MSWindowsWatchdog.cpp @@ -246,7 +246,7 @@ void MSWindowsWatchdog::mainLoop(void *) LOG_DEBUG("watchdog main loop finished"); if (m_process != nullptr) { - LOG_DEBUG("terminated running process on exit"); + LOG_DEBUG("terminating running process on exit"); m_process->shutdown(); m_process.reset(); }