aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorSami Shalayel <sami.shalayel@qt.io>2026-09-16 11:16:16 +0200
committerSami Shalayel <sami.shalayel@qt.io>2026-09-21 07:01:15 +0000
commit64e7ce82fc24746590d8b16c3cd006c20bb130fb (patch)
treef2eb101708e836b034b22b9c92df5707fc52f363
parent4b64bdec73fdc9965dcbfd2f448fd711c03cb6ec (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.cpp31
-rw-r--r--src/qmlls/qlanguageserver_p.h1
-rw-r--r--src/qmlls/qlanguageserver_p_p.h1
-rw-r--r--tests/auto/qmlls/CMakeLists.txt1
-rw-r--r--tests/auto/qmlls/qqmllanguageserver/CMakeLists.txt18
-rw-r--r--tests/auto/qmlls/qqmllanguageserver/tst_qqmllanguageserver.cpp67
-rw-r--r--tests/auto/qmlls/qqmllanguageserver/tst_qqmllanguageserver.h20
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