================ @@ -0,0 +1,280 @@ +//===- ProtocolServerMCP.cpp ----------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include "ProtocolServerMCP.h" +#include "MCPError.h" +#include "lldb/Core/PluginManager.h" +#include "lldb/Utility/LLDBLog.h" +#include "lldb/Utility/Log.h" +#include "llvm/ADT/StringExtras.h" +#include "llvm/Support/Threading.h" +#include <thread> + +using namespace lldb_private; +using namespace lldb_private::mcp; +using namespace llvm; + +LLDB_PLUGIN_DEFINE(ProtocolServerMCP) + +ProtocolServerMCP::ProtocolServerMCP(Debugger &debugger) + : ProtocolServer(), m_debugger(debugger) { + AddHandler("initialize", std::bind(&ProtocolServerMCP::InitializeHandler, + this, std::placeholders::_1)); + AddHandler("tools/list", std::bind(&ProtocolServerMCP::ToolsListHandler, this, + std::placeholders::_1)); + AddHandler("tools/call", std::bind(&ProtocolServerMCP::ToolsCallHandler, this, + std::placeholders::_1)); + AddTool(std::make_unique<LLDBCommandTool>( + "lldb_command", "Run an lldb command.", m_debugger)); +} + +ProtocolServerMCP::~ProtocolServerMCP() { llvm::consumeError(Stop()); } + +void ProtocolServerMCP::Initialize() { + PluginManager::RegisterPlugin(GetPluginNameStatic(), + GetPluginDescriptionStatic(), CreateInstance); +} + +void ProtocolServerMCP::Terminate() { + PluginManager::UnregisterPlugin(CreateInstance); +} + +lldb::ProtocolServerSP ProtocolServerMCP::CreateInstance(Debugger &debugger) { + return std::make_shared<ProtocolServerMCP>(debugger); +} + +llvm::StringRef ProtocolServerMCP::GetPluginDescriptionStatic() { + return "MCP Server."; +} + +llvm::Expected<protocol::Response> +ProtocolServerMCP::Handle(protocol::Request request) { + auto it = m_handlers.find(request.method); + if (it != m_handlers.end()) + return it->second(request); + + return make_error<MCPError>( + llvm::formatv("no handler for request: {0}", request.method).str(), 1); +} + +llvm::Error ProtocolServerMCP::Start(ProtocolServer::Connection connection) { + std::lock_guard<std::mutex> guard(m_server_mutex); + + if (m_running) + return llvm::createStringError("server already running"); + + Status status; + m_listener = Socket::Create(connection.protocol, status); + if (status.Fail()) + return status.takeError(); + + status = m_listener->Listen(connection.name, /*backlog=*/5); + if (status.Fail()) + return status.takeError(); + + std::string address = + llvm::join(m_listener->GetListeningConnectionURI(), ", "); + Log *log = GetLog(LLDBLog::Host); + LLDB_LOG(log, "MCP server started with connection listeners: {0}", address); + + auto handles = m_listener->Accept(m_loop, [=](std::unique_ptr<Socket> sock) { + std::lock_guard<std::mutex> guard(m_server_mutex); + + const std::string client_name = + llvm::formatv("client-{0}", m_clients.size() + 1).str(); + LLDB_LOG(log, "client {0} connected", client_name); + + lldb::IOObjectSP io(std::move(sock)); + + m_clients.emplace_back(io, [=]() { + llvm::set_thread_name(client_name + "-runloop"); + if (auto Err = Run(std::make_unique<JSONRPCTransport>(io, io))) + LLDB_LOG_ERROR(GetLog(LLDBLog::Host), std::move(Err), "MCP Error: {0}"); + }); + }); + if (llvm::Error error = handles.takeError()) + return error; + + m_read_handles = std::move(*handles); + m_loop_thread = std::thread([=] { + llvm::set_thread_name("mcp-runloop"); + m_loop.Run(); + }); + + return llvm::Error::success(); +} + +llvm::Error ProtocolServerMCP::Stop() { + { + std::lock_guard<std::mutex> guard(m_server_mutex); + m_running = false; + } + + // Stop accepting new connections. + m_loop.AddPendingCallback( + [](MainLoopBase &loop) { loop.RequestTermination(); }); + + // Wait for the main loop to exit. + if (m_loop_thread.joinable()) + m_loop_thread.join(); + + // Wait for all our clients to exit. + for (auto &client : m_clients) { + client.first->Close(); ---------------- labath wrote:
This is a very wrong way to shut down a connection. The problem is that closed fd can get recycled and then the reading thread will end up reading from a random file. And since there's no way to guarantee this does not happen, linux doesn't even bother to support the case it theoretically could support: closing an fd while a thread is already blocked reading from it (as opposed to "a thread that is *about to* read from it) -- the thread will just end up blocked there ~forever. You really need a way to have the thread wait for data on the connection *and* the termination signal. Currently, I think the only portable way we have is with MainLoop::AddPendingCallback, but since you already have a main loop for accepting the connections, maybe you could structure the code such that the loop also handles reading from those connections (and submits them to the worker thread via a (mutex-protected) std::queue or whatever)? Then it would be easy to make that wait for termination as well. This is going to make reading serialized, but I doubt that's going to be the bottleneck here. And maybe then we don't need one thread for every connection, and we could have some sort of a thread pool of threads waiting to process requests? https://github.com/llvm/llvm-project/pull/143628 _______________________________________________ lldb-commits mailing list lldb-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits