diff options
| author | Sami Shalayel <sami.shalayel@qt.io> | 2026-09-16 11:16:16 +0200 |
|---|---|---|
| committer | Sami Shalayel <sami.shalayel@qt.io> | 2026-09-21 07:01:15 +0000 |
| commit | 64e7ce82fc24746590d8b16c3cd006c20bb130fb (patch) | |
| tree | f2eb101708e836b034b22b9c92df5707fc52f363 | |
| parent | 4b64bdec73fdc9965dcbfd2f448fd711c03cb6ec (diff) | |
qmlls: get rid of requestsInProcess, don't wait when shutting down
Remove requestsInProcess-machinery, it can't keep track of finished
server-to-client requests and freezes qmlls on shutdown.
The LSP protocol specifies:
> Clients must not send any requests or notifications other than exit to
a server to which they have sent a shutdown request. Clients should also
wait with sending the exit notification until they have received a
response from the shutdown request.
And I believe that it is silly to make qmlls wait for all requests to
complete on reception of the shutdown request before shutting down. The
client is probably not interested in the requests it just made to qmlls
so drop all running requests. Therefore, also remove the "WaitPending"
state (which is now unused) and shutdown qmlls without waiting for any
running requests.
This way, a server-to-client request like WorkspaceSemanticTokensRefresh
(or any other request) can't freeze qmlls on shutdown anymore.
We still instruct the QQmlCodeModel threads to finish and wait for them,
and the QProcessScheduler kills a currently running CMake QProcess and
wait for it to finish on destruction.
Pick-to: 6.12
Fixes: QTBUG-150326
Change-Id: Iacfc5467eef23f140a4fd94c54f0f605a58249cc
Reviewed-by: Olivier De Cannière <olivier.decanniere@qt.io>
| -rw-r--r-- | src/qmlls/qlanguageserver.cpp | 31 | ||||
| -rw-r--r-- | src/qmlls/qlanguageserver_p.h | 1 | ||||
| -rw-r--r-- | src/qmlls/qlanguageserver_p_p.h | 1 | ||||
| -rw-r--r-- | tests/auto/qmlls/CMakeLists.txt | 1 | ||||
| -rw-r--r-- | tests/auto/qmlls/qqmllanguageserver/CMakeLists.txt | 18 | ||||
| -rw-r--r-- | tests/auto/qmlls/qqmllanguageserver/tst_qqmllanguageserver.cpp | 67 | ||||
| -rw-r--r-- | tests/auto/qmlls/qqmllanguageserver/tst_qqmllanguageserver.h | 20 |
7 files changed, 110 insertions, 29 deletions
diff --git a/src/qmlls/qlanguageserver.cpp b/src/qmlls/qlanguageserver.cpp index e6812e6f3b..6525044b80 100644 --- a/src/qmlls/qlanguageserver.cpp +++ b/src/qmlls/qlanguageserver.cpp @@ -109,13 +109,9 @@ void QLanguageServer::registerMethods(QJsonRpc::TypedRpc &typedRpc) } } } - if (!sendErrorResponse) { - if (id.isString() || id.isDouble()) { - QMutexLocker l(&d->mutex); - d->requestsInProgress.insert(id, QRequestInProgress {}); - } + if (!sendErrorResponse) return QJsonRpcProtocol::Processing::Continue; - } + if (rState == RunStatus::NotInitialized) responder(QJsonRpcProtocol::MessageHandler::error( int(QLspSpecification::ErrorCodes::ServerNotInitialized), @@ -129,21 +125,6 @@ void QLanguageServer::registerMethods(QJsonRpc::TypedRpc &typedRpc) int(rState)))); return QJsonRpcProtocol::Processing::Stop; }); - typedRpc.installOnCloseAction([this](QJsonRpc::TypedResponse::Status, - const QJsonRpc::IdType &id, QJsonRpc::TypedRpc &) { - Q_D(QLanguageServer); - QJsonValue idValue = QTypedJson::toJsonValue(id); - bool lastReq; - { - QMutexLocker l(&d->mutex); - d->requestsInProgress.remove(idValue); - lastReq = d->runStatus == RunStatus::WaitPending && d->requestsInProgress.size() <= 1; - if (lastReq) - d->runStatus = RunStatus::Stopping; - } - if (lastReq) - executeShutdown(); - }); } const QLspSpecification::InitializeParams &QLanguageServer::clientInfo() const @@ -207,12 +188,8 @@ void QLanguageServer::registerHandlers(QLanguageServerProtocol *protocol) rStatus = d->runStatus; if (rStatus == RunStatus::Initialized) { d->shutdownResponse = std::move(response); - if (d->requestsInProgress.size() <= 1) { - d->runStatus = RunStatus::Stopping; - shouldExecuteShutdown = true; - } else { - d->runStatus = RunStatus::WaitPending; - } + d->runStatus = RunStatus::Stopping; + shouldExecuteShutdown = true; } } if (rStatus != RunStatus::Initialized) diff --git a/src/qmlls/qlanguageserver_p.h b/src/qmlls/qlanguageserver_p.h index 2e50506927..48a48f61e6 100644 --- a/src/qmlls/qlanguageserver_p.h +++ b/src/qmlls/qlanguageserver_p.h @@ -44,7 +44,6 @@ public: enum class RunStatus { NotInitialized, Initialized, // normal state of execution - WaitPending, Stopping, WaitingForExit, Stopped diff --git a/src/qmlls/qlanguageserver_p_p.h b/src/qmlls/qlanguageserver_p_p.h index 55a312ecf0..55c61e8889 100644 --- a/src/qmlls/qlanguageserver_p_p.h +++ b/src/qmlls/qlanguageserver_p_p.h @@ -47,7 +47,6 @@ public: mutable QMutex mutex; // mutex gated, monotonically increasing QLanguageServer::RunStatus runStatus = QLanguageServer::RunStatus::NotInitialized; - QHash<QJsonValue, QRequestInProgress> requestsInProgress; // mutex gated QLspSpecification::InitializeParams clientInfo; // immutable after runStatus > Initialized QLspSpecification::InitializeResult serverInfo; // immutable after runStatus > Initialized QLspSpecification::Responses::ShutdownResponseType shutdownResponse; diff --git a/tests/auto/qmlls/CMakeLists.txt b/tests/auto/qmlls/CMakeLists.txt index daf7d64cd4..92e70aae0c 100644 --- a/tests/auto/qmlls/CMakeLists.txt +++ b/tests/auto/qmlls/CMakeLists.txt @@ -8,5 +8,6 @@ if (TARGET Qt::QmlLSPrivate) add_subdirectory(qmlls) add_subdirectory(modules) add_subdirectory(cli) + add_subdirectory(qqmllanguageserver) endif() diff --git a/tests/auto/qmlls/qqmllanguageserver/CMakeLists.txt b/tests/auto/qmlls/qqmllanguageserver/CMakeLists.txt new file mode 100644 index 0000000000..7aecc9005c --- /dev/null +++ b/tests/auto/qmlls/qqmllanguageserver/CMakeLists.txt @@ -0,0 +1,18 @@ +# Copyright (C) 2026 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +if(NOT QT_BUILD_STANDALONE_TESTS AND NOT QT_BUILDING_QT) + cmake_minimum_required(VERSION 3.16) + project(tst_qqmllanguageserver LANGUAGES CXX) + find_package(Qt6BuildInternals REQUIRED COMPONENTS STANDALONE_TEST) +endif() + +qt_internal_add_test(tst_qqmllanguageserver + SOURCES + tst_qqmllanguageserver.cpp tst_qqmllanguageserver.h + LIBRARIES + Qt::CorePrivate + Qt::LanguageServerPrivate + Qt::Test + Qt::QmlLSPrivate +) diff --git a/tests/auto/qmlls/qqmllanguageserver/tst_qqmllanguageserver.cpp b/tests/auto/qmlls/qqmllanguageserver/tst_qqmllanguageserver.cpp new file mode 100644 index 0000000000..0847a57ddb --- /dev/null +++ b/tests/auto/qmlls/qqmllanguageserver/tst_qqmllanguageserver.cpp @@ -0,0 +1,67 @@ +// Copyright (C) 2026 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +#include <private/qlanguageserver_p.h> +#include <private/qqmllanguageserver_p.h> +#include <QtTest/qtest.h> + +#include "tst_qqmllanguageserver.h" + +using namespace Qt::StringLiterals; +using namespace QLspSpecification; +using namespace QmlLsp; + +tst_qqmllanguageserver::tst_qqmllanguageserver() { } + +struct ClientAndServer +{ + std::unique_ptr<QLanguageServerProtocol> client; + std::unique_ptr<QQmlLanguageServer> server; + + ClientAndServer() + { + client = std::make_unique<QLanguageServerProtocol>( + [this](const QByteArray &data) { server->receiveData(data, true); }); + server = std::make_unique<QQmlLanguageServer>( + [this](const QByteArray &data) { client->receiveData(data); }); + } + + static ClientAndServer createAndInitialize() + { + ClientAndServer result; + + bool initializedOk = false; + InitializeParams initializeParams; + initializeParams.capabilities.window.emplace().workDoneProgress = true; + result.client->requestInitialize( + initializeParams, + [&initializedOk](const InitializeResult &) { initializedOk = true; }); + [&initializedOk] { QTRY_VERIFY_WITH_TIMEOUT(initializedOk, 3000); }(); + result.client->notifyInitialized({ }); + + return result; + } +}; + +void tst_qqmllanguageserver::noFreezeAfterRequestToClient() +{ + auto [client, server] = ClientAndServer::createAndInitialize(); + + client->registerWorkspaceSemanticTokensRefreshRequestHandler( + [](const QByteArray, const std::nullptr_t &, auto &&response) { + response.sendResponse({ }); + }); + + bool ok = false; + server->protocol()->requestWorkspaceSemanticTokensRefresh({ }, [&ok]() { ok = true; }); + QTRY_VERIFY_WITH_TIMEOUT(ok, 5000); + + ok = false; + client->requestShutdown({ }, [&ok, &c = client]() { + c->notifyExit({ }); + ok = true; + }); + QTRY_VERIFY_WITH_TIMEOUT(ok, 5000); +} + +QTEST_MAIN(tst_qqmllanguageserver) diff --git a/tests/auto/qmlls/qqmllanguageserver/tst_qqmllanguageserver.h b/tests/auto/qmlls/qqmllanguageserver/tst_qqmllanguageserver.h new file mode 100644 index 0000000000..b555b3d77c --- /dev/null +++ b/tests/auto/qmlls/qqmllanguageserver/tst_qqmllanguageserver.h @@ -0,0 +1,20 @@ +// Copyright (C) 2026 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +#ifndef TST_QQMLLANGUAGESERVER_H +#define TST_QQMLLANGUAGESERVER_H + +#include <QtCore/qobject.h> +#include <QtTest/qtest.h> + +class tst_qqmllanguageserver : public QObject +{ + Q_OBJECT +public: + tst_qqmllanguageserver(); + +private slots: + void noFreezeAfterRequestToClient(); +}; + +#endif // TST_QQMLLANGUAGESERVER_H |
