diff --git a/src/multilspy/language_servers/clangd_language_server/clangd_language_server.py b/src/multilspy/language_servers/clangd_language_server/clangd_language_server.py index 969a898..59afca1 100644 --- a/src/multilspy/language_servers/clangd_language_server/clangd_language_server.py +++ b/src/multilspy/language_servers/clangd_language_server/clangd_language_server.py @@ -139,6 +139,10 @@ async def check_experimental_status(params): async def window_log_message(msg): self.logger.log(f"LSP: window/logMessage: {msg}", logging.INFO) + # Fresh events for every session, so they bind to the current event loop and start unset + self.server_ready = asyncio.Event() + self.completions_available = asyncio.Event() + self.server.on_request("client/registerCapability", do_nothing) self.server.on_notification("language/status", do_nothing) self.server.on_notification("window/logMessage", window_log_message) @@ -176,3 +180,5 @@ async def window_log_message(msg): finally: await self.server.shutdown() await self.server.stop() + self.server_ready = asyncio.Event() + self.completions_available = asyncio.Event() diff --git a/src/multilspy/language_servers/eclipse_jdtls/eclipse_jdtls.py b/src/multilspy/language_servers/eclipse_jdtls/eclipse_jdtls.py index 7d55f9f..df2a4af 100644 --- a/src/multilspy/language_servers/eclipse_jdtls/eclipse_jdtls.py +++ b/src/multilspy/language_servers/eclipse_jdtls/eclipse_jdtls.py @@ -340,6 +340,12 @@ async def window_log_message(msg): async def do_nothing(params): return + # Fresh events for every session, so they bind to the current event loop and start unset + self.service_ready_event = asyncio.Event() + self.intellicode_enable_command_available = asyncio.Event() + self.initialize_searcher_command_available = asyncio.Event() + self.completions_available = asyncio.Event() + self.server.on_request("client/registerCapability", register_capability_handler) self.server.on_notification("language/status", lang_status_handler) self.server.on_notification("window/logMessage", window_log_message) @@ -404,3 +410,7 @@ async def do_nothing(params): finally: await self.server.shutdown() await self.server.stop() + self.service_ready_event = asyncio.Event() + self.intellicode_enable_command_available = asyncio.Event() + self.initialize_searcher_command_available = asyncio.Event() + self.completions_available = asyncio.Event() diff --git a/src/multilspy/language_servers/gopls/gopls.py b/src/multilspy/language_servers/gopls/gopls.py index 6b71aa8..31beb62 100644 --- a/src/multilspy/language_servers/gopls/gopls.py +++ b/src/multilspy/language_servers/gopls/gopls.py @@ -122,6 +122,10 @@ async def window_log_message(msg): async def do_nothing(params): return + # Fresh events for every session, so they bind to the current event loop and start unset + self.server_ready = asyncio.Event() + self.completions_available = asyncio.Event() + self.server.on_request("client/registerCapability", register_capability_handler) self.server.on_notification("window/logMessage", window_log_message) self.server.on_notification("$/progress", do_nothing) @@ -154,3 +158,5 @@ async def do_nothing(params): finally: await self.server.shutdown() await self.server.stop() + self.server_ready = asyncio.Event() + self.completions_available = asyncio.Event() diff --git a/src/multilspy/language_servers/omnisharp/omnisharp.py b/src/multilspy/language_servers/omnisharp/omnisharp.py index 6f95a5f..101490b 100644 --- a/src/multilspy/language_servers/omnisharp/omnisharp.py +++ b/src/multilspy/language_servers/omnisharp/omnisharp.py @@ -398,6 +398,11 @@ async def workspace_configuration_handler(params): } ] + # Fresh events for every session, so they bind to the current event loop and start unset + self.definition_available = asyncio.Event() + self.references_available = asyncio.Event() + self.completions_available = asyncio.Event() + self.server.on_request("client/registerCapability", register_capability_handler) self.server.on_notification("language/status", lang_status_handler) self.server.on_notification("window/logMessage", window_log_message) @@ -442,3 +447,6 @@ async def workspace_configuration_handler(params): finally: await self.server.shutdown() await self.server.stop() + self.definition_available = asyncio.Event() + self.references_available = asyncio.Event() + self.completions_available = asyncio.Event() diff --git a/src/multilspy/language_servers/rust_analyzer/rust_analyzer.py b/src/multilspy/language_servers/rust_analyzer/rust_analyzer.py index dbdbafe..5f70c36 100644 --- a/src/multilspy/language_servers/rust_analyzer/rust_analyzer.py +++ b/src/multilspy/language_servers/rust_analyzer/rust_analyzer.py @@ -130,6 +130,10 @@ async def check_experimental_status(params): async def window_log_message(msg): self.logger.log(f"LSP: window/logMessage: {msg}", logging.INFO) + # Fresh events for every session, so they bind to the current event loop and start unset + self.server_ready = asyncio.Event() + self.completions_available = asyncio.Event() + self.server.on_request("client/registerCapability", do_nothing) self.server.on_notification("language/status", do_nothing) self.server.on_notification("window/logMessage", window_log_message) @@ -165,3 +169,5 @@ async def window_log_message(msg): finally: await self.server.shutdown() await self.server.stop() + self.server_ready = asyncio.Event() + self.completions_available = asyncio.Event() diff --git a/src/multilspy/language_servers/solargraph/solargraph.py b/src/multilspy/language_servers/solargraph/solargraph.py index 45d6f3e..5ed6977 100644 --- a/src/multilspy/language_servers/solargraph/solargraph.py +++ b/src/multilspy/language_servers/solargraph/solargraph.py @@ -138,6 +138,10 @@ async def do_nothing(params): async def window_log_message(msg): self.logger.log(f"LSP: window/logMessage: {msg}", logging.INFO) + # Fresh events for every session, so they bind to the current event loop and start unset + self.server_ready = asyncio.Event() + self.completions_available = asyncio.Event() + self.server.on_request("client/registerCapability", do_nothing) self.server.on_notification("language/status", do_nothing) self.server.on_notification("window/logMessage", window_log_message) @@ -174,3 +178,5 @@ async def window_log_message(msg): finally: await self.server.shutdown() await self.server.stop() + self.server_ready = asyncio.Event() + self.completions_available = asyncio.Event() diff --git a/src/multilspy/language_servers/typescript_language_server/typescript_language_server.py b/src/multilspy/language_servers/typescript_language_server/typescript_language_server.py index d9d8a12..1b11f5f 100644 --- a/src/multilspy/language_servers/typescript_language_server/typescript_language_server.py +++ b/src/multilspy/language_servers/typescript_language_server/typescript_language_server.py @@ -158,6 +158,10 @@ async def do_nothing(params): async def window_log_message(msg): self.logger.log(f"LSP: window/logMessage: {msg}", logging.INFO) + # Fresh events for every session, so they bind to the current event loop and start unset + self.server_ready = asyncio.Event() + self.completions_available = asyncio.Event() + self.server.on_request("client/registerCapability", do_nothing) self.server.on_notification("window/logMessage", window_log_message) self.server.on_request("workspace/executeClientCommand", execute_client_command_handler) @@ -194,3 +198,5 @@ async def window_log_message(msg): finally: await self.server.shutdown() await self.server.stop() + self.server_ready = asyncio.Event() + self.completions_available = asyncio.Event() diff --git a/tests/multilspy/test_multilspy_restart.py b/tests/multilspy/test_multilspy_restart.py new file mode 100644 index 0000000..164cd94 --- /dev/null +++ b/tests/multilspy/test_multilspy_restart.py @@ -0,0 +1,41 @@ +""" +This file contains a test for starting the same language server twice (typescript-language-server) +""" + +from multilspy import SyncLanguageServer +from multilspy.multilspy_config import Language +from tests.test_utils import create_test_context +from pathlib import PurePath + + +def test_sync_multilspy_typescript_start_server_twice() -> None: + """ + Test that start_server can be entered repeatedly on the same SyncLanguageServer + """ + code_language = Language.TYPESCRIPT + params = { + "code_language": code_language, + "repo_url": "https://github.com/trpc/trpc/", + "repo_commit": "936db6dd2598337758e29c843ff66984ed54faaf" + } + with create_test_context(params) as context: + lsp = SyncLanguageServer.create(context.config, context.logger, context.source_directory) + path = str(PurePath("packages/server/src/core/router.ts")) + + with lsp.start_server(): + first_result = lsp.request_definition(path, 194, 8) + first_server_ready = lsp.language_server.server_ready + first_completions_available = lsp.language_server.completions_available + + # The readiness events are the root cause of the bug: they must not stay set after the session ends + assert not lsp.language_server.server_ready.is_set() + assert not lsp.language_server.completions_available.is_set() + assert lsp.language_server.server_ready is not first_server_ready + assert lsp.language_server.completions_available is not first_completions_available + + with lsp.start_server(): + second_result = lsp.request_definition(path, 194, 8) + + assert isinstance(first_result, list) + assert len(first_result) == 1 + assert second_result == first_result