🐛 fix(core): enforce strict LSP lifecycle

This commit is contained in:
csh
2026-07-13 21:35:40 +08:00
parent 5edd4b3637
commit 06a0da5124
21 changed files with 555 additions and 664 deletions
+11 -4
View File
@@ -6,6 +6,7 @@ import spdlog;
import std; import std;
import lsp.core.server; import lsp.core.server;
import lsp.provider.manifest;
import lsp.utils.args_parser; import lsp.utils.args_parser;
namespace namespace
@@ -46,11 +47,17 @@ export int Run(int argc, char* argv[])
return 1; return 1;
} }
int exit_code = 1;
try try
{ {
spdlog::info("TSL-LSP server starting..."); spdlog::info("TSL-LSP server starting...");
lsp::core::LspServer server(config.thread_count, config.interpreter_path); lsp::core::LspServer server(
server.Run(); std::cin,
std::cout,
lsp::provider::RegisterAllProviders,
config.thread_count,
config.interpreter_path);
exit_code = server.Run();
} }
catch (const std::exception& error) catch (const std::exception& error)
{ {
@@ -67,7 +74,7 @@ export int Run(int argc, char* argv[])
return 1; return 1;
} }
spdlog::info("TSL-LSP server stopped normally"); spdlog::info("TSL-LSP server stopped with exit code {}", exit_code);
spdlog::shutdown(); spdlog::shutdown();
return 0; return exit_code;
} }
+97 -139
View File
@@ -1,29 +1,19 @@
module; module;
export module lsp.core.dispatcher; export module lsp.core.dispatcher;
import spdlog;
import spdlog;
import std; import std;
import lsp.protocol.types;
import lsp.codec.facade; import lsp.codec.facade;
import lsp.scheduler.async_executor;
import lsp.manager.manager_hub; import lsp.manager.manager_hub;
import lsp.protocol.types;
import lsp.scheduler.async_executor;
namespace transform = lsp::codec; namespace transform = lsp::codec;
export namespace lsp::core export namespace lsp::core
{ {
enum class ServerLifecycleEvent
{
kInitializing,
kInitialized,
kInitializeFailed,
kShuttingDown,
kShutdown
};
using LifecycleCallback = std::function<void(ServerLifecycleEvent)>;
class IProvider class IProvider
{ {
public: public:
@@ -32,61 +22,65 @@ export namespace lsp::core
virtual std::string GetProviderName() const = 0; virtual std::string GetProviderName() const = 0;
}; };
class ExecutionContext;
class IRequestProvider : public IProvider class IRequestProvider : public IProvider
{ {
public: public:
virtual ~IRequestProvider() = default; virtual ~IRequestProvider() = default;
virtual std::string ProvideResponse(const protocol::RequestMessage& request, class ExecutionContext& execution_context) = 0; virtual std::string ProvideResponse(const protocol::RequestMessage& request,
ExecutionContext& execution_context) = 0;
}; };
class INotificationProvider : public IProvider class INotificationProvider : public IProvider
{ {
public: public:
virtual ~INotificationProvider() = default; virtual ~INotificationProvider() = default;
virtual void HandleNotification(const protocol::NotificationMessage& notification, class ExecutionContext& execution_context) = 0; virtual void HandleNotification(const protocol::NotificationMessage& notification,
ExecutionContext& execution_context) = 0;
}; };
class ExecutionContext class ExecutionContext
{ {
public: public:
ExecutionContext(LifecycleCallback lifecycle_callback, ExecutionContext(scheduler::async_executor::AsyncExecutor& scheduler,
scheduler::async_executor::AsyncExecutor& scheduler, manager::ManagerHub& manager_hub)
manager::ManagerHub& manager_hub) : : async_executor_(scheduler), manager_hub_(manager_hub)
lifecycle_callback_(lifecycle_callback),
async_executor_(scheduler),
manager_hub_(manager_hub) {}
scheduler::async_executor::AsyncExecutor& GetScheduler() const { return async_executor_; }
manager::ManagerHub& GetManagerHub() const { return manager_hub_; }
void TriggerLifecycleEvent(ServerLifecycleEvent event) const
{ {
if (lifecycle_callback_) }
lifecycle_callback_(event);
scheduler::async_executor::AsyncExecutor& GetScheduler() const
{
return async_executor_;
}
manager::ManagerHub& GetManagerHub() const
{
return manager_hub_;
} }
private: private:
LifecycleCallback lifecycle_callback_;
scheduler::async_executor::AsyncExecutor& async_executor_; scheduler::async_executor::AsyncExecutor& async_executor_;
manager::ManagerHub& manager_hub_; manager::ManagerHub& manager_hub_;
}; };
std::string BuildErrorResponseMessage(const protocol::RequestMessage& request, protocol::ErrorCodes code, const std::string& message); std::string BuildErrorResponseMessage(std::optional<protocol::RequestId> id,
protocol::ErrorCodes code,
std::string_view message);
std::string BuildErrorResponseMessage(const protocol::RequestMessage& request,
protocol::ErrorCodes code,
std::string_view message);
class RequestDispatcher class RequestDispatcher
{ {
public: public:
RequestDispatcher(); RequestDispatcher(scheduler::async_executor::AsyncExecutor& scheduler,
manager::ManagerHub& manager_hub);
~RequestDispatcher() = default; ~RequestDispatcher() = default;
void SetRequestScheduler(scheduler::async_executor::AsyncExecutor* scheduler);
void SetManagerHub(manager::ManagerHub* manager_hub);
void RegisterRequestProvider(std::shared_ptr<IRequestProvider> provider); void RegisterRequestProvider(std::shared_ptr<IRequestProvider> provider);
void RegisterNotificationProvider(std::shared_ptr<INotificationProvider> provider); void RegisterNotificationProvider(std::shared_ptr<INotificationProvider> provider);
void RegisterLifecycleCallback(LifecycleCallback callback);
std::string Dispatch(const protocol::RequestMessage& request); std::string Dispatch(const protocol::RequestMessage& request);
void Dispatch(const protocol::NotificationMessage& notification); void Dispatch(const protocol::NotificationMessage& notification);
@@ -97,99 +91,71 @@ export namespace lsp::core
std::vector<std::string> GetAllSupportedMethods() const; std::vector<std::string> GetAllSupportedMethods() const;
private: private:
void NotifyAllLifecycleListeners(ServerLifecycleEvent event);
std::string HandleUnknownRequest(const protocol::RequestMessage& request); std::string HandleUnknownRequest(const protocol::RequestMessage& request);
void HandleUnknownNotification(const protocol::NotificationMessage& notification); void HandleUnknownNotification(const protocol::NotificationMessage& notification);
private:
mutable std::shared_mutex providers_mutex_; mutable std::shared_mutex providers_mutex_;
std::unordered_map<std::string, std::shared_ptr<IRequestProvider>> providers_; std::unordered_map<std::string, std::shared_ptr<IRequestProvider>> providers_;
mutable std::shared_mutex notification_providers_mutex_; mutable std::shared_mutex notification_providers_mutex_;
std::unordered_map<std::string, std::shared_ptr<INotificationProvider>> notification_providers_; std::unordered_map<std::string, std::shared_ptr<INotificationProvider>>
notification_providers_;
std::mutex callbacks_mutex_; scheduler::async_executor::AsyncExecutor& async_executor_;
std::vector<LifecycleCallback> lifecycle_callbacks_; manager::ManagerHub& manager_hub_;
LifecycleCallback context_lifecycle_callback_;
scheduler::async_executor::AsyncExecutor* async_executor_ = nullptr;
manager::ManagerHub* manager_hub_ = nullptr;
}; };
} }
namespace lsp::core namespace lsp::core
{ {
RequestDispatcher::RequestDispatcher() RequestDispatcher::RequestDispatcher(
scheduler::async_executor::AsyncExecutor& scheduler,
manager::ManagerHub& manager_hub)
: async_executor_(scheduler), manager_hub_(manager_hub)
{ {
context_lifecycle_callback_ = [this](ServerLifecycleEvent event) {
NotifyAllLifecycleListeners(event);
};
} }
void RequestDispatcher::SetRequestScheduler(scheduler::async_executor::AsyncExecutor* scheduler) void RequestDispatcher::RegisterRequestProvider(
std::shared_ptr<IRequestProvider> provider)
{ {
async_executor_ = scheduler; std::unique_lock lock(providers_mutex_);
spdlog::debug("Request scheduler set");
}
void RequestDispatcher::SetManagerHub(manager::ManagerHub* manager_hub)
{
manager_hub_ = manager_hub;
spdlog::debug("Manager hub bound to dispatcher");
}
void RequestDispatcher::RegisterRequestProvider(std::shared_ptr<IRequestProvider> provider)
{
std::unique_lock<std::shared_mutex> lock(providers_mutex_);
std::string method = provider->GetMethod(); std::string method = provider->GetMethod();
providers_[method] = provider; providers_[method] = std::move(provider);
} }
void RequestDispatcher::RegisterNotificationProvider(std::shared_ptr<INotificationProvider> provider) void RequestDispatcher::RegisterNotificationProvider(
std::shared_ptr<INotificationProvider> provider)
{ {
std::unique_lock<std::shared_mutex> lock(notification_providers_mutex_); std::unique_lock lock(notification_providers_mutex_);
std::string method = provider->GetMethod(); std::string method = provider->GetMethod();
notification_providers_[method] = provider; notification_providers_[method] = std::move(provider);
}
void RequestDispatcher::RegisterLifecycleCallback(LifecycleCallback callback)
{
std::lock_guard<std::mutex> lock(callbacks_mutex_);
lifecycle_callbacks_.push_back(std::move(callback));
} }
std::string RequestDispatcher::Dispatch(const protocol::RequestMessage& request) std::string RequestDispatcher::Dispatch(const protocol::RequestMessage& request)
{ {
std::shared_ptr<IRequestProvider> provider = nullptr; std::shared_ptr<IRequestProvider> provider;
{ {
std::shared_lock<std::shared_mutex> lock(providers_mutex_); std::shared_lock lock(providers_mutex_);
auto it = providers_.find(request.method); auto provider_it = providers_.find(request.method);
if (it != providers_.end()) if (provider_it != providers_.end())
provider = it->second; provider = provider_it->second;
} }
if (!provider) if (!provider)
return HandleUnknownRequest(request); return HandleUnknownRequest(request);
if (!async_executor_ || !manager_hub_) ExecutionContext context(async_executor_, manager_hub_);
{
spdlog::error("RequestDispatcher dependencies not set");
return "{}";
}
ExecutionContext context(context_lifecycle_callback_, *async_executor_, *manager_hub_);
return provider->ProvideResponse(request, context); return provider->ProvideResponse(request, context);
} }
void RequestDispatcher::Dispatch(const protocol::NotificationMessage& notification) void RequestDispatcher::Dispatch(const protocol::NotificationMessage& notification)
{ {
std::shared_ptr<INotificationProvider> provider = nullptr; std::shared_ptr<INotificationProvider> provider;
{ {
std::shared_lock<std::shared_mutex> lock(notification_providers_mutex_); std::shared_lock lock(notification_providers_mutex_);
auto it = notification_providers_.find(notification.method); auto provider_it = notification_providers_.find(notification.method);
if (it != notification_providers_.end()) if (provider_it != notification_providers_.end())
provider = it->second; provider = provider_it->second;
} }
if (!provider) if (!provider)
@@ -198,45 +164,45 @@ namespace lsp::core
return; return;
} }
if (!async_executor_ || !manager_hub_) ExecutionContext context(async_executor_, manager_hub_);
{
spdlog::error("NotificationDispatcher dependencies not set");
return;
}
ExecutionContext context(context_lifecycle_callback_, *async_executor_, *manager_hub_);
provider->HandleNotification(notification, context); provider->HandleNotification(notification, context);
} }
bool RequestDispatcher::SupportsRequest(const std::string& method) const bool RequestDispatcher::SupportsRequest(const std::string& method) const
{ {
std::shared_lock<std::shared_mutex> lock(providers_mutex_); std::shared_lock lock(providers_mutex_);
return providers_.contains(method); return providers_.contains(method);
} }
bool RequestDispatcher::SupportsNotification(const std::string& method) const bool RequestDispatcher::SupportsNotification(const std::string& method) const
{ {
std::shared_lock<std::shared_mutex> lock(notification_providers_mutex_); std::shared_lock lock(notification_providers_mutex_);
return notification_providers_.contains(method); return notification_providers_.contains(method);
} }
std::vector<std::string> RequestDispatcher::GetSupportedRequests() const std::vector<std::string> RequestDispatcher::GetSupportedRequests() const
{ {
std::vector<std::string> methods; std::vector<std::string> methods;
std::shared_lock<std::shared_mutex> lock(providers_mutex_); std::shared_lock lock(providers_mutex_);
methods.reserve(providers_.size()); methods.reserve(providers_.size());
for (const auto& [method, _] : providers_) for (const auto& [method, provider] : providers_)
{
static_cast<void>(provider);
methods.push_back(method); methods.push_back(method);
}
return methods; return methods;
} }
std::vector<std::string> RequestDispatcher::GetSupportedNotifications() const std::vector<std::string> RequestDispatcher::GetSupportedNotifications() const
{ {
std::vector<std::string> methods; std::vector<std::string> methods;
std::shared_lock<std::shared_mutex> lock(notification_providers_mutex_); std::shared_lock lock(notification_providers_mutex_);
methods.reserve(notification_providers_.size()); methods.reserve(notification_providers_.size());
for (const auto& [method, _] : notification_providers_) for (const auto& [method, provider] : notification_providers_)
{
static_cast<void>(provider);
methods.push_back(method); methods.push_back(method);
}
return methods; return methods;
} }
@@ -248,48 +214,40 @@ namespace lsp::core
return methods; return methods;
} }
void RequestDispatcher::NotifyAllLifecycleListeners(ServerLifecycleEvent event) std::string RequestDispatcher::HandleUnknownRequest(
{ const protocol::RequestMessage& request)
std::lock_guard<std::mutex> lock(callbacks_mutex_);
for (const auto& callback : lifecycle_callbacks_)
{
if (callback)
callback(event);
}
}
std::string RequestDispatcher::HandleUnknownRequest(const protocol::RequestMessage& request)
{ {
spdlog::warn("No request provider registered for method: {}", request.method); spdlog::warn("No request provider registered for method: {}", request.method);
return BuildErrorResponseMessage(
request, protocol::ErrorCodes::MethodNotFound, "Method not supported");
}
void RequestDispatcher::HandleUnknownNotification(
const protocol::NotificationMessage& notification)
{
spdlog::warn("No notification provider registered for method: {}",
notification.method);
}
std::string BuildErrorResponseMessage(std::optional<protocol::RequestId> id,
protocol::ErrorCodes code,
std::string_view message)
{
protocol::ResponseMessage response; protocol::ResponseMessage response;
response.id = request.id; response.id = std::move(id);
response.error = protocol::ResponseError{ response.error = protocol::ResponseError{
.jsonrpc = "2.0", .jsonrpc = "2.0",
.code = static_cast<protocol::integer>(protocol::ErrorCodes::MethodNotFound), .code = static_cast<protocol::integer>(code),
.message = "Method not supported", .message = std::string(message),
.data = std::nullopt .data = std::nullopt,
}; };
auto json = transform::Serialize(response); return transform::Serialize(response).value();
return json.value_or("{}");
} }
void RequestDispatcher::HandleUnknownNotification(const protocol::NotificationMessage& notification) std::string BuildErrorResponseMessage(const protocol::RequestMessage& request,
protocol::ErrorCodes code,
std::string_view message)
{ {
spdlog::warn("No notification provider registered for method: {}", notification.method); return BuildErrorResponseMessage(request.id, code, message);
}
std::string BuildErrorResponseMessage(const protocol::RequestMessage& request, protocol::ErrorCodes code, const std::string& message)
{
protocol::ResponseMessage response;
response.id = request.id;
protocol::ResponseError error;
error.code = static_cast<protocol::integer>(code);
error.message = message;
response.error = error;
auto json = transform::Serialize(response);
if (json.has_value())
return json.value();
spdlog::error("Failed to serialize error response.");
return R"({"jsonrpc":"2.0","id":null,"error":{"code":-32603,"message":"Failed to serialize error response"}})";
} }
} }
+186 -241
View File
@@ -1,65 +1,59 @@
module; module;
export module lsp.core.server; export module lsp.core.server;
import spdlog; import spdlog;
import std;
import tree_sitter; import tree_sitter;
import std;
import lsp.bridge.win32_stdio; import lsp.bridge.win32_stdio;
import lsp.core.dispatcher;
import lsp.protocol;
import lsp.codec.facade; import lsp.codec.facade;
import lsp.core.dispatcher;
import lsp.language.ast; import lsp.language.ast;
import lsp.manager.manager_hub;
import lsp.manager.bootstrap; import lsp.manager.bootstrap;
import lsp.manager.events; import lsp.manager.events;
import lsp.scheduler.async_executor; import lsp.manager.manager_hub;
import lsp.provider.base.interface; import lsp.protocol;
import lsp.provider.manifest; import lsp.provider.manifest;
import lsp.scheduler.async_executor;
namespace transform = lsp::codec; namespace transform = lsp::codec;
export namespace lsp::core export namespace lsp::core
{ {
using ProviderRegistrar = std::function<void(RequestDispatcher&)>;
enum class ServerState
{
kUninitialized,
kRunning,
kShutdownRequested,
kExiting,
};
class LspServer class LspServer
{ {
public: public:
explicit LspServer(std::size_t concurrency = std::thread::hardware_concurrency(), LspServer(std::istream& input,
std::ostream& output,
ProviderRegistrar registrar,
std::size_t concurrency = std::thread::hardware_concurrency(),
std::string interpreter_path = ""); std::string interpreter_path = "");
~LspServer(); ~LspServer() = default;
void Run();
int Run();
private: private:
// 读取LSP消息
std::optional<std::string> ReadMessage(); std::optional<std::string> ReadMessage();
// 处理LSP请求 - 返回序列化的响应或空字符串(对于通知)
void HandleMessage(const std::string& raw_message); void HandleMessage(const std::string& raw_message);
// 发送LSP消息(响应/通知)
void SendMessage(const std::string& message); void SendMessage(const std::string& message);
// 处理不同类型的消息
void HandleRequest(const protocol::RequestMessage& request); void HandleRequest(const protocol::RequestMessage& request);
void HandleNotification(const protocol::NotificationMessage& notification); void HandleNotification(const protocol::NotificationMessage& notification);
void HandleResponse(const protocol::ResponseMessage& response); void HandleResponse(const protocol::ResponseMessage& response);
// 生命周期事件处理
void OnLifecycleEvent(provider::ServerLifecycleEvent event);
// 判断是否需要同步处理
bool RequiresSyncProcessing(const std::string& method) const;
// 检查是否可以处理请求
bool CanProcessRequest(const std::string& method) const;
// 处理取消请求
void HandleCancelRequest(const protocol::NotificationMessage& notification);
private:
void InitializeManagerHub(); void InitializeManagerHub();
void RegisterProviders(); void RegisterProviders(ProviderRegistrar registrar);
void RegisterDiagnosticsPublisher(); void RegisterDiagnosticsPublisher();
void PublishDiagnostics(const protocol::DocumentUri& uri, void PublishDiagnostics(const protocol::DocumentUri& uri,
@@ -68,32 +62,42 @@ export namespace lsp::core
const protocol::string& content); const protocol::string& content);
void ClearDiagnostics(const protocol::DocumentUri& uri); void ClearDiagnostics(const protocol::DocumentUri& uri);
// 错误处理 void SendError(const protocol::RequestMessage& request,
void SendError(const protocol::RequestMessage& request, protocol::ErrorCodes code, const std::string& message); protocol::ErrorCodes code,
void SendStateError(const protocol::RequestMessage& request); std::string_view message);
private: std::istream& input_;
RequestDispatcher dispatcher_; std::ostream& output_;
manager::ManagerHub manager_hub_; manager::ManagerHub manager_hub_;
scheduler::async_executor::AsyncExecutor async_executor_; scheduler::async_executor::AsyncExecutor async_executor_;
RequestDispatcher dispatcher_;
std::string interpreter_path_; std::string interpreter_path_;
std::atomic<bool> is_initialized_ = false; ServerState state_ = ServerState::kUninitialized;
std::atomic<bool> is_shutting_down_ = false; int exit_code_ = 1;
std::atomic<bool> fatal_io_error_ = false;
std::mutex output_mutex_; std::mutex output_mutex_;
}; };
} }
namespace lsp::core namespace lsp::core
{ {
LspServer::LspServer(std::size_t concurrency, std::string interpreter_path) : manager_hub_(), LspServer::LspServer(std::istream& input,
std::ostream& output,
ProviderRegistrar registrar,
std::size_t concurrency,
std::string interpreter_path)
: input_(input),
output_(output),
manager_hub_(),
async_executor_(concurrency), async_executor_(concurrency),
dispatcher_(async_executor_, manager_hub_),
interpreter_path_(std::move(interpreter_path)) interpreter_path_(std::move(interpreter_path))
{ {
spdlog::info("Initializing LSP server with {} worker threads", concurrency); spdlog::info("Initializing LSP server with {} worker threads", concurrency);
InitializeManagerHub(); InitializeManagerHub();
RegisterProviders(); RegisterProviders(std::move(registrar));
if (provider::kEnableDiagnosticsPublisher) if (provider::kEnableDiagnosticsPublisher)
{ {
RegisterDiagnosticsPublisher(); RegisterDiagnosticsPublisher();
@@ -103,48 +107,48 @@ namespace lsp::core
spdlog::debug("Diagnostics publisher disabled (staged rollout)"); spdlog::debug("Diagnostics publisher disabled (staged rollout)");
} }
spdlog::debug("LSP server initialized with {} providers.", dispatcher_.GetAllSupportedMethods().size()); spdlog::debug("LSP server initialized with {} providers.",
dispatcher_.GetAllSupportedMethods().size());
} }
LspServer::~LspServer() int LspServer::Run()
{
is_shutting_down_ = true;
spdlog::info("LSP server shutting down...");
}
void LspServer::Run()
{ {
spdlog::info("LSP server starting main loop..."); spdlog::info("LSP server starting main loop...");
spdlog::info("Waiting for LSP messages on stdin..."); spdlog::info("Waiting for LSP messages on stdin...");
bridge::win32_stdio::SetStdioBinaryMode(); bridge::win32_stdio::SetStdioBinaryMode();
while (!is_shutting_down_) while (state_ != ServerState::kExiting)
{ {
if (fatal_io_error_)
break;
try try
{ {
std::optional<std::string> message = ReadMessage(); auto message = ReadMessage();
if (!message) if (!message)
{
if (std::cin.eof())
{ {
spdlog::info("End of input stream, exiting main loop"); spdlog::info("End of input stream, exiting main loop");
break; // EOF break;
} }
spdlog::debug("No message received, continuing...");
std::this_thread::sleep_for(std::chrono::milliseconds(5));
continue;
}
HandleMessage(*message); HandleMessage(*message);
} }
catch (const std::exception& e) catch (const std::exception& error)
{ {
spdlog::error("Error in main loop: {}", e.what()); spdlog::error("Fatal error in main loop: {}", error.what());
std::this_thread::sleep_for(std::chrono::milliseconds(5)); exit_code_ = 1;
break;
}
catch (...)
{
spdlog::error("Unknown fatal error in main loop");
exit_code_ = 1;
break;
} }
} }
spdlog::info("LSP server main loop ended"); spdlog::info("LSP server main loop ended");
return exit_code_;
} }
std::optional<std::string> LspServer::ReadMessage() std::optional<std::string> LspServer::ReadMessage()
@@ -152,56 +156,32 @@ namespace lsp::core
std::string line; std::string line;
std::size_t content_length = 0; std::size_t content_length = 0;
// 读取 LSP Header while (std::getline(input_, line))
while (std::getline(std::cin, line))
{ {
// 去掉尾部 \\r
if (!line.empty() && line.back() == '\r') if (!line.empty() && line.back() == '\r')
{
line.pop_back(); line.pop_back();
}
if (line.empty()) if (line.empty())
{ break;
break; // 空行表示 header 结束
}
if (line.rfind("Content-Length:", 0) == 0) if (line.rfind("Content-Length:", 0) != 0)
{ continue;
std::string length_str = line.substr(15); // 跳过 "Content-Length:"
std::size_t start = length_str.find_first_not_of(' '); std::string length = line.substr(std::string_view("Content-Length:").size());
if (start != std::string::npos) const auto start = length.find_first_not_of(' ');
{ if (start == std::string::npos)
length_str = length_str.substr(start);
try
{
content_length = std::stoul(length_str);
spdlog::trace("Content-Length: {}", content_length);
}
catch (const std::exception& e)
{
spdlog::error("Failed to parse Content-Length: {}", e.what());
return std::nullopt; return std::nullopt;
}
} content_length = std::stoul(length.substr(start));
}
} }
if (content_length == 0) if (content_length == 0)
{
spdlog::debug("No Content-Length found in header");
return std::nullopt; return std::nullopt;
}
// 读取内容体
std::string body(content_length, '\0'); std::string body(content_length, '\0');
std::cin.read(&body[0], content_length); input_.read(body.data(), static_cast<std::streamsize>(content_length));
if (input_.gcount() != static_cast<std::streamsize>(content_length))
if (std::cin.gcount() != static_cast<std::streamsize>(content_length))
{
spdlog::error("Failed to read expected content length: {} bytes, got {} bytes", content_length, std::cin.gcount());
return std::nullopt; return std::nullopt;
}
spdlog::trace("Received message: {}", body); spdlog::trace("Received message: {}", body);
return body; return body;
@@ -216,11 +196,11 @@ namespace lsp::core
return; return;
} }
const auto& obj = any->Get<protocol::LSPObject>(); const auto& object = any->Get<protocol::LSPObject>();
const bool has_id = obj.find("id") != obj.end(); const bool has_id = object.contains("id");
const bool has_method = obj.find("method") != obj.end(); const bool has_method = object.contains("method");
const bool has_result = obj.find("result") != obj.end(); const bool has_result = object.contains("result");
const bool has_error = obj.find("error") != obj.end(); const bool has_error = object.contains("error");
if (has_method && has_id) if (has_method && has_id)
{ {
@@ -233,10 +213,15 @@ namespace lsp::core
if (has_method) if (has_method)
{ {
if (auto notification = transform::Deserialize<protocol::NotificationMessage>(raw_message)) if (auto notification =
transform::Deserialize<protocol::NotificationMessage>(raw_message))
{
HandleNotification(*notification); HandleNotification(*notification);
}
else else
{
spdlog::warn("Failed to parse notification message"); spdlog::warn("Failed to parse notification message");
}
return; return;
} }
@@ -254,35 +239,84 @@ namespace lsp::core
void LspServer::SendMessage(const std::string& message) void LspServer::SendMessage(const std::string& message)
{ {
if (message.empty()) std::lock_guard lock(output_mutex_);
return; output_ << "Content-Length: " << message.size() << "\r\n\r\n"
std::lock_guard<std::mutex> lock(output_mutex_);
std::cout << "Content-Length: " << message.size() << "\r\n\r\n"
<< message << std::flush; << message << std::flush;
if (!output_)
{
fatal_io_error_ = true;
throw std::runtime_error("Failed to write LSP message");
}
} }
void LspServer::HandleRequest(const protocol::RequestMessage& request) void LspServer::HandleRequest(const protocol::RequestMessage& request)
{ {
spdlog::debug("Handling request: {}", request.method); spdlog::debug("Handling request: {}", request.method);
if (request.method == "initialize")
{
if (state_ != ServerState::kUninitialized)
{
SendError(request,
protocol::ErrorCodes::InvalidRequest,
"Server is already initialized");
return;
}
const auto response = dispatcher_.Dispatch(request);
SendMessage(response);
const auto parsed = transform::Deserialize<protocol::ResponseMessage>(response);
if (!parsed)
throw std::runtime_error("Initialize provider returned an invalid response");
if (!parsed->error)
state_ = ServerState::kRunning;
return;
}
if (request.method == "shutdown") if (request.method == "shutdown")
{ {
auto response = dispatcher_.Dispatch(request); if (state_ == ServerState::kUninitialized)
SendMessage(response);
is_shutting_down_ = true;
return;
}
// 未初始化时的特殊处理
if (!is_initialized_ && request.method != "initialize")
{ {
SendStateError(request); SendError(request,
protocol::ErrorCodes::ServerNotInitialized,
"Server not initialized");
return;
}
if (state_ != ServerState::kRunning)
{
SendError(request,
protocol::ErrorCodes::InvalidRequest,
"Shutdown already requested");
return; return;
} }
auto response = dispatcher_.Dispatch(request); async_executor_.WaitAll();
SendMessage(response); manager_hub_.Shutdown();
protocol::ResponseMessage response;
response.id = request.id;
response.result = protocol::LSPAny(std::nullptr_t{});
SendMessage(transform::Serialize(response).value());
state_ = ServerState::kShutdownRequested;
return;
}
if (state_ == ServerState::kUninitialized)
{
SendError(request,
protocol::ErrorCodes::ServerNotInitialized,
"Server not initialized");
return;
}
if (state_ == ServerState::kShutdownRequested)
{
SendError(request,
protocol::ErrorCodes::InvalidRequest,
"Server is shutting down");
return;
}
SendMessage(dispatcher_.Dispatch(request));
} }
void LspServer::HandleNotification(const protocol::NotificationMessage& notification) void LspServer::HandleNotification(const protocol::NotificationMessage& notification)
@@ -291,21 +325,17 @@ namespace lsp::core
if (notification.method == "exit") if (notification.method == "exit")
{ {
is_shutting_down_ = true; const bool orderly = state_ == ServerState::kShutdownRequested;
state_ = ServerState::kExiting;
exit_code_ = orderly ? 0 : 1;
return; return;
} }
// 处理取消请求 if (state_ != ServerState::kRunning)
if (notification.method == "$/cancelRequest")
{ {
HandleCancelRequest(notification); spdlog::warn("Ignoring notification {} in server state {}",
return; notification.method,
} static_cast<int>(state_));
// 未初始化时只接受 initialized/exit
if (!is_initialized_ && notification.method != "initialized" && notification.method != "exit")
{
spdlog::warn("Server not initialized; ignoring notification: {}", notification.method);
return; return;
} }
@@ -315,108 +345,37 @@ namespace lsp::core
void LspServer::HandleResponse(const protocol::ResponseMessage& response) void LspServer::HandleResponse(const protocol::ResponseMessage& response)
{ {
std::string id = "<no id>"; std::string id = "<no id>";
if (response.id.has_value()) if (response.id)
id = transform::debug::GetIdString(response.id.value()); id = transform::debug::GetIdString(*response.id);
spdlog::debug("Received response: {}", id); spdlog::debug("Received response: {}", id);
// 当前服务器作为 client 的场景较少,这里暂时不处理
}
void LspServer::OnLifecycleEvent(provider::ServerLifecycleEvent event)
{
switch (event)
{
case provider::ServerLifecycleEvent::kInitialized:
is_initialized_ = true;
spdlog::info("Server initialized");
break;
case provider::ServerLifecycleEvent::kShutdown:
is_shutting_down_ = true;
spdlog::info("Server shutting down");
break;
default:
break;
}
}
bool LspServer::RequiresSyncProcessing(const std::string& method) const
{
static const std::unordered_set<std::string> kSyncMethods = {
"initialize",
"shutdown",
"exit",
"$/setTrace",
};
return kSyncMethods.contains(method);
}
bool LspServer::CanProcessRequest(const std::string& method) const
{
if (is_shutting_down_)
return false;
// 初始化前,只处理 initialize 请求
if (!is_initialized_ && method != "initialize")
return false;
return true;
}
void LspServer::HandleCancelRequest(const protocol::NotificationMessage& notification)
{
if (!notification.params.has_value())
return;
protocol::CancelParams params =
transform::FromLSPAny.template operator()<protocol::CancelParams>(notification.params.value());
const std::string id_string = std::visit([](const auto& value) -> std::string {
if constexpr (std::is_same_v<std::decay_t<decltype(value)>, int>)
return std::to_string(value);
else
return value;
},
params.id);
spdlog::debug("Cancel request received for id: {}", id_string);
// TODO: 实现请求取消逻辑
} }
void LspServer::InitializeManagerHub() void LspServer::InitializeManagerHub()
{ {
manager_hub_.Initialize(); manager_hub_.Initialize();
if (!interpreter_path_.empty()) if (interpreter_path_.empty())
{ return;
std::filesystem::path base = interpreter_path_;
std::filesystem::path funcext_path = base / "funcext"; const std::filesystem::path funcext_path =
if (std::filesystem::exists(funcext_path)) std::filesystem::path(interpreter_path_) / "funcext";
if (!std::filesystem::exists(funcext_path))
{ {
spdlog::warn("Interpreter funcext path does not exist: {}",
funcext_path.string());
return;
}
manager::bootstrap::InitializeManagerHub( manager::bootstrap::InitializeManagerHub(
manager_hub_, manager_hub_, async_executor_, {funcext_path.string()});
async_executor_,
{ funcext_path.string() });
} }
else
void LspServer::RegisterProviders(ProviderRegistrar registrar)
{ {
spdlog::warn("Interpreter funcext path does not exist: {}", funcext_path.string());
}
}
}
void LspServer::RegisterProviders()
{
dispatcher_.SetRequestScheduler(&async_executor_);
dispatcher_.SetManagerHub(&manager_hub_);
dispatcher_.RegisterLifecycleCallback([this](ServerLifecycleEvent event) {
OnLifecycleEvent(event);
});
spdlog::info("Registering LSP providers..."); spdlog::info("Registering LSP providers...");
registrar(dispatcher_);
provider::RegisterAllProviders(dispatcher_); spdlog::info("Registered {} LSP providers",
dispatcher_.GetAllSupportedMethods().size());
spdlog::info("Registered {} LSP providers", dispatcher_.GetAllSupportedMethods().size());
} }
void LspServer::RegisterDiagnosticsPublisher() void LspServer::RegisterDiagnosticsPublisher()
@@ -425,12 +384,14 @@ namespace lsp::core
event_bus.Subscribe<manager::events::DocumentParsed>( event_bus.Subscribe<manager::events::DocumentParsed>(
[this](const manager::events::DocumentParsed& event) { [this](const manager::events::DocumentParsed& event) {
PublishDiagnostics(event.item.uri, event.item.version, event.tree, event.item.text); PublishDiagnostics(
event.item.uri, event.item.version, event.tree, event.item.text);
}); });
event_bus.Subscribe<manager::events::DocumentReparsed>( event_bus.Subscribe<manager::events::DocumentReparsed>(
[this](const manager::events::DocumentReparsed& event) { [this](const manager::events::DocumentReparsed& event) {
PublishDiagnostics(event.item.uri, event.item.version, event.tree, event.item.text); PublishDiagnostics(
event.item.uri, event.item.version, event.tree, event.item.text);
}); });
event_bus.Subscribe<manager::events::DocumentClosed>( event_bus.Subscribe<manager::events::DocumentClosed>(
@@ -496,7 +457,6 @@ namespace lsp::core
spdlog::warn("Failed to serialize diagnostics notification for {}", uri); spdlog::warn("Failed to serialize diagnostics notification for {}", uri);
return; return;
} }
SendMessage(*json); SendMessage(*json);
} }
@@ -517,28 +477,13 @@ namespace lsp::core
spdlog::warn("Failed to serialize diagnostics clear notification for {}", uri); spdlog::warn("Failed to serialize diagnostics clear notification for {}", uri);
return; return;
} }
SendMessage(*json); SendMessage(*json);
} }
void LspServer::SendError(const protocol::RequestMessage& request, protocol::ErrorCodes code, const std::string& message) void LspServer::SendError(const protocol::RequestMessage& request,
protocol::ErrorCodes code,
std::string_view message)
{ {
protocol::ResponseMessage response; SendMessage(BuildErrorResponseMessage(request, code, message));
response.id = request.id;
protocol::ResponseError error;
error.code = static_cast<protocol::integer>(code);
error.message = message;
response.error = error;
auto json = transform::Serialize(response);
if (json)
{
SendMessage(*json);
} }
}
void LspServer::SendStateError(const protocol::RequestMessage& request)
{
SendError(request, protocol::ErrorCodes::ServerNotInitialized, "Server not initialized");
}
} }
@@ -4,13 +4,10 @@ export module lsp.provider.base.interface;
import std; import std;
// Thin bridge to core dispatcher interfaces and aliases for backward compatibility.
export import lsp.core.dispatcher; export import lsp.core.dispatcher;
export namespace lsp::provider export namespace lsp::provider
{ {
using ServerLifecycleEvent = lsp::core::ServerLifecycleEvent;
using LifecycleCallback = lsp::core::LifecycleCallback;
using ExecutionContext = lsp::core::ExecutionContext; using ExecutionContext = lsp::core::ExecutionContext;
using IProvider = lsp::core::IProvider; using IProvider = lsp::core::IProvider;
using IRequestProvider = lsp::core::IRequestProvider; using IRequestProvider = lsp::core::IRequestProvider;
@@ -1,45 +0,0 @@
module;
export module lsp.provider.cancel_request.cancel_request;
import spdlog;
import std;
import lsp.protocol.types;
import lsp.codec.facade;
import lsp.provider.base.interface;
namespace transform = lsp::codec;
export namespace lsp::provider
{
class CancelRequest : public AutoRegisterProvider<CancelRequest, INotificationProvider>
{
public:
static constexpr std::string_view kMethod = "$/cancelRequest";
static constexpr std::string_view kProviderName = "CancelRequest";
CancelRequest() = default;
void HandleNotification(const protocol::NotificationMessage& notification, ExecutionContext& context) override;
};
}
namespace lsp::provider
{
void CancelRequest::HandleNotification(const protocol::NotificationMessage& notification, ExecutionContext& context)
{
spdlog::debug("CancelRequestProvider received {}", notification.method);
auto params = transform::FromLSPAny.template operator()<protocol::CancelParams>(notification.params.value());
std::string id_to_cancel = transform::debug::GetIdString(params.id);
spdlog::debug("Processing cancel request for ID: {}", id_to_cancel);
auto& scheduler = context.GetScheduler();
bool cancelled = scheduler.Cancel(id_to_cancel);
spdlog::debug("Cancel request {} {}", id_to_cancel, cancelled ? "succeeded" : "not found");
}
}
-47
View File
@@ -1,47 +0,0 @@
module;
export module lsp.provider.exit.exit;
import spdlog;
import std;
import lsp.provider.base.interface;
import lsp.protocol.types;
export namespace lsp::provider
{
class Exit : public AutoRegisterProvider<Exit, INotificationProvider>
{
public:
static constexpr std::string_view kMethod = "exit";
static constexpr std::string_view kProviderName = "Exit";
Exit() = default;
void HandleNotification(const protocol::NotificationMessage& notification, ExecutionContext& context) override;
};
}
namespace lsp::provider
{
void Exit::HandleNotification(const protocol::NotificationMessage& notification, ExecutionContext& context)
{
spdlog::debug("Exit notification {}", notification.method);
spdlog::info("Exit notification received");
// 触发生命周期事件
context.TriggerLifecycleEvent(ServerLifecycleEvent::kShuttingDown);
// 给一些时间完成清理
std::this_thread::sleep_for(std::chrono::milliseconds(100));
context.TriggerLifecycleEvent(ServerLifecycleEvent::kShutdown);
std::exit(0);
}
}
@@ -60,11 +60,7 @@ namespace lsp::provider
response.result = transform::ToLSPAny(BuildInitializeResult()); response.result = transform::ToLSPAny(BuildInitializeResult());
std::optional<std::string> json = transform::Serialize(response); std::optional<std::string> json = transform::Serialize(response);
if (!json.has_value()) if (!json.has_value())
{ throw std::runtime_error("Failed to serialize initialize response");
context.TriggerLifecycleEvent(ServerLifecycleEvent::kInitializeFailed);
return BuildErrorResponseMessage(request, protocol::ErrorCodes::InternalError, "Internal error");
}
context.TriggerLifecycleEvent(ServerLifecycleEvent::kInitialized);
return json.value(); return json.value();
} }
-7
View File
@@ -8,7 +8,6 @@ import lsp.provider.base.registry;
import lsp.provider.completion_item.resolve; import lsp.provider.completion_item.resolve;
import lsp.provider.initialize.initialize; import lsp.provider.initialize.initialize;
import lsp.provider.initialized.initialized; import lsp.provider.initialized.initialized;
import lsp.provider.shutdown.shutdown;
import lsp.provider.text_document.definition; import lsp.provider.text_document.definition;
import lsp.provider.text_document.did_change; import lsp.provider.text_document.did_change;
import lsp.provider.text_document.did_close; import lsp.provider.text_document.did_close;
@@ -20,13 +19,11 @@ import lsp.provider.trace.set_trace;
// Uncomment when re-enabling additional capabilities. // Uncomment when re-enabling additional capabilities.
// import lsp.provider.call_hierarchy.incoming_calls; // import lsp.provider.call_hierarchy.incoming_calls;
// import lsp.provider.call_hierarchy.outgoing_calls; // import lsp.provider.call_hierarchy.outgoing_calls;
// import lsp.provider.cancel_request.cancel_request;
// import lsp.provider.client.register_capability; // import lsp.provider.client.register_capability;
// import lsp.provider.client.unregister_capability; // import lsp.provider.client.unregister_capability;
// import lsp.provider.code_action.resolve; // import lsp.provider.code_action.resolve;
// import lsp.provider.code_lens.resolve; // import lsp.provider.code_lens.resolve;
// import lsp.provider.document_link.resolve; // import lsp.provider.document_link.resolve;
// import lsp.provider.exit.exit;
// import lsp.provider.inlay_hint.resolve; // import lsp.provider.inlay_hint.resolve;
// import lsp.provider.telemetry.event; // import lsp.provider.telemetry.event;
// import lsp.provider.text_document.code_action; // import lsp.provider.text_document.code_action;
@@ -94,7 +91,6 @@ export namespace lsp::provider
completion_item::Resolve, completion_item::Resolve,
Initialize, Initialize,
Initialized, Initialized,
Shutdown,
text_document::Completion, text_document::Completion,
text_document::Definition, text_document::Definition,
text_document::DidChange, text_document::DidChange,
@@ -107,18 +103,15 @@ export namespace lsp::provider
// using AllProviders = ProviderRegistry< // using AllProviders = ProviderRegistry<
// call_hierarchy::IncomingCalls, // call_hierarchy::IncomingCalls,
// call_hierarchy::OutgoingCalls, // call_hierarchy::OutgoingCalls,
// CancelRequest,
// client::RegisterCapability, // client::RegisterCapability,
// client::UnregisterCapability, // client::UnregisterCapability,
// code_action::Resolve, // code_action::Resolve,
// code_lens::Resolve, // code_lens::Resolve,
// completion_item::Resolve, // completion_item::Resolve,
// document_link::Resolve, // document_link::Resolve,
// Exit,
// Initialize, // Initialize,
// Initialized, // Initialized,
// inlay_hint::Resolve, // inlay_hint::Resolve,
// Shutdown,
// telemetry::Event, // telemetry::Event,
// text_document::CodeAction, // text_document::CodeAction,
// text_document::CodeLens, // text_document::CodeLens,
@@ -1,53 +0,0 @@
module;
export module lsp.provider.shutdown.shutdown;
import spdlog;
import std;
import lsp.protocol;
import lsp.codec.facade;
import lsp.provider.base.interface;
namespace transform = lsp::codec;
export namespace lsp::provider
{
class Shutdown : public AutoRegisterProvider<Shutdown, IRequestProvider>
{
public:
static constexpr std::string_view kMethod = "shutdown";
static constexpr std::string_view kProviderName = "Shutdown";
Shutdown() = default;
std::string ProvideResponse(const protocol::RequestMessage& request, ExecutionContext& context) override;
};
}
namespace lsp::provider
{
std::string Shutdown::ProvideResponse(const protocol::RequestMessage& request, ExecutionContext& context)
{
spdlog::debug("ShutdownProvider: Providing response for method {}", request.method);
// 触发关闭事件
context.TriggerLifecycleEvent(ServerLifecycleEvent::kShuttingDown);
context.GetManagerHub().Shutdown();
// 构建响应 - shutdown 返回 null
protocol::ResponseMessage response;
response.id = request.id;
auto json = transform::Serialize(response);
if (!json.has_value())
return BuildErrorResponseMessage(request, protocol::ErrorCodes::InternalError, "Failed to serialize response");
spdlog::info("Shutdown request processed successfully");
return json.value();
}
}
+4
View File
@@ -56,6 +56,10 @@ if(BUILD_TESTS)
COMMAND ${PYTHON3_EXECUTABLE} COMMAND ${PYTHON3_EXECUTABLE}
${CMAKE_CURRENT_LIST_DIR}/test_cli_startup.py ${CMAKE_CURRENT_LIST_DIR}/test_cli_startup.py
--server $<TARGET_FILE:tsl-server>) --server $<TARGET_FILE:tsl-server>)
add_test(NAME test_core_server
COMMAND ${PYTHON3_EXECUTABLE}
${CMAKE_CURRENT_LIST_DIR}/test_core_server.py
--server $<TARGET_FILE:test_provider>)
else() else()
message(WARNING "python3 not found; skipping test_lsp_json and test_cli_startup registration") message(WARNING "python3 not found; skipping test_lsp_json and test_cli_startup registration")
endif() endif()
+182
View File
@@ -0,0 +1,182 @@
#!/usr/bin/env python3
import argparse
import json
import os
import select
import subprocess
import time
from pathlib import Path
def frame(message: dict) -> bytes:
body = json.dumps(message, separators=(",", ":")).encode("utf-8")
return f"Content-Length: {len(body)}\r\n\r\n".encode("ascii") + body
def read_messages(data: bytes) -> list[dict]:
messages = []
offset = 0
while offset < len(data):
header_end = data.find(b"\r\n\r\n", offset)
if header_end < 0:
raise RuntimeError("Incomplete LSP response header")
header = data[offset:header_end].decode("ascii")
fields = dict(line.split(": ", 1) for line in header.split("\r\n"))
length = int(fields["Content-Length"])
body_start = header_end + 4
body_end = body_start + length
if body_end > len(data):
raise RuntimeError("Incomplete LSP response body")
messages.append(json.loads(data[body_start:body_end].decode("utf-8")))
offset = body_end
return messages
def response_by_id(data: bytes, request_id: int | str) -> dict:
return next(message for message in read_messages(data)
if message.get("id") == request_id)
def run_raw(server: Path, payload: bytes) -> subprocess.CompletedProcess[bytes]:
return subprocess.run(
[str(server), "--core-server-fixture"],
input=payload,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
timeout=5,
check=False,
)
def run_batch(server: Path, messages: list[dict]) -> subprocess.CompletedProcess[bytes]:
return run_raw(server, b"".join(frame(message) for message in messages))
class LspClient:
def __init__(self, server: Path) -> None:
self.proc = subprocess.Popen(
[str(server), "--core-server-fixture"],
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
)
self.buffer = b""
def send(self, message: dict) -> None:
if self.proc.stdin is None:
raise RuntimeError("Fixture stdin is unavailable")
self.proc.stdin.write(frame(message))
self.proc.stdin.flush()
def read(self, timeout: float = 1.0) -> dict:
if self.proc.stdout is None:
raise RuntimeError("Fixture stdout is unavailable")
deadline = time.monotonic() + timeout
while True:
header_end = self.buffer.find(b"\r\n\r\n")
if header_end >= 0:
header = self.buffer[:header_end].decode("ascii")
fields = dict(line.split(": ", 1)
for line in header.split("\r\n"))
length = int(fields["Content-Length"])
body_start = header_end + 4
body_end = body_start + length
if len(self.buffer) >= body_end:
body = self.buffer[body_start:body_end]
self.buffer = self.buffer[body_end:]
return json.loads(body.decode("utf-8"))
remaining = deadline - time.monotonic()
if remaining <= 0:
raise TimeoutError("Timed out waiting for LSP response")
ready, _, _ = select.select(
[self.proc.stdout.fileno()], [], [], remaining)
if not ready:
continue
chunk = os.read(self.proc.stdout.fileno(), 4096)
if not chunk:
raise RuntimeError("Fixture closed stdout before a response")
self.buffer += chunk
def close_input(self) -> int:
if self.proc.stdin is not None:
self.proc.stdin.close()
return self.proc.wait(timeout=5)
def kill(self) -> None:
if self.proc.poll() is None:
self.proc.kill()
self.proc.wait(timeout=5)
def assert_lifecycle(server: Path) -> None:
before_init = run_batch(server, [
{"jsonrpc": "2.0", "id": 1, "method": "shutdown"},
{"jsonrpc": "2.0", "method": "exit"},
])
if before_init.returncode != 1:
raise RuntimeError("shutdown before initialize should exit with code 1")
if response_by_id(before_init.stdout, 1)["error"]["code"] != -32002:
raise RuntimeError("shutdown before initialize should return ServerNotInitialized")
repeated = run_batch(server, [
{"jsonrpc": "2.0", "id": 1, "method": "initialize", "params": {}},
{"jsonrpc": "2.0", "id": 2, "method": "initialize", "params": {}},
{"jsonrpc": "2.0", "id": 3, "method": "shutdown"},
{"jsonrpc": "2.0", "method": "exit"},
])
if repeated.returncode != 0:
raise RuntimeError("shutdown followed by exit should return code 0")
if response_by_id(repeated.stdout, 2)["error"]["code"] != -32600:
raise RuntimeError("repeated initialize should return InvalidRequest")
if response_by_id(repeated.stdout, 3).get("result", "missing") is not None:
raise RuntimeError("shutdown should return a null result")
direct_exit = run_batch(
server, [{"jsonrpc": "2.0", "method": "exit"}])
if direct_exit.returncode != 1:
raise RuntimeError("exit before shutdown should return code 1")
client = LspClient(server)
try:
client.send({
"jsonrpc": "2.0",
"id": 11,
"method": "initialize",
"params": {},
})
if client.read().get("id") != 11:
raise RuntimeError("missing initialize response")
client.send({"jsonrpc": "2.0", "id": 12, "method": "shutdown"})
if client.read().get("result", "missing") is not None:
raise RuntimeError("shutdown should return a null result")
time.sleep(0.1)
if client.proc.poll() is not None:
raise RuntimeError("server exited before receiving exit notification")
client.send({"jsonrpc": "2.0", "method": "exit"})
if client.close_input() != 0:
raise RuntimeError("orderly lifecycle should return code 0")
finally:
client.kill()
def main() -> int:
parser = argparse.ArgumentParser()
parser.add_argument("--server", type=Path, required=True)
args = parser.parse_args()
assert_lifecycle(args.server)
return 0
if __name__ == "__main__":
raise SystemExit(main())
+6 -3
View File
@@ -20,6 +20,7 @@ set(SOURCES
main.cc main.cc
test_main.cppm test_main.cppm
../test_lsp_any/test_framework.cppm ../test_lsp_any/test_framework.cppm
core_server_fixture.cppm
fixtures.cppm fixtures.cppm
completion_test.cppm completion_test.cppm
json_flow_test.cppm json_flow_test.cppm
@@ -49,6 +50,7 @@ target_sources(
${CMAKE_CURRENT_SOURCE_DIR}/../../src ${CMAKE_CURRENT_SOURCE_DIR}/../../src
FILES ${CMAKE_CURRENT_SOURCE_DIR}/test_main.cppm FILES ${CMAKE_CURRENT_SOURCE_DIR}/test_main.cppm
${CMAKE_CURRENT_SOURCE_DIR}/../test_lsp_any/test_framework.cppm ${CMAKE_CURRENT_SOURCE_DIR}/../test_lsp_any/test_framework.cppm
${CMAKE_CURRENT_SOURCE_DIR}/core_server_fixture.cppm
${CMAKE_CURRENT_SOURCE_DIR}/fixtures.cppm ${CMAKE_CURRENT_SOURCE_DIR}/fixtures.cppm
${CMAKE_CURRENT_SOURCE_DIR}/completion_test.cppm ${CMAKE_CURRENT_SOURCE_DIR}/completion_test.cppm
${CMAKE_CURRENT_SOURCE_DIR}/json_flow_test.cppm ${CMAKE_CURRENT_SOURCE_DIR}/json_flow_test.cppm
@@ -62,10 +64,14 @@ target_sources(
../../src/bridge/spdlog.cppm ../../src/bridge/spdlog.cppm
../../src/bridge/taskflow.cppm ../../src/bridge/taskflow.cppm
../../src/bridge/tree_sitter.cppm ../../src/bridge/tree_sitter.cppm
../../src/bridge/win32_stdio.cppm
../../src/utils/args_parser.cppm
../../src/utils/string.cppm ../../src/utils/string.cppm
../../src/utils/text_coordinates.cppm ../../src/utils/text_coordinates.cppm
../../src/core/dispatcher.cppm ../../src/core/dispatcher.cppm
../../src/core/server.cppm
../../src/scheduler/async_executor.cppm ../../src/scheduler/async_executor.cppm
../../src/manager/bootstrap.cppm
../../src/manager/event_bus.cppm ../../src/manager/event_bus.cppm
../../src/manager/events.cppm ../../src/manager/events.cppm
../../src/manager/detail/text_document.cppm ../../src/manager/detail/text_document.cppm
@@ -140,9 +146,6 @@ target_sources(
../../src/provider/completion_item/resolve.cppm ../../src/provider/completion_item/resolve.cppm
../../src/provider/initialize/initialize.cppm ../../src/provider/initialize/initialize.cppm
../../src/provider/initialized/initialized.cppm ../../src/provider/initialized/initialized.cppm
../../src/provider/shutdown/shutdown.cppm
../../src/provider/exit/exit.cppm
../../src/provider/cancel_request/cancel_request.cppm
../../src/provider/trace/set_trace.cppm ../../src/provider/trace/set_trace.cppm
../../src/provider/client/register_capability.cppm ../../src/provider/client/register_capability.cppm
../../src/provider/client/unregister_capability.cppm ../../src/provider/client/unregister_capability.cppm
@@ -69,7 +69,7 @@ namespace lsp::test::provider
core::ExecutionContext context; core::ExecutionContext context;
ProviderEnv() ProviderEnv()
: context([](core::ServerLifecycleEvent) {}, scheduler, hub) : context(scheduler, hub)
{ {
hub.Initialize(); hub.Initialize();
} }
@@ -0,0 +1,56 @@
module;
export module lsp.test.provider.core_server_fixture;
import spdlog;
import std;
import lsp.codec.facade;
import lsp.core.server;
import lsp.protocol;
import lsp.provider.base.interface;
export namespace lsp::test::provider
{
int RunCoreServerFixture();
}
namespace lsp::test::provider
{
class FixtureInitialize final : public core::IRequestProvider
{
public:
std::string GetMethod() const override
{
return "initialize";
}
std::string GetProviderName() const override
{
return "FixtureInitialize";
}
std::string ProvideResponse(const protocol::RequestMessage& request,
core::ExecutionContext&) override
{
protocol::ResponseMessage response;
response.id = request.id;
response.result = protocol::LSPAny(protocol::LSPObject{});
return codec::Serialize(response).value();
}
};
int RunCoreServerFixture()
{
spdlog::set_level(spdlog::level::off);
core::LspServer server(
std::cin,
std::cout,
[](core::RequestDispatcher& dispatcher) {
dispatcher.RegisterRequestProvider(std::make_shared<FixtureInitialize>());
},
2,
"");
return server.Run();
}
}
@@ -38,7 +38,7 @@ namespace lsp::test::provider
core::ExecutionContext context; core::ExecutionContext context;
ProviderEnv() ProviderEnv()
: context([](core::ServerLifecycleEvent) {}, scheduler, hub) : context(scheduler, hub)
{ {
hub.Initialize(); hub.Initialize();
} }
@@ -39,7 +39,7 @@ namespace lsp::test::provider
core::ExecutionContext context; core::ExecutionContext context;
ProviderEnv() ProviderEnv()
: context([](core::ServerLifecycleEvent) {}, scheduler, hub) : context(scheduler, hub)
{ {
hub.Initialize(); hub.Initialize();
} }
@@ -207,4 +207,3 @@ namespace lsp::test::provider
return result; return result;
} }
} }
@@ -40,7 +40,7 @@ namespace lsp::test::provider
core::ExecutionContext context; core::ExecutionContext context;
ProviderEnv() ProviderEnv()
: context([](core::ServerLifecycleEvent) {}, scheduler, hub) : context(scheduler, hub)
{ {
hub.Initialize(); hub.Initialize();
} }
@@ -51,13 +51,11 @@ namespace lsp::test::provider
{ {
scheduler::async_executor::AsyncExecutor scheduler{ 1 }; scheduler::async_executor::AsyncExecutor scheduler{ 1 };
manager::ManagerHub hub{}; manager::ManagerHub hub{};
core::RequestDispatcher dispatcher{}; core::RequestDispatcher dispatcher{ scheduler, hub };
ProviderEnv() ProviderEnv()
{ {
hub.Initialize(); hub.Initialize();
dispatcher.SetRequestScheduler(&scheduler);
dispatcher.SetManagerHub(&hub);
provider::RegisterAllProviders(dispatcher); provider::RegisterAllProviders(dispatcher);
} }
}; };
@@ -77,10 +77,7 @@ import lsp.provider.workspace.will_delete_files;
import lsp.provider.workspace.will_rename_files; import lsp.provider.workspace.will_rename_files;
import lsp.provider.workspace_symbol.resolve; import lsp.provider.workspace_symbol.resolve;
import lsp.provider.text_document.publish_diagnostics; import lsp.provider.text_document.publish_diagnostics;
import lsp.provider.shutdown.shutdown;
import lsp.provider.cancel_request.cancel_request;
import lsp.provider.trace.set_trace; import lsp.provider.trace.set_trace;
import lsp.provider.exit.exit;
import lsp.core.dispatcher; import lsp.core.dispatcher;
import lsp.manager.manager_hub; import lsp.manager.manager_hub;
import lsp.manager.symbol; import lsp.manager.symbol;
@@ -160,13 +157,8 @@ export namespace lsp::test::provider
static TestResult TestExecuteCommandProvider(); static TestResult TestExecuteCommandProvider();
static TestResult TestWillFileOperationsProviders(); static TestResult TestWillFileOperationsProviders();
static TestResult TestWorkspaceSymbolResolveProvider(); static TestResult TestWorkspaceSymbolResolveProvider();
static TestResult TestShutdownProvider();
static TestResult TestCancelRequestProvider();
static TestResult TestSetTraceProvider(); static TestResult TestSetTraceProvider();
static TestResult TestExitProvider();
}; };
int RunExitProviderChild();
} }
namespace lsp::test::provider namespace lsp::test::provider
@@ -175,13 +167,12 @@ namespace lsp::test::provider
{ {
struct ProviderEnv struct ProviderEnv
{ {
std::vector<core::ServerLifecycleEvent> events;
scheduler::async_executor::AsyncExecutor scheduler{ 1 }; scheduler::async_executor::AsyncExecutor scheduler{ 1 };
manager::ManagerHub hub{}; manager::ManagerHub hub{};
core::ExecutionContext context; core::ExecutionContext context;
ProviderEnv() ProviderEnv()
: context([this](core::ServerLifecycleEvent event) { events.push_back(event); }, scheduler, hub) : context(scheduler, hub)
{ {
hub.Initialize(); hub.Initialize();
} }
@@ -365,10 +356,7 @@ namespace lsp::test::provider
runner.addTest("workspace executeCommand provider", TestExecuteCommandProvider); runner.addTest("workspace executeCommand provider", TestExecuteCommandProvider);
runner.addTest("workspace will file operations providers", TestWillFileOperationsProviders); runner.addTest("workspace will file operations providers", TestWillFileOperationsProviders);
runner.addTest("workspaceSymbol/resolve provider", TestWorkspaceSymbolResolveProvider); runner.addTest("workspaceSymbol/resolve provider", TestWorkspaceSymbolResolveProvider);
runner.addTest("shutdown provider", TestShutdownProvider);
runner.addTest("cancel request provider", TestCancelRequestProvider);
runner.addTest("setTrace provider", TestSetTraceProvider); runner.addTest("setTrace provider", TestSetTraceProvider);
runner.addTest("exit provider", TestExitProvider);
} }
TestResult ProviderMiscTests::TestInitializeProvider() TestResult ProviderMiscTests::TestInitializeProvider()
@@ -434,8 +422,6 @@ namespace lsp::test::provider
}); });
assertTrue(found_workspace, "Workspace symbols should be indexed"); assertTrue(found_workspace, "Workspace symbols should be indexed");
assertTrue(!env.events.empty(), "Initialize should emit lifecycle event");
assertTrue(env.events.back() == core::ServerLifecycleEvent::kInitialized, "Initialize should emit initialized");
return result; return result;
} }
@@ -2233,7 +2219,6 @@ namespace lsp::test::provider
const auto& stored_obj = stored.Get<protocol::LSPObject>(); const auto& stored_obj = stored.Get<protocol::LSPObject>();
assertTrue(stored_obj.contains("tsl"), "stored settings should include tsl section"); assertTrue(stored_obj.contains("tsl"), "stored settings should include tsl section");
assertTrue(env.events.empty(), "didChangeConfiguration should not trigger lifecycle events");
return result; return result;
} }
@@ -2761,7 +2746,6 @@ namespace lsp::test::provider
provider.HandleNotification(notification, env.context); provider.HandleNotification(notification, env.context);
} }
assertTrue(env.events.empty(), "window message notifications should not trigger lifecycle events");
return result; return result;
} }
@@ -2781,7 +2765,6 @@ namespace lsp::test::provider
::lsp::provider::telemetry::Event provider; ::lsp::provider::telemetry::Event provider;
provider.HandleNotification(notification, env.context); provider.HandleNotification(notification, env.context);
assertTrue(env.events.empty(), "telemetry/event should not trigger lifecycle events");
return result; return result;
} }
@@ -2816,7 +2799,6 @@ namespace lsp::test::provider
::lsp::provider::text_document::PublishDiagnostics provider; ::lsp::provider::text_document::PublishDiagnostics provider;
provider.HandleNotification(notification, env.context); provider.HandleNotification(notification, env.context);
assertTrue(env.events.empty(), "publishDiagnostics should not trigger lifecycle events");
return result; return result;
} }
@@ -3396,63 +3378,6 @@ namespace lsp::test::provider
return result; return result;
} }
TestResult ProviderMiscTests::TestShutdownProvider()
{
TestResult result{ "", true, "ok" };
ProviderEnv env;
auto path = FixturePath("rename_case.tsl");
auto content = ReadTextFile(path);
auto uri = ToUri(path);
OpenDocument(env.hub, uri, content, 1);
protocol::RequestMessage request;
request.id = "shutdown";
request.method = "shutdown";
::lsp::provider::Shutdown provider;
auto json = provider.ProvideResponse(request, env.context);
auto response = ParseResponse(json);
assertTrue(!response.error.has_value(), "Shutdown should not return error");
assertTrue(env.events.size() >= 1, "Shutdown should emit lifecycle event");
assertTrue(env.events.back() == core::ServerLifecycleEvent::kShuttingDown, "Shutdown should emit shutting down");
assertFalse(env.hub.documents().GetContent(uri).has_value(), "Shutdown should clear documents");
return result;
}
TestResult ProviderMiscTests::TestCancelRequestProvider()
{
TestResult result{ "", true, "ok" };
ProviderEnv env;
std::atomic<bool> started{ false };
env.scheduler.Submit("cancel_me", [&started](std::stop_token) -> std::optional<std::string> {
started.store(true);
std::this_thread::sleep_for(std::chrono::milliseconds(200));
return std::string("done");
});
while (!started.load())
{
std::this_thread::sleep_for(std::chrono::milliseconds(5));
}
protocol::CancelParams params;
params.id = std::string("cancel_me");
protocol::NotificationMessage notification;
notification.method = "$/cancelRequest";
notification.params = codec::ToLSPAny(params);
::lsp::provider::CancelRequest provider;
provider.HandleNotification(notification, env.context);
env.scheduler.WaitAll();
auto stats = env.scheduler.GetStatistics();
assertEqual(std::size_t(1), static_cast<std::size_t>(stats.cancelled),
"CancelRequest should mark task cancelled");
return result;
}
TestResult ProviderMiscTests::TestSetTraceProvider() TestResult ProviderMiscTests::TestSetTraceProvider()
{ {
TestResult result{ "", true, "ok" }; TestResult result{ "", true, "ok" };
@@ -3484,24 +3409,4 @@ namespace lsp::test::provider
return result; return result;
} }
TestResult ProviderMiscTests::TestExitProvider()
{
TestResult result{ "", true, "ok" };
auto exe = ExecutablePath();
assertTrue(!exe.empty(), "ExecutablePath should be set");
std::string command = "\"" + exe + "\" --exit-provider";
int code = std::system(command.c_str());
assertEqual(0, code, "Exit should return code 0");
return result;
}
int RunExitProviderChild()
{
ProviderEnv env;
::lsp::provider::Exit provider;
protocol::NotificationMessage notification;
notification.method = "exit";
provider.HandleNotification(notification, env.context);
return 1;
}
} }
@@ -12,16 +12,13 @@ import lsp.manager.manager_hub;
import lsp.scheduler.async_executor; import lsp.scheduler.async_executor;
import lsp.test.provider.fixtures; import lsp.test.provider.fixtures;
import lsp.provider.cancel_request.cancel_request;
import lsp.provider.code_action.resolve; import lsp.provider.code_action.resolve;
import lsp.provider.code_lens.resolve; import lsp.provider.code_lens.resolve;
import lsp.provider.completion_item.resolve; import lsp.provider.completion_item.resolve;
import lsp.provider.document_link.resolve; import lsp.provider.document_link.resolve;
import lsp.provider.exit.exit;
import lsp.provider.initialize.initialize; import lsp.provider.initialize.initialize;
import lsp.provider.initialized.initialized; import lsp.provider.initialized.initialized;
import lsp.provider.inlay_hint.resolve; import lsp.provider.inlay_hint.resolve;
import lsp.provider.shutdown.shutdown;
import lsp.provider.trace.set_trace; import lsp.provider.trace.set_trace;
import lsp.provider.call_hierarchy.incoming_calls; import lsp.provider.call_hierarchy.incoming_calls;
import lsp.provider.call_hierarchy.outgoing_calls; import lsp.provider.call_hierarchy.outgoing_calls;
@@ -118,7 +115,7 @@ namespace lsp::test::provider
core::ExecutionContext context; core::ExecutionContext context;
ProviderEnv() ProviderEnv()
: context([](core::ServerLifecycleEvent) {}, scheduler, hub) : context(scheduler, hub)
{ {
hub.Initialize(); hub.Initialize();
} }
@@ -174,7 +171,6 @@ namespace lsp::test::provider
TestResult result{ "", true, "ok" }; TestResult result{ "", true, "ok" };
CheckProviderMetadata<provider::Initialize>("initialize", "Initialize"); CheckProviderMetadata<provider::Initialize>("initialize", "Initialize");
CheckProviderMetadata<provider::Shutdown>("shutdown", "Shutdown");
CheckProviderMetadata<provider::completion_item::Resolve>("completionItem/resolve", "CompletionItemResolve"); CheckProviderMetadata<provider::completion_item::Resolve>("completionItem/resolve", "CompletionItemResolve");
CheckProviderMetadata<provider::text_document::Completion>("textDocument/completion", "TextDocumentCompletion"); CheckProviderMetadata<provider::text_document::Completion>("textDocument/completion", "TextDocumentCompletion");
CheckProviderMetadata<provider::text_document::Definition>("textDocument/definition", "TextDocumentDefinition"); CheckProviderMetadata<provider::text_document::Definition>("textDocument/definition", "TextDocumentDefinition");
@@ -273,8 +269,6 @@ namespace lsp::test::provider
CheckProviderMetadata<provider::workspace::DidDeleteFiles>("workspace/didDeleteFiles", "WorkspaceDidDeleteFiles"); CheckProviderMetadata<provider::workspace::DidDeleteFiles>("workspace/didDeleteFiles", "WorkspaceDidDeleteFiles");
CheckProviderMetadata<provider::workspace::DidRenameFiles>("workspace/didRenameFiles", "WorkspaceDidRenameFiles"); CheckProviderMetadata<provider::workspace::DidRenameFiles>("workspace/didRenameFiles", "WorkspaceDidRenameFiles");
CheckProviderMetadata<provider::Initialized>("initialized", "Initialized"); CheckProviderMetadata<provider::Initialized>("initialized", "Initialized");
CheckProviderMetadata<provider::Exit>("exit", "Exit");
CheckProviderMetadata<provider::CancelRequest>("$/cancelRequest", "CancelRequest");
CheckProviderMetadata<provider::SetTrace>("$/setTrace", "SetTrace"); CheckProviderMetadata<provider::SetTrace>("$/setTrace", "SetTrace");
CheckProviderMetadata<provider::text_document::DidOpen>("textDocument/didOpen", "TextDocumentDidOpen"); CheckProviderMetadata<provider::text_document::DidOpen>("textDocument/didOpen", "TextDocumentDidOpen");
CheckProviderMetadata<provider::text_document::DidChange>("textDocument/didChange", "TextDocumentDidChange"); CheckProviderMetadata<provider::text_document::DidChange>("textDocument/didChange", "TextDocumentDidChange");
@@ -287,7 +281,6 @@ namespace lsp::test::provider
{ {
TestResult result{ "", true, "ok" }; TestResult result{ "", true, "ok" };
CheckRequestResponse<provider::Shutdown>();
CheckRequestResponse<provider::completion_item::Resolve>(); CheckRequestResponse<provider::completion_item::Resolve>();
CheckRequestResponse<provider::text_document::Completion>(); CheckRequestResponse<provider::text_document::Completion>();
CheckRequestResponse<provider::text_document::Definition>(); CheckRequestResponse<provider::text_document::Definition>();
@@ -357,7 +350,6 @@ namespace lsp::test::provider
TestResult result{ "", true, "ok" }; TestResult result{ "", true, "ok" };
CheckNotificationHandler<provider::Initialized>(std::nullopt); CheckNotificationHandler<provider::Initialized>(std::nullopt);
CheckNotificationHandler<provider::Exit>(std::nullopt);
CheckNotificationHandler<provider::workspace::DidChangeConfiguration>(std::nullopt); CheckNotificationHandler<provider::workspace::DidChangeConfiguration>(std::nullopt);
CheckNotificationHandler<provider::workspace::DidChangeWatchedFiles>(std::nullopt); CheckNotificationHandler<provider::workspace::DidChangeWatchedFiles>(std::nullopt);
CheckNotificationHandler<provider::workspace::DidChangeWorkspaceFolders>(std::nullopt); CheckNotificationHandler<provider::workspace::DidChangeWorkspaceFolders>(std::nullopt);
+3 -2
View File
@@ -6,6 +6,7 @@ import std;
import lsp.test.framework; import lsp.test.framework;
import lsp.test.provider.completion; import lsp.test.provider.completion;
import lsp.test.provider.core_server_fixture;
import lsp.test.provider.definitions; import lsp.test.provider.definitions;
import lsp.test.provider.interpreter; import lsp.test.provider.interpreter;
import lsp.test.provider.json_flow; import lsp.test.provider.json_flow;
@@ -26,9 +27,9 @@ export int Run(int argc, char** argv)
{ {
std::string_view arg(argv[i]); std::string_view arg(argv[i]);
if (arg == "--exit-provider") if (arg == "--core-server-fixture")
{ {
return lsp::test::provider::RunExitProviderChild(); return lsp::test::provider::RunCoreServerFixture();
} }
constexpr std::string_view kInterpreterPrefix = "--interpreter="; constexpr std::string_view kInterpreterPrefix = "--interpreter=";