Skip to content

feat: Add optional timeout parameter to SyncLanguageServer #66

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Feb 28, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 10 additions & 9 deletions src/multilspy/language_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
from .multilspy_exceptions import MultilspyException
from .multilspy_utils import PathUtils, FileUtils, TextUtils
from pathlib import PurePath
from typing import AsyncIterator, Iterator, List, Dict, Union, Tuple
from typing import AsyncIterator, Iterator, List, Dict, Optional, Union, Tuple
from .type_helpers import ensure_all_methods_implemented


Expand Down Expand Up @@ -656,14 +656,15 @@ class SyncLanguageServer:
It is used to communicate with Language Servers of different programming languages.
"""

def __init__(self, language_server: LanguageServer) -> None:
def __init__(self, language_server: LanguageServer, timeout: Optional[int] = None):
self.language_server = language_server
self.loop = None
self.loop_thread = None
self.timeout = timeout

@classmethod
def create(
cls, config: MultilspyConfig, logger: MultilspyLogger, repository_root_path: str
cls, config: MultilspyConfig, logger: MultilspyLogger, repository_root_path: str, timeout: Optional[int] = None
) -> "SyncLanguageServer":
"""
Creates a language specific LanguageServer instance based on the given configuration, and appropriate settings for the programming language.
Expand All @@ -676,7 +677,7 @@ def create(

:return SyncLanguageServer: A language specific LanguageServer instance.
"""
return SyncLanguageServer(LanguageServer.create(config, logger, repository_root_path))
return SyncLanguageServer(LanguageServer.create(config, logger, repository_root_path), timeout=timeout)

@contextmanager
def open_file(self, relative_file_path: str) -> Iterator[None]:
Expand Down Expand Up @@ -751,7 +752,7 @@ def request_definition(self, file_path: str, line: int, column: int) -> List[mul
"""
result = asyncio.run_coroutine_threadsafe(
self.language_server.request_definition(file_path, line, column), self.loop
).result()
).result(timeout=self.timeout)
return result

def request_references(self, file_path: str, line: int, column: int) -> List[multilspy_types.Location]:
Expand All @@ -767,7 +768,7 @@ def request_references(self, file_path: str, line: int, column: int) -> List[mul
"""
result = asyncio.run_coroutine_threadsafe(
self.language_server.request_references(file_path, line, column), self.loop
).result()
).result(timeout=self.timeout)
return result

def request_completions(
Expand All @@ -786,7 +787,7 @@ def request_completions(
result = asyncio.run_coroutine_threadsafe(
self.language_server.request_completions(relative_file_path, line, column, allow_incomplete),
self.loop,
).result()
).result(timeout=self.timeout)
return result

def request_document_symbols(self, relative_file_path: str) -> Tuple[List[multilspy_types.UnifiedSymbolInformation], Union[List[multilspy_types.TreeRepr], None]]:
Expand All @@ -800,7 +801,7 @@ def request_document_symbols(self, relative_file_path: str) -> Tuple[List[multil
"""
result = asyncio.run_coroutine_threadsafe(
self.language_server.request_document_symbols(relative_file_path), self.loop
).result()
).result(timeout=self.timeout)
return result

def request_hover(self, relative_file_path: str, line: int, column: int) -> Union[multilspy_types.Hover, None]:
Expand All @@ -816,5 +817,5 @@ def request_hover(self, relative_file_path: str, line: int, column: int) -> Unio
"""
result = asyncio.run_coroutine_threadsafe(
self.language_server.request_hover(relative_file_path, line, column), self.loop
).result()
).result(timeout=self.timeout)
return result
34 changes: 34 additions & 0 deletions tests/multilspy/test_sync_multilspy_timeout.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
"""
This file contains tests for running the Python Language Server: jedi-language-server
"""

import pytest
from multilspy import SyncLanguageServer
from multilspy.multilspy_config import Language
from tests.test_utils import create_test_context
from pathlib import PurePath
import time

def test_multilspy_timeout() -> None:
"""
Test timeout error in multilspy
"""
code_language = Language.PYTHON
params = {
"code_language": code_language,
"repo_url": "https://github.com/psf/black/",
"repo_commit": "f3b50e466969f9142393ec32a4b2a383ffbe5f23"
}
with create_test_context(params) as context:
lsp = SyncLanguageServer.create(context.config, context.logger, context.source_directory, timeout=1)

# Mock the request_definition method to simulate a long running process
async def request_definition(*args, **kwargs):
time.sleep(5)
return []

lsp.language_server.request_definition = request_definition

with lsp.start_server():
with pytest.raises(TimeoutError):
lsp.request_definition(str(PurePath("src/black/mode.py")), 163, 4)
Loading