From 5375c095c3eb71c669053c4ca569a960dc76fabf Mon Sep 17 00:00:00 2001 From: Ulf Hermann Date: Fri, 10 Oct 2014 13:32:38 +0200 Subject: QmlDebug: Provide public method for starting a debug server With QQmlDebuggingEnabler::startTcpDebugServer you can create a debug server for debugging or profiling also without the qmljsdebugger command line argument. Change-Id: I642f73680585f9c7578762bcc0b247c736fe1338 Reviewed-by: Simon Hausmann --- .../qmltooling/qmldbg_tcp/qtcpserverconnection.cpp | 17 ++- .../qmltooling/qmldbg_tcp/qtcpserverconnection.h | 4 +- src/qml/debugger/qqmldebug.h | 2 + src/qml/debugger/qqmldebugserver.cpp | 137 +++++++++++++++------ src/qml/debugger/qqmldebugserver_p.h | 5 +- src/qml/debugger/qqmldebugserverconnection_p.h | 3 +- src/qml/qml/qqmlengine.cpp | 20 +++ 7 files changed, 144 insertions(+), 44 deletions(-) diff --git a/src/plugins/qmltooling/qmldbg_tcp/qtcpserverconnection.cpp b/src/plugins/qmltooling/qmldbg_tcp/qtcpserverconnection.cpp index 4388fa7eb1..810f8d18e8 100644 --- a/src/plugins/qmltooling/qmldbg_tcp/qtcpserverconnection.cpp +++ b/src/plugins/qmltooling/qmldbg_tcp/qtcpserverconnection.cpp @@ -134,7 +134,7 @@ bool QTcpServerConnection::waitForMessage() return d->protocol->waitForReadyRead(-1); } -void QTcpServerConnection::setPortRange(int portFrom, int portTo, bool block, +bool QTcpServerConnection::setPortRange(int portFrom, int portTo, bool block, const QString &hostaddress) { Q_D(QTcpServerConnection); @@ -143,12 +143,16 @@ void QTcpServerConnection::setPortRange(int portFrom, int portTo, bool block, d->block = block; d->hostaddress = hostaddress; - listen(); - if (block) - d->tcpServer->waitForNewConnection(-1); + return listen(); } -void QTcpServerConnection::listen() +void QTcpServerConnection::waitForConnection() +{ + Q_D(QTcpServerConnection); + d->tcpServer->waitForNewConnection(-1); +} + +bool QTcpServerConnection::listen() { Q_D(QTcpServerConnection); @@ -177,6 +181,9 @@ void QTcpServerConnection::listen() qWarning("QML Debugger: Unable to listen to port %d.", d->portFrom); else qWarning("QML Debugger: Unable to listen to ports %d - %d.", d->portFrom, d->portTo); + return false; + } else { + return true; } } diff --git a/src/plugins/qmltooling/qmldbg_tcp/qtcpserverconnection.h b/src/plugins/qmltooling/qmldbg_tcp/qtcpserverconnection.h index 1b339f1239..66a9c59b26 100644 --- a/src/plugins/qmltooling/qmldbg_tcp/qtcpserverconnection.h +++ b/src/plugins/qmltooling/qmldbg_tcp/qtcpserverconnection.h @@ -53,14 +53,14 @@ public: ~QTcpServerConnection(); void setServer(QQmlDebugServer *server); - void setPortRange(int portFrom, int portTo, bool bock, const QString &hostaddress); + bool setPortRange(int portFrom, int portTo, bool bock, const QString &hostaddress); bool isConnected() const; void send(const QList &messages); void disconnect(); bool waitForMessage(); - void listen(); + bool listen(); void waitForConnection(); private Q_SLOTS: diff --git a/src/qml/debugger/qqmldebug.h b/src/qml/debugger/qqmldebug.h index 876d0683ce..fc22b295a6 100644 --- a/src/qml/debugger/qqmldebug.h +++ b/src/qml/debugger/qqmldebug.h @@ -42,6 +42,8 @@ QT_BEGIN_NAMESPACE struct Q_QML_EXPORT QQmlDebuggingEnabler { QQmlDebuggingEnabler(bool printWarning = true); + static bool startTcpDebugServer(int port, bool block = false, + const QString &hostName = QString()); }; // Execute code in constructor before first QQmlEngine is instantiated diff --git a/src/qml/debugger/qqmldebugserver.cpp b/src/qml/debugger/qqmldebugserver.cpp index 59ac548fa8..9715b5c8d4 100644 --- a/src/qml/debugger/qqmldebugserver.cpp +++ b/src/qml/debugger/qqmldebugserver.cpp @@ -104,6 +104,8 @@ class QQmlDebugServerPrivate : public QObjectPrivate public: QQmlDebugServerPrivate(); + bool start(int portFrom, int portTo, bool block, const QString &hostAddress, + const QString &pluginName); void advertisePlugins(); void cleanup(); QQmlDebugServerConnection *loadConnectionPlugin(const QString &pluginName); @@ -142,6 +144,7 @@ private: void _q_changeServiceState(const QString &serviceName, QQmlDebugService::State newState); void _q_sendMessages(const QList &messages); + void _q_removeThread(); }; void QQmlDebugServerInstanceWrapper::cleanup() @@ -154,7 +157,7 @@ public: m_pluginName = pluginName; } - void setPortRange(int portFrom, int portTo, bool block, QString &hostAddress) { + void setPortRange(int portFrom, int portTo, bool block, const QString &hostAddress) { m_portFrom = portFrom; m_portTo = portTo; m_block = block; @@ -226,12 +229,13 @@ void QQmlDebugServerPrivate::cleanup() while (!changeServiceStateCalls.testAndSetOrdered(0, 0)) loop.processEvents(); - // Stop the thread while the application is still there. - if (thread) { - thread->exit(); - thread->wait(); - delete thread; - thread = 0; + // Stop the thread while the application is still there. Copy here as the thread will set itself + // to 0 when it stops. It will also do deleteLater, but as long as we don't allow the GUI + // thread's event loop to run we're safe from that. + QThread *threadCopy = thread; + if (threadCopy) { + threadCopy->exit(); + threadCopy->wait(); } } @@ -252,6 +256,7 @@ QQmlDebugServerConnection *QQmlDebugServerPrivate::loadConnectionPlugin( } } + QQmlDebugServerConnection *loadedConnection = 0; foreach (const QString &pluginPath, pluginCandidates) { if (qmlDebugVerbose()) qDebug() << "QML Debugger: Trying to load plugin " << pluginPath << "..."; @@ -263,13 +268,13 @@ QQmlDebugServerConnection *QQmlDebugServerPrivate::loadConnectionPlugin( continue; } if (QObject *instance = loader.instance()) - connection = qobject_cast(instance); + loadedConnection = qobject_cast(instance); - if (connection) { + if (loadedConnection) { if (qmlDebugVerbose()) qDebug() << "QML Debugger: Plugin successfully loaded."; - return connection; + return loadedConnection; } if (qmlDebugVerbose()) @@ -289,18 +294,22 @@ void QQmlDebugServerThread::run() #if defined(QT_STATIC) && ! defined(QT_QML_NO_DEBUGGER) QQmlDebugServerConnection *connection = new QTcpServerConnection; - server->d_func()->connection = connection; #else QQmlDebugServerConnection *connection = server->d_func()->loadConnectionPlugin(m_pluginName); #endif if (connection) { connection->setServer(server); - connection->setPortRange(m_portFrom, m_portTo, m_block, m_hostAddress); + if (!connection->setPortRange(m_portFrom, m_portTo, m_block, m_hostAddress)) { + delete connection; + return; + } + server->d_func()->connection = connection; + if (m_block) + connection->waitForConnection(); } else { - QCoreApplicationPrivate *appD = static_cast(QObjectPrivate::get(qApp)); - qWarning() << QString(QLatin1String("QML Debugger: Ignoring \"-qmljsdebugger=%1\". " - "Remote debugger plugin has not been found.")).arg(appD->qmljsDebugArgumentsString()); + qWarning() << "QML Debugger: Couldn't load plugin" << m_pluginName; + return; } exec(); @@ -318,6 +327,18 @@ bool QQmlDebugServer::hasDebuggingClient() const && d->gotHello; } +bool QQmlDebugServer::hasThread() const +{ + Q_D(const QQmlDebugServer); + return d->thread != 0; +} + +bool QQmlDebugServer::hasConnection() const +{ + Q_D(const QQmlDebugServer); + return d->connection != 0; +} + bool QQmlDebugServer::blockingMode() const { Q_D(const QQmlDebugServer); @@ -339,17 +360,46 @@ QQmlDebugServer *QQmlDebugServer::instance() } } -static void cleanup() +static void cleanupOnShutdown() { QQmlDebugServerInstanceWrapper *wrapper = debugServerInstance(); if (wrapper) wrapper->cleanup(); } +bool QQmlDebugServerPrivate::start(int portFrom, int portTo, bool block, const QString &hostAddress, + const QString &pluginName) +{ + if (!QQmlEnginePrivate::qml_debugging_enabled) + return false; + if (thread) + return false; + static bool postRoutineAdded = false; + if (!postRoutineAdded) { + qAddPostRoutine(cleanupOnShutdown); + postRoutineAdded = true; + } + Q_Q(QQmlDebugServer); + thread = new QQmlDebugServerThread; + q->moveToThread(thread); + + // Remove the thread immmediately when it finishes, so that we don't have to wait for the event + // loop to signal that. + QObject::connect(thread, SIGNAL(finished()), q, SLOT(_q_removeThread()), Qt::DirectConnection); + + thread->setObjectName(QStringLiteral("QQmlDebugServerThread")); + thread->setPluginName(pluginName); + thread->setPortRange(portFrom, portTo == -1 ? portFrom : portTo, block, hostAddress); + blockingMode = block; + thread->start(); + return true; +} QQmlDebugServer::QQmlDebugServer() : QObject(*(new QQmlDebugServerPrivate)) { + if (qApp == 0) + return; QCoreApplicationPrivate *appD = static_cast(QObjectPrivate::get(qApp)); #ifndef QT_QML_NO_DEBUGGER // ### remove port definition when protocol is changed @@ -400,17 +450,8 @@ QQmlDebugServer::QQmlDebugServer() } if (ok) { - qAddPostRoutine(cleanup); Q_D(QQmlDebugServer); - d->thread = new QQmlDebugServerThread; - moveToThread(d->thread); - d->thread->setObjectName(QStringLiteral("QQmlDebugServerThread")); - d->thread->setPluginName(pluginName); - d->thread->setPortRange(portFrom, portTo, block, hostAddress); - - d->blockingMode = block; - d->thread->start(); - + d->start(portFrom, portTo, block, hostAddress, pluginName); } else { qWarning() << QString(QLatin1String( "QML Debugger: Ignoring \"-qmljsdebugger=%1\". " @@ -428,15 +469,6 @@ QQmlDebugServer::QQmlDebugServer() #endif } -// called from GUI thread! -QQmlDebugServer::~QQmlDebugServer() -{ - Q_D(QQmlDebugServer); - - delete d->thread; - delete d->connection; -} - void QQmlDebugServer::receiveMessage(const QByteArray &message) { // to be executed in debugger thread @@ -574,6 +606,24 @@ void QQmlDebugServerPrivate::_q_sendMessages(const QList &messages) connection->send(messages); } +void QQmlDebugServerPrivate::_q_removeThread() +{ + Q_ASSERT(thread->isFinished()); + Q_ASSERT(QThread::currentThread() == thread); + + QThread *parentThread = thread->thread(); + + // We cannot delete it right away as it will access its data after the finished() signal. + thread->deleteLater(); + thread = 0; + + delete connection; + connection = 0; + + // Move it back to the parent thread so that we can potentially restart it on a new thread. + q_func()->moveToThread(parentThread); +} + QList QQmlDebugServer::services() const { Q_D(const QQmlDebugServer); @@ -680,6 +730,23 @@ void QQmlDebugServer::sendMessages(QQmlDebugService *service, Q_ARG(QList, prefixedMessages)); } +bool QQmlDebugServer::enable(int portFrom, int portTo, bool block, const QString &hostAddress) +{ + QQmlDebugServerInstanceWrapper *wrapper = debugServerInstance(); + if (!wrapper) + return false; + QQmlDebugServerPrivate *d = wrapper->m_instance.d_func(); + if (d->thread) + return false; + if (!d->start(portFrom, portTo, block, hostAddress, QLatin1String("qmldbg_tcp"))) + return false; + while (!wrapper->m_instance.hasConnection()) { + if (!wrapper->m_instance.hasThread()) + return false; + } + return true; +} + void QQmlDebugServer::wakeEngine(QQmlEngine *engine) { // to be executed in debugger thread diff --git a/src/qml/debugger/qqmldebugserver_p.h b/src/qml/debugger/qqmldebugserver_p.h index 4ea71fb769..ede7f68a09 100644 --- a/src/qml/debugger/qqmldebugserver_p.h +++ b/src/qml/debugger/qqmldebugserver_p.h @@ -59,10 +59,11 @@ class Q_QML_PRIVATE_EXPORT QQmlDebugServer : public QObject Q_DECLARE_PRIVATE(QQmlDebugServer) Q_DISABLE_COPY(QQmlDebugServer) public: - ~QQmlDebugServer(); static QQmlDebugServer *instance(); + bool hasThread() const; + bool hasConnection() const; bool hasDebuggingClient() const; bool blockingMode() const; @@ -78,6 +79,7 @@ public: void receiveMessage(const QByteArray &message); void sendMessages(QQmlDebugService *service, const QList &messages); + static bool enable(int portFrom, int portTo, bool block, const QString &hostAddress); private slots: void wakeEngine(QQmlEngine *engine); @@ -91,6 +93,7 @@ private: Q_PRIVATE_SLOT(d_func(), void _q_changeServiceState(const QString &serviceName, QQmlDebugService::State state)) Q_PRIVATE_SLOT(d_func(), void _q_sendMessages(QList)) + Q_PRIVATE_SLOT(d_func(), void _q_removeThread()) public: static int s_dataStreamVersion; diff --git a/src/qml/debugger/qqmldebugserverconnection_p.h b/src/qml/debugger/qqmldebugserverconnection_p.h index 18a01544f3..fecb2d8c26 100644 --- a/src/qml/debugger/qqmldebugserverconnection_p.h +++ b/src/qml/debugger/qqmldebugserverconnection_p.h @@ -59,10 +59,11 @@ public: virtual ~QQmlDebugServerConnection() {} virtual void setServer(QQmlDebugServer *server) = 0; - virtual void setPortRange(int portFrom, int portTo, bool bock, const QString &hostaddress) = 0; + virtual bool setPortRange(int portFrom, int portTo, bool bock, const QString &hostaddress) = 0; virtual bool isConnected() const = 0; virtual void send(const QList &messages) = 0; virtual void disconnect() = 0; + virtual void waitForConnection() = 0; virtual bool waitForMessage() = 0; }; diff --git a/src/qml/qml/qqmlengine.cpp b/src/qml/qml/qqmlengine.cpp index e6239f0081..2a27c543fd 100644 --- a/src/qml/qml/qqmlengine.cpp +++ b/src/qml/qml/qqmlengine.cpp @@ -1448,6 +1448,26 @@ QQmlDebuggingEnabler::QQmlDebuggingEnabler(bool printWarning) #endif } +/*! + * Enables debugging for QML engines created after calling this function. The debug server will + * listen on \a port at \a hostName and block the QML engine until it receives a connection if + * \a block is true. If \a block is not specified it won't block and if \a hostName isn't specified + * it will listen on all available interfaces. You can only start one debug server at a time. A + * debug server may have already been started if the -qmljsdebugger= command line argument was + * given. This method returns \c true if a new debug server was successfully started, or \c false + * otherwise. + */ +bool QQmlDebuggingEnabler::startTcpDebugServer(int port, bool block, const QString &hostName) +{ +#ifndef QQML_NO_DEBUG_PROTOCOL + return QQmlDebugServer::enable(port, port, block, hostName); +#else + Q_UNUSED(port); + Q_UNUSED(block); + Q_UNUSED(hostName); + return false; +#endif +} class QQmlDataExtended { public: -- cgit v1.2.3