aboutsummaryrefslogtreecommitdiffstats
path: root/src/libs/qmldebug
diff options
context:
space:
mode:
authorUlf Hermann <ulf.hermann@qt.io>2017-09-27 18:23:53 +0200
committerUlf Hermann <ulf.hermann@qt.io>2017-10-16 14:04:24 +0000
commitd386b4ed6df48f19e8b1c2b1b5044ff847c59699 (patch)
tree665d07f8ed53e97093af3cf78517f73c1cb315a0 /src/libs/qmldebug
parenta54685aa59e48fc993c1d3531d6abd9c70ca3a56 (diff)
QmlDebug: Extract a general QML debug connection manager from QmlProfiler
We can use it for other QmlDebug run controls, too. Change-Id: Iee1cd592848ef4c48954a2674b6fa509223fcda4 Reviewed-by: hjk <hjk@qt.io>
Diffstat (limited to 'src/libs/qmldebug')
-rw-r--r--src/libs/qmldebug/qmldebug-lib.pri6
-rw-r--r--src/libs/qmldebug/qmldebug.qbs2
-rw-r--r--src/libs/qmldebug/qmldebugconnectionmanager.cpp255
-rw-r--r--src/libs/qmldebug/qmldebugconnectionmanager.h89
4 files changed, 350 insertions, 2 deletions
diff --git a/src/libs/qmldebug/qmldebug-lib.pri b/src/libs/qmldebug/qmldebug-lib.pri
index 28c269ab35..9236637338 100644
--- a/src/libs/qmldebug/qmldebug-lib.pri
+++ b/src/libs/qmldebug/qmldebug-lib.pri
@@ -20,7 +20,8 @@ HEADERS += \
$$PWD/qmltoolsclient.h \
$$PWD/qmlenginecontrolclient.h \
$$PWD/qmldebugcommandlinearguments.h \
- $$PWD/qmldebugconnection.h
+ $$PWD/qmldebugconnection.h \
+ $$PWD/qmldebugconnectionmanager.h
SOURCES += \
$$PWD/qmldebugclient.cpp \
@@ -33,4 +34,5 @@ SOURCES += \
$$PWD/qmltoolsclient.cpp \
$$PWD/declarativeenginedebugclient.cpp \
$$PWD/qmlenginecontrolclient.cpp \
- $$PWD/qmldebugconnection.cpp
+ $$PWD/qmldebugconnection.cpp \
+ $$PWD/qmldebugconnectionmanager.cpp
diff --git a/src/libs/qmldebug/qmldebug.qbs b/src/libs/qmldebug/qmldebug.qbs
index 2f5c30e163..9cac1e8d7f 100644
--- a/src/libs/qmldebug/qmldebug.qbs
+++ b/src/libs/qmldebug/qmldebug.qbs
@@ -29,6 +29,8 @@ Project {
"qmldebugcommandlinearguments.h",
"qmldebugconnection.cpp",
"qmldebugconnection.h",
+ "qmldebugconnectionmanager.cpp",
+ "qmldebugconnectionmanager.h",
"qmldebugconstants.h",
"qmlenginecontrolclient.cpp",
"qmlenginecontrolclient.h",
diff --git a/src/libs/qmldebug/qmldebugconnectionmanager.cpp b/src/libs/qmldebug/qmldebugconnectionmanager.cpp
new file mode 100644
index 0000000000..bb1c0ebde8
--- /dev/null
+++ b/src/libs/qmldebug/qmldebugconnectionmanager.cpp
@@ -0,0 +1,255 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt Creator.
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+****************************************************************************/
+
+#include "qmldebugconnectionmanager.h"
+#include "qmldebugconnection.h"
+
+#include <utils/qtcassert.h>
+#include <utils/url.h>
+
+namespace QmlDebug {
+
+QmlDebugConnectionManager::QmlDebugConnectionManager(QObject *parent) : QObject(parent)
+{
+}
+
+QmlDebugConnectionManager::~QmlDebugConnectionManager()
+{
+ // Don't receive any signals from the dtors of child objects while our own dtor is running.
+ // That can lead to invalid reads.
+ if (m_connection)
+ disconnectConnectionSignals();
+}
+
+void QmlDebugConnectionManager::setRetryParams(int interval, int maxAttempts)
+{
+ m_retryInterval = interval;
+ m_maximumRetries = maxAttempts;
+}
+
+void QmlDebugConnectionManager::connectToServer(const QUrl &server)
+{
+ if (m_server != server) {
+ m_server = server;
+ destroyConnection();
+ stopConnectionTimer();
+ }
+ if (server.scheme() == Utils::urlTcpScheme())
+ connectToTcpServer();
+ else if (server.scheme() == Utils::urlSocketScheme())
+ startLocalServer();
+ else
+ QTC_ASSERT(false, emit connectionFailed());
+}
+
+void QmlDebugConnectionManager::disconnectFromServer()
+{
+ m_server.clear();
+ destroyConnection();
+ stopConnectionTimer();
+}
+
+static quint16 port16(const QUrl &url)
+{
+ const int port32 = url.port();
+ QTC_ASSERT(port32 > 0 && port32 <= std::numeric_limits<quint16>::max(), return 0);
+ return static_cast<quint16>(port32);
+}
+
+void QmlDebugConnectionManager::connectToTcpServer()
+{
+ // Calling this again when we're already trying means "reset the retry timer". This is
+ // useful in cases where we have to parse the port from the output. We might waste retries
+ // on an initial guess for the port.
+ stopConnectionTimer();
+ connect(&m_connectionTimer, &QTimer::timeout, this, [this]{
+ QTC_ASSERT(!isConnected(), return);
+
+ if (++(m_numRetries) < m_maximumRetries) {
+ if (m_connection.isNull()) {
+ // If the previous connection failed, recreate it.
+ createConnection();
+ m_connection->connectToHost(m_server.host(), port16(m_server));
+ } else if (m_numRetries < 3
+ && m_connection->socketState() != QAbstractSocket::ConnectedState) {
+ // If we don't get connected in the first retry interval, drop the socket and try
+ // with a new one. On some operating systems (maxOS) the very first connection to a
+ // TCP server takes a very long time to get established and this helps.
+ // On other operating systems (windows) every connection takes forever to get
+ // established. So, after tearing down and rebuilding the socket twice, just
+ // keep trying with the same one.
+ m_connection->connectToHost(m_server.host(), port16(m_server));
+ } // Else leave it alone and wait for hello.
+ } else {
+ // On final timeout, clear the connection.
+ stopConnectionTimer();
+ destroyConnection();
+ emit connectionFailed();
+ }
+ });
+ m_connectionTimer.start(m_retryInterval);
+
+ if (m_connection.isNull()) {
+ createConnection();
+ QTC_ASSERT(m_connection, emit connectionFailed(); return);
+ m_connection->connectToHost(m_server.host(), port16(m_server));
+ }
+}
+
+void QmlDebugConnectionManager::startLocalServer()
+{
+ stopConnectionTimer();
+ connect(&m_connectionTimer, &QTimer::timeout, this, [this]() {
+ QTC_ASSERT(!isConnected(), return);
+
+ // We leave the server running as some application might currently be trying to
+ // connect. Don't cut this off, or the application might hang on the hello mutex.
+ // qmlConnectionFailed() might drop the connection, which is fatal. We detect this
+ // here and signal it accordingly.
+
+ if (!m_connection || ++(m_numRetries) >= m_maximumRetries) {
+ stopConnectionTimer();
+ emit connectionFailed();
+ }
+ });
+ m_connectionTimer.start(m_retryInterval);
+
+ if (m_connection.isNull()) {
+ // Otherwise, reuse the same one
+ createConnection();
+ QTC_ASSERT(m_connection, emit connectionFailed(); return);
+ m_connection->startLocalServer(m_server.path());
+ }
+}
+
+void QmlDebugConnectionManager::retryConnect()
+{
+ if (m_server.scheme() == Utils::urlSocketScheme()) {
+ startLocalServer();
+ } else if (m_server.scheme() == Utils::urlTcpScheme()) {
+ destroyConnection();
+ connectToTcpServer();
+ } else {
+ emit connectionFailed();
+ }
+}
+
+void QmlDebugConnectionManager::logState(const QString &message)
+{
+ Q_UNUSED(message);
+}
+
+QmlDebugConnection *QmlDebugConnectionManager::connection() const
+{
+ return m_connection.data();
+}
+
+void QmlDebugConnectionManager::createConnection()
+{
+ QTC_ASSERT(m_connection.isNull(), destroyConnection());
+
+ m_connection.reset(new QmlDebug::QmlDebugConnection);
+
+ createClients();
+ connectConnectionSignals();
+}
+
+void QmlDebugConnectionManager::connectConnectionSignals()
+{
+ QTC_ASSERT(m_connection, return);
+ QObject::connect(m_connection.data(), &QmlDebug::QmlDebugConnection::connected,
+ this, &QmlDebugConnectionManager::qmlDebugConnectionOpened);
+ QObject::connect(m_connection.data(), &QmlDebug::QmlDebugConnection::disconnected,
+ this, &QmlDebugConnectionManager::qmlDebugConnectionClosed);
+ QObject::connect(m_connection.data(), &QmlDebug::QmlDebugConnection::connectionFailed,
+ this, &QmlDebugConnectionManager::qmlDebugConnectionFailed);
+
+ QObject::connect(m_connection.data(), &QmlDebug::QmlDebugConnection::logStateChange,
+ this, &QmlDebugConnectionManager::logState);
+ QObject::connect(m_connection.data(), &QmlDebug::QmlDebugConnection::logError,
+ this, &QmlDebugConnectionManager::logState);
+}
+
+void QmlDebugConnectionManager::disconnectConnectionSignals()
+{
+ QTC_ASSERT(m_connection, return);
+ m_connection->disconnect();
+}
+
+bool QmlDebugConnectionManager::isConnected() const
+{
+ return m_connection && m_connection->isConnected();
+}
+
+void QmlDebugConnectionManager::destroyConnection()
+{
+ // This might be called indirectly by QDebugConnectionPrivate::readyRead().
+ // Therefore, allow the function to complete before deleting the object.
+ if (m_connection) {
+ // Don't receive any more signals from the connection or the client
+ disconnectConnectionSignals();
+ destroyClients();
+ m_connection.take()->deleteLater();
+ }
+}
+
+void QmlDebugConnectionManager::qmlDebugConnectionOpened()
+{
+ logState(tr("Debug connection opened"));
+ QTC_ASSERT(m_connection, return);
+ QTC_ASSERT(m_connection->isConnected(), return);
+ stopConnectionTimer();
+ emit connectionOpened();
+}
+
+void QmlDebugConnectionManager::qmlDebugConnectionClosed()
+{
+ logState(tr("Debug connection closed"));
+ QTC_ASSERT(m_connection, return);
+ QTC_ASSERT(!m_connection->isConnected(), return);
+ destroyConnection();
+ emit connectionClosed();
+}
+
+void QmlDebugConnectionManager::qmlDebugConnectionFailed()
+{
+ logState(tr("Debug connection failed"));
+ QTC_ASSERT(m_connection, return);
+ QTC_ASSERT(!m_connection->isConnected(), /**/);
+
+ destroyConnection();
+ // The retry handler, driven by m_connectionTimer should decide to retry or signal a failure.
+
+ QTC_ASSERT(m_connectionTimer.isActive(), emit connectionFailed());
+}
+
+void QmlDebugConnectionManager::stopConnectionTimer()
+{
+ m_connectionTimer.stop();
+ m_connectionTimer.disconnect();
+ m_numRetries = 0;
+}
+
+} // namespace QmlDebug
diff --git a/src/libs/qmldebug/qmldebugconnectionmanager.h b/src/libs/qmldebug/qmldebugconnectionmanager.h
new file mode 100644
index 0000000000..b9ff5618db
--- /dev/null
+++ b/src/libs/qmldebug/qmldebugconnectionmanager.h
@@ -0,0 +1,89 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt Creator.
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+****************************************************************************/
+
+#pragma once
+
+#include <qmldebug/qmldebug_global.h>
+#include <qmldebug/qmldebugclient.h>
+
+#include <QPointer>
+#include <QTimer>
+#include <QUrl>
+
+namespace QmlDebug {
+
+class QMLDEBUG_EXPORT QmlDebugConnectionManager : public QObject
+{
+ Q_OBJECT
+public:
+ explicit QmlDebugConnectionManager(QObject *parent = 0);
+ ~QmlDebugConnectionManager();
+
+ void connectToServer(const QUrl &server);
+ void disconnectFromServer();
+
+ bool isConnected() const;
+
+ void setRetryParams(int interval, int maxAttempts);
+ void retryConnect();
+
+signals:
+ void connectionOpened();
+ void connectionFailed();
+ void connectionClosed();
+
+protected:
+ virtual void createClients() = 0;
+ virtual void destroyClients() = 0;
+ virtual void logState(const QString &message);
+
+ QmlDebugConnection *connection() const;
+
+private:
+ void connectToTcpServer();
+ void startLocalServer();
+
+ QScopedPointer<QmlDebug::QmlDebugConnection> m_connection;
+ QTimer m_connectionTimer;
+ QUrl m_server;
+
+ int m_retryInterval = 200;
+ int m_maximumRetries = 50;
+ int m_numRetries = 0;
+
+ void createConnection();
+ void destroyConnection();
+
+ void connectConnectionSignals();
+ void disconnectConnectionSignals();
+
+ void stopConnectionTimer();
+
+ void qmlDebugConnectionOpened();
+ void qmlDebugConnectionClosed();
+ void qmlDebugConnectionFailed();
+};
+
+} // namespace QmlDebug