From 449188693ee4f82264daf9029f840b1b6cf4a3bd Mon Sep 17 00:00:00 2001 From: Dominik Jain Date: Mon, 23 Jun 2025 21:15:32 +0200 Subject: [PATCH] Consider timeout for LS requests --- src/solidlsp/ls.py | 45 ++++++++++++++++++++------------------ src/solidlsp/ls_handler.py | 21 +++++++++--------- 2 files changed, 35 insertions(+), 31 deletions(-) diff --git a/src/solidlsp/ls.py b/src/solidlsp/ls.py index 0d4dcaa8..22be2deb 100644 --- a/src/solidlsp/ls.py +++ b/src/solidlsp/ls.py @@ -101,85 +101,83 @@ class SolidLanguageServer(ABC): :param repository_root_path: The root path of the repository. :param config: The Multilspy configuration. :param logger: The logger to use. + :param timeout: the timeout for requests to the language server. If None, no timeout will be used. :return LanguageServer: A language specific LanguageServer instance. """ - # TODO: Apply timeout + ls: SolidLanguageServer + if config.code_language == Language.PYTHON: from solidlsp.language_servers.pyright_language_server.pyright_server import ( PyrightServer, ) - return PyrightServer(config, logger, repository_root_path) - # It used to be jedi, but pyright is a bit faster, and also more actively maintained - # Keeping the previous code for reference - # from multilspy.language_servers.jedi_language_server.jedi_server import ( - # JediServer, - # ) - - # return JediServer(config, logger, repository_root_path) + ls = PyrightServer(config, logger, repository_root_path) elif config.code_language == Language.JAVA: from solidlsp.language_servers.eclipse_jdtls.eclipse_jdtls import ( EclipseJDTLS, ) - return EclipseJDTLS(config, logger, repository_root_path) + ls = EclipseJDTLS(config, logger, repository_root_path) elif config.code_language == Language.KOTLIN: from solidlsp.language_servers.kotlin_language_server.kotlin_language_server import ( KotlinLanguageServer, ) - return KotlinLanguageServer(config, logger, repository_root_path) + ls = KotlinLanguageServer(config, logger, repository_root_path) elif config.code_language == Language.RUST: from solidlsp.language_servers.rust_analyzer.rust_analyzer import ( RustAnalyzer, ) - return RustAnalyzer(config, logger, repository_root_path) + ls = RustAnalyzer(config, logger, repository_root_path) elif config.code_language == Language.CSHARP: from solidlsp.language_servers.omnisharp.omnisharp import OmniSharp - return OmniSharp(config, logger, repository_root_path) + ls = OmniSharp(config, logger, repository_root_path) elif config.code_language in [Language.TYPESCRIPT, Language.JAVASCRIPT]: from solidlsp.language_servers.typescript_language_server.typescript_language_server import ( TypeScriptLanguageServer, ) - return TypeScriptLanguageServer(config, logger, repository_root_path) + ls = TypeScriptLanguageServer(config, logger, repository_root_path) elif config.code_language == Language.GO: from solidlsp.language_servers.gopls.gopls import Gopls - return Gopls(config, logger, repository_root_path) + ls = Gopls(config, logger, repository_root_path) elif config.code_language == Language.RUBY: from solidlsp.language_servers.solargraph.solargraph import Solargraph - return Solargraph(config, logger, repository_root_path) + ls = Solargraph(config, logger, repository_root_path) elif config.code_language == Language.DART: from solidlsp.language_servers.dart_language_server.dart_language_server import DartLanguageServer - return DartLanguageServer(config, logger, repository_root_path) + ls = DartLanguageServer(config, logger, repository_root_path) elif config.code_language == Language.CPP: from solidlsp.language_servers.clangd_language_server.clangd_language_server import ClangdLanguageServer - return ClangdLanguageServer(config, logger, repository_root_path) + ls = ClangdLanguageServer(config, logger, repository_root_path) elif config.code_language == Language.PHP: from solidlsp.language_servers.intelephense.intelephense import Intelephense - return Intelephense(config, logger, repository_root_path) + ls = Intelephense(config, logger, repository_root_path) else: logger.log(f"Language {config.code_language} is not supported", logging.ERROR) raise LanguageServerException(f"Language {config.code_language} is not supported") + ls.set_request_timeout(timeout) + return ls + def __init__( self, config: LanguageServerConfig, @@ -255,6 +253,13 @@ class SolidLanguageServer(ABC): self._ignore_spec = pathspec.PathSpec.from_lines(pathspec.patterns.GitWildMatchPattern, processed_patterns) self._server_context = None + self._request_timeout: float | None = None + + def set_request_timeout(self, timeout: float | None) -> None: + """ + :param timeout: the timeout, in seconds, for requests to the language server. + """ + self.server.set_request_timeout(timeout) def get_ignore_spec(self) -> pathspec.PathSpec: """Returns the pathspec matcher for the paths that were configured to be ignored through @@ -1133,8 +1138,6 @@ class SolidLanguageServer(ABC): return ls_types.Hover(**response) - # ----------------------------- FROM HERE ON MODIFICATIONS BY MISCHA -------------------- - def retrieve_symbol_body(self, symbol: ls_types.UnifiedSymbolInformation | LSPTypes.DocumentSymbol | LSPTypes.SymbolInformation) -> str: """ Load the body of the given symbol. If the body is already contained in the symbol, just return it. diff --git a/src/solidlsp/ls_handler.py b/src/solidlsp/ls_handler.py index 458a0236..f367a1fc 100644 --- a/src/solidlsp/ls_handler.py +++ b/src/solidlsp/ls_handler.py @@ -53,9 +53,8 @@ class Request: def on_error(self, err: Error) -> None: self._result_queue.put(Request.Result(error=err)) - def get_result(self) -> Result: - # TODO could add timeout - return self._result_queue.get() + def get_result(self, timeout: float | None = None) -> Result: + return self._result_queue.get(timeout=timeout) class SolidLanguageServerHandler: @@ -102,13 +101,8 @@ class SolidLanguageServerHandler: process_launch_info: ProcessLaunchInfo, logger: Callable[[str, str, StringDict | str], None] | None = None, start_independent_lsp_process=True, + request_timeout: float | None = None, ) -> None: - """ - Params: - cmd: A string that represents the command to launch the language server process. - logger: An optional function that takes two strings (source and destination) and - a payload dictionary, and logs the communication between the client and the server. - """ self.send = LanguageServerRequest(self.send_request) self.notify = LspNotification(self.send_notification) @@ -125,6 +119,7 @@ class SolidLanguageServerHandler: self.task_counter = 0 self.loop = None self.start_independent_lsp_process = start_independent_lsp_process + self._request_timeout = request_timeout # Add thread locks for shared resources to prevent race conditions self._stdin_lock = threading.Lock() @@ -132,6 +127,12 @@ class SolidLanguageServerHandler: self._response_handlers_lock = threading.Lock() self._tasks_lock = threading.Lock() + def set_request_timeout(self, timeout: float | None) -> None: + """ + :param timeout: the timeout, in seconds, for all requests sent to the language server. + """ + self._request_timeout = timeout + def is_running(self) -> bool: """ Checks if the language server process is currently running. @@ -418,7 +419,7 @@ class SolidLanguageServerHandler: self._send_payload(make_request(method, request_id, params)) self._log(f"Waiting for response to request {method} with params:\n{params}") - result = request.get_result() + result = request.get_result(timeout=self._request_timeout) self._log("Processing result") if result.is_error():