Skip to content

Commit 5009202

Browse files
committed
refactor(langserver): remove async code from completions
1 parent 87cb416 commit 5009202

File tree

3 files changed

+165
-181
lines changed

3 files changed

+165
-181
lines changed

packages/core/src/robotcode/core/utils/threading.py

Lines changed: 24 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import inspect
22
from concurrent.futures import CancelledError, Future
3-
from threading import Thread, current_thread, local
3+
from threading import Event, Thread, current_thread, local
44
from typing import Any, Callable, Dict, Generic, Optional, Tuple, TypeVar, cast, overload
55

66
_F = TypeVar("_F", bound=Callable[..., Any])
@@ -12,10 +12,10 @@
1212
class FutureEx(Future, Generic[_TResult]): # type: ignore[type-arg]
1313
def __init__(self) -> None:
1414
super().__init__()
15-
self.cancelation_requested = False
15+
self.cancelation_requested = Event()
1616

1717
def cancel(self) -> bool:
18-
self.cancelation_requested = True
18+
self.cancelation_requested.set()
1919
return super().cancel()
2020

2121
def result(self, timeout: Optional[float] = None) -> _TResult:
@@ -66,23 +66,36 @@ def _run_callable_in_thread_handler(
6666
_local_storage._local_future = future
6767
future.set_running_or_notify_cancel()
6868
try:
69-
future.set_result(callable(*args, **kwargs))
69+
result = callable(*args, **kwargs)
7070
except Exception as e:
7171
# TODO: add traceback to exception e.traceback = format_exc()
72-
73-
future.set_exception(e)
72+
if not future.cancelled():
73+
future.set_exception(e)
74+
else:
75+
if not future.cancelled():
76+
future.set_result(result)
7477
finally:
7578
_local_storage._local_future = None
7679

7780

7881
def is_thread_cancelled() -> bool:
79-
return _local_storage._local_future is not None and _local_storage._local_future.cancelation_requested
82+
local_future = _local_storage._local_future
83+
return local_future is not None and local_future.cancelation_requested.is_set()
84+
85+
86+
def check_thread_canceled(seconds: Optional[float] = None) -> None:
87+
local_future = _local_storage._local_future
88+
if local_future is None:
89+
return
8090

91+
if seconds is None or seconds <= 0:
92+
if not local_future.cancelation_requested.is_set():
93+
return
94+
elif not local_future.cancelation_requested.wait(seconds):
95+
return
8196

82-
def check_thread_canceled() -> None:
83-
if _local_storage._local_future is not None and _local_storage._local_future.cancelation_requested:
84-
name = current_thread().name
85-
raise CancelledError(f"Thread {name+' ' if name else ' '}Cancelled")
97+
name = current_thread().name
98+
raise CancelledError(f"Thread {name+' ' if name else ' '}cancelled")
8699

87100

88101
def run_callable_in_thread(callable: Callable[..., _TResult], *args: Any, **kwargs: Any) -> FutureEx[_TResult]:

packages/language_server/src/robotcode/language_server/common/parts/completion.py

Lines changed: 17 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,8 @@
1-
from __future__ import annotations
2-
3-
import asyncio
4-
from asyncio import CancelledError
1+
from concurrent.futures import CancelledError
52
from itertools import chain
63
from typing import TYPE_CHECKING, Any, Final, List, Optional, Union, cast
74

8-
from robotcode.core.async_tools import async_tasking_event
5+
from robotcode.core.event import event
96
from robotcode.core.lsp.types import (
107
CompletionContext,
118
CompletionItem,
@@ -21,7 +18,7 @@
2118
TextEdit,
2219
)
2320
from robotcode.core.utils.logging import LoggingDescriptor
24-
from robotcode.core.utils.threading import threaded
21+
from robotcode.core.utils.threading import check_thread_canceled, threaded
2522
from robotcode.jsonrpc2.protocol import rpc_method
2623
from robotcode.language_server.common.decorators import (
2724
ALL_COMMIT_CHARACTERS_ATTR,
@@ -40,17 +37,17 @@
4037
class CompletionProtocolPart(LanguageServerProtocolPart):
4138
_logger: Final = LoggingDescriptor()
4239

43-
def __init__(self, parent: LanguageServerProtocol) -> None:
40+
def __init__(self, parent: "LanguageServerProtocol") -> None:
4441
super().__init__(parent)
4542

46-
@async_tasking_event
47-
async def collect(
43+
@event
44+
def collect(
4845
sender, document: TextDocument, position: Position, context: Optional[CompletionContext] # NOSONAR
4946
) -> Union[List[CompletionItem], CompletionList, None]:
5047
...
5148

52-
@async_tasking_event
53-
async def resolve(sender, completion_item: CompletionItem) -> Optional[CompletionItem]: # NOSONAR
49+
@event
50+
def resolve(sender, completion_item: CompletionItem) -> Optional[CompletionItem]: # NOSONAR
5451
...
5552

5653
def extend_capabilities(self, capabilities: ServerCapabilities) -> None:
@@ -84,7 +81,7 @@ def extend_capabilities(self, capabilities: ServerCapabilities) -> None:
8481

8582
@rpc_method(name="textDocument/completion", param_type=CompletionParams)
8683
@threaded
87-
async def _text_document_completion(
84+
def _text_document_completion(
8885
self,
8986
text_document: TextDocumentIdentifier,
9087
position: Position,
@@ -95,21 +92,17 @@ async def _text_document_completion(
9592
results: List[Union[List[CompletionItem], CompletionList]] = []
9693

9794
if context is not None and context.trigger_kind == CompletionTriggerKind.TRIGGER_CHARACTER:
98-
await asyncio.sleep(0.25)
95+
check_thread_canceled(0.25)
9996

10097
document = self.parent.documents.get(text_document.uri)
10198
if document is None:
10299
return None
103100

104101
p = document.position_from_utf16(position)
105102

106-
for result in await self.collect(
107-
self,
108-
document,
109-
p,
110-
context,
111-
callback_filter=language_id_filter(document),
112-
):
103+
for result in self.collect(self, document, p, context, callback_filter=language_id_filter(document)):
104+
check_thread_canceled()
105+
113106
if isinstance(result, BaseException):
114107
if not isinstance(result, CancelledError):
115108
self._logger.exception(result, exc_info=result)
@@ -121,6 +114,8 @@ async def _text_document_completion(
121114
return None
122115

123116
for result in results:
117+
check_thread_canceled()
118+
124119
if isinstance(result, CompletionList):
125120
for item in result.items:
126121
if item.text_edit is not None:
@@ -155,15 +150,15 @@ def update_completion_item_to_utf16(self, document: TextDocument, item: Completi
155150

156151
@rpc_method(name="completionItem/resolve", param_type=CompletionItem)
157152
@threaded
158-
async def _completion_item_resolve(
153+
def _completion_item_resolve(
159154
self,
160155
params: CompletionItem,
161156
*args: Any,
162157
**kwargs: Any,
163158
) -> CompletionItem:
164159
results: List[CompletionItem] = []
165160

166-
for result in await self.resolve(self, params):
161+
for result in self.resolve(self, params):
167162
if isinstance(result, BaseException):
168163
if not isinstance(result, CancelledError):
169164
self._logger.exception(result, exc_info=result)

0 commit comments

Comments
 (0)