Skip to content

Commit 37d8119

Browse files
committed
fix(langserver): langserver that sometimes hangs, 'loop is not running' or 'lock take to long' messages
Rewrote and refactor diagnostics and refreshing part to use normal threads and not async. This should also bring a better performance for bigger projects, but maybe this needs some more optimizations.
1 parent d75eb9a commit 37d8119

File tree

181 files changed

+115870
-401
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

181 files changed

+115870
-401
lines changed

.vscode/launch.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,9 @@
113113
//"./tests/robotcode/language_server/robotframework/parts/test_discovering.py::test_workspace_discovery"
114114
//"tests/robotcode/language_server/robotframework/parts/test_document_highlight.py"
115115
//"tests/robotcode/utils/test_dataclasses.py::test_really_complex_data"
116-
"tests/robotcode/language_server/robotframework/parts/test_foldingrange.py"
116+
//"tests/robotcode/language_server/robotframework/parts/test_foldingrange.py"
117+
//"tests/robotcode/language_server/robotframework/parts/test_document_symbols.py",
118+
"./tests/robotcode/language_server/robotframework/parts/test_semantic_tokens.py "
117119
],
118120
"console": "integratedTerminal",
119121
"justMyCode": false,

packages/core/src/robotcode/core/concurrent.py

Lines changed: 99 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,107 @@
1+
import contextlib
12
import inspect
3+
import threading
24
from concurrent.futures import CancelledError, Future
3-
from threading import Event, RLock, Thread, current_thread, local
5+
from types import TracebackType
46
from typing import (
57
Any,
68
Callable,
79
Dict,
810
Generic,
11+
Iterator,
912
List,
1013
Optional,
14+
Protocol,
1115
Tuple,
16+
Type,
1217
TypeVar,
1318
cast,
1419
overload,
1520
)
1621

17-
from typing_extensions import ParamSpec
22+
from typing_extensions import ParamSpec, Self
23+
24+
25+
class Lockable(Protocol):
26+
def acquire(self, blocking: bool = True, timeout: float = -1) -> bool:
27+
...
28+
29+
def release(self) -> None:
30+
...
31+
32+
def __enter__(self) -> bool:
33+
...
34+
35+
def __exit__(
36+
self, exc_type: Optional[Type[BaseException]], exc_val: Optional[BaseException], exc_tb: Optional[TracebackType]
37+
) -> None:
38+
...
39+
40+
def __str__(self) -> str:
41+
...
42+
43+
44+
class LockBase:
45+
def __init__(
46+
self,
47+
lock: Lockable,
48+
default_timeout: Optional[float] = None,
49+
name: Optional[str] = None,
50+
) -> None:
51+
self._default_timeout = default_timeout
52+
self.name = name
53+
self._lock = threading.RLock()
54+
55+
def acquire(self, blocking: bool = True, timeout: Optional[float] = None) -> bool:
56+
timeout = timeout if timeout is not None else self._default_timeout or -1
57+
aquired = self._lock.acquire(blocking, timeout=timeout)
58+
if not aquired and blocking and timeout > 0:
59+
raise RuntimeError(
60+
f"Could not acquire {self.__class__.__qualname__} {self.name+' ' if self.name else ' '}in {timeout}s."
61+
)
62+
return aquired
63+
64+
def release(self) -> None:
65+
return self._lock.release()
66+
67+
def __enter__(self) -> bool:
68+
return self.acquire()
69+
70+
def __exit__(
71+
self, exc_type: Optional[Type[BaseException]], exc_val: Optional[BaseException], exc_tb: Optional[TracebackType]
72+
) -> None:
73+
return self.release()
74+
75+
def __str__(self) -> str:
76+
return self._lock.__str__()
77+
78+
@contextlib.contextmanager
79+
def __call__(self, *, timeout: Optional[float] = None) -> Iterator["Self"]:
80+
aquired = self.acquire(timeout=timeout)
81+
try:
82+
yield self
83+
finally:
84+
if aquired:
85+
self.release()
86+
87+
88+
class RLock(LockBase):
89+
def __init__(
90+
self,
91+
default_timeout: Optional[float] = None,
92+
name: Optional[str] = None,
93+
) -> None:
94+
super().__init__(threading.RLock(), default_timeout=default_timeout, name=name)
95+
96+
97+
class Lock(LockBase):
98+
def __init__(
99+
self,
100+
default_timeout: Optional[float] = None,
101+
name: Optional[str] = None,
102+
) -> None:
103+
super().__init__(threading.Lock(), default_timeout=default_timeout, name=name)
104+
18105

19106
_F = TypeVar("_F", bound=Callable[..., Any])
20107
_TResult = TypeVar("_TResult")
@@ -25,7 +112,7 @@
25112
class Task(Future, Generic[_TResult]): # type: ignore[type-arg]
26113
def __init__(self) -> None:
27114
super().__init__()
28-
self.cancelation_requested_event = Event()
115+
self.cancelation_requested_event = threading.Event()
29116

30117
@property
31118
def cancelation_requested(self) -> bool:
@@ -43,16 +130,16 @@ def add_done_callback(self, fn: Callable[["Task[Any]"], Any]) -> None:
43130

44131

45132
@overload
46-
def threaded(__func: _F) -> _F:
133+
def threaded_task(__func: _F) -> _F:
47134
...
48135

49136

50137
@overload
51-
def threaded(*, enabled: bool = True) -> Callable[[_F], _F]:
138+
def threaded_task(*, enabled: bool = True) -> Callable[[_F], _F]:
52139
...
53140

54141

55-
def threaded(__func: _F = None, *, enabled: bool = True) -> Callable[[_F], _F]:
142+
def threaded_task(__func: _F = None, *, enabled: bool = True) -> Callable[[_F], _F]:
56143
def decorator(func: _F) -> _F:
57144
setattr(func, __THREADED_MARKER, enabled)
58145
return func
@@ -71,7 +158,7 @@ def is_threaded_callable(callable: Callable[..., Any]) -> bool:
71158
)
72159

73160

74-
class _Local(local):
161+
class _Local(threading.local):
75162
def __init__(self) -> None:
76163
super().__init__()
77164
self._local_future: Optional[Task[Any]] = None
@@ -119,14 +206,14 @@ def check_current_task_canceled(at_least_seconds: Optional[float] = None, raise_
119206
return False
120207

121208
if raise_exception:
122-
name = current_thread().name
209+
name = threading.current_thread().name
123210
raise CancelledError(f"Thread {name + ' ' if name else ' '}cancelled")
124211

125212
return True
126213

127214

128215
_running_tasks_lock = RLock()
129-
_running_tasks: Dict[Task[Any], Thread] = {}
216+
_running_tasks: Dict[Task[Any], threading.Thread] = {}
130217

131218

132219
def _remove_future_from_running_tasks(future: Task[Any]) -> None:
@@ -140,7 +227,7 @@ def _remove_future_from_running_tasks(future: Task[Any]) -> None:
140227
def run_as_task(callable: Callable[_P, _TResult], *args: _P.args, **kwargs: _P.kwargs) -> Task[_TResult]:
141228
future: Task[_TResult] = Task()
142229
with _running_tasks_lock:
143-
thread = Thread(
230+
thread = threading.Thread(
144231
target=_run_task_in_thread_handler,
145232
args=(future, callable, args, kwargs),
146233
name=str(callable),
@@ -155,12 +242,12 @@ def run_as_task(callable: Callable[_P, _TResult], *args: _P.args, **kwargs: _P.k
155242

156243

157244
def _cancel_all_running_tasks(timeout: Optional[float] = None) -> None:
158-
threads: List[Thread] = []
245+
threads: List[threading.Thread] = []
159246
with _running_tasks_lock:
160247
for future, thread in _running_tasks.items():
161248
if not future.cancelation_requested:
162249
future.cancel()
163250
threads.append(thread)
164251
for thread in threads:
165-
if thread is not current_thread():
252+
if thread is not threading.current_thread():
166253
thread.join(timeout=timeout)

packages/core/src/robotcode/core/event.py

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -31,16 +31,16 @@ def __init__(self) -> None:
3131

3232
self._listeners: MutableSet[weakref.ref[Any]] = set()
3333

34-
def add(self, callback: Callable[_TParams, _TResult]) -> None:
35-
def remove_listener(ref: Any) -> None:
36-
with self._lock:
37-
self._listeners.remove(ref)
34+
def __remove_listener(self, ref: Any) -> None:
35+
with self._lock:
36+
self._listeners.remove(ref)
3837

38+
def add(self, callback: Callable[_TParams, _TResult]) -> None:
3939
with self._lock:
4040
if inspect.ismethod(callback):
41-
self._listeners.add(weakref.WeakMethod(callback, remove_listener))
41+
self._listeners.add(weakref.WeakMethod(callback, self.__remove_listener))
4242
else:
43-
self._listeners.add(weakref.ref(callback, remove_listener))
43+
self._listeners.add(weakref.ref(callback, self.__remove_listener))
4444

4545
def remove(self, callback: Callable[_TParams, _TResult]) -> None:
4646
with self._lock:
@@ -155,7 +155,3 @@ def __init__(self, _func: Callable[_TParams, _TResult]) -> None:
155155
class event(EventDescriptorBase[_TParams, _TResult, Event[_TParams, _TResult]]): # noqa: N801
156156
def __init__(self, _func: Callable[_TParams, _TResult]) -> None:
157157
super().__init__(_func, Event[_TParams, _TResult])
158-
159-
160-
# TODO implement this as something with concurrent.futures.Future
161-
tasking_event = event

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -498,8 +498,8 @@ def build_exception_message(exception: BaseException) -> str:
498498

499499
result = None
500500
try:
501-
start_time: float = 0
502-
end_time: float = 0
501+
start_time: float = 0.0
502+
end_time: float = 0.0
503503
if timed:
504504
start_time = time.monotonic()
505505

packages/jsonrpc2/src/robotcode/jsonrpc2/protocol.py

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -868,15 +868,12 @@ async def handle_notification(self, message: JsonRPCNotification) -> None:
868868

869869
class GenericJsonRPCProtocolPart(Generic[TProtocol]):
870870
def __init__(self, parent: TProtocol) -> None:
871-
self._parent = weakref.ref(parent)
871+
self._parent = parent
872872
parent.registry.add_class_part_instance(self)
873873

874874
@property
875875
def parent(self) -> TProtocol:
876-
result = self._parent()
877-
if result is None:
878-
raise JsonRPCException("WeakRef is dead.")
879-
return result
876+
return self._parent
880877

881878

882879
class JsonRPCProtocolPart(GenericJsonRPCProtocolPart[JsonRPCProtocol]):

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

Lines changed: 20 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
1+
import threading
12
from concurrent.futures import CancelledError
23
from typing import TYPE_CHECKING, Any, Final, List, Optional
34

4-
from robotcode.core.concurrent import Task, check_current_task_canceled, run_as_task
5+
from robotcode.core.concurrent import check_current_task_canceled
56
from robotcode.core.event import event
67
from robotcode.core.lsp.types import (
78
CodeLens,
@@ -26,8 +27,8 @@ class CodeLensProtocolPart(LanguageServerProtocolPart):
2627

2728
def __init__(self, parent: "LanguageServerProtocol") -> None:
2829
super().__init__(parent)
29-
self.refresh_task: Optional[Task[Any]] = None
30-
self._refresh_timeout = 5
30+
self.refresh_timer_lock = threading.RLock()
31+
self.refresh_timer: Optional[threading.Timer] = None
3132

3233
@event
3334
def collect(sender, document: TextDocument) -> Optional[List[CodeLens]]:
@@ -88,20 +89,27 @@ def _code_lens_resolve(self, params: CodeLens, *args: Any, **kwargs: Any) -> Cod
8889

8990
return params
9091

91-
def refresh(self, now: bool = True) -> None:
92-
if self.refresh_task is not None and not self.refresh_task.done():
93-
self.refresh_task.cancel()
92+
def refresh(self, now: bool = False) -> None:
93+
with self.refresh_timer_lock:
94+
if self.refresh_timer is not None:
95+
self.refresh_timer.cancel()
96+
self.refresh_timer = None
9497

95-
self.refresh_task = run_as_task(self._refresh, now)
98+
if not now:
99+
self.refresh_timer = threading.Timer(1, self._refresh)
100+
self.refresh_timer.start()
101+
return
102+
103+
self._refresh()
104+
105+
def _refresh(self) -> None:
106+
with self.refresh_timer_lock:
107+
self.refresh_timer = None
96108

97-
def _refresh(self, now: bool = True) -> None:
98109
if (
99110
self.parent.client_capabilities is not None
100111
and self.parent.client_capabilities.workspace is not None
101112
and self.parent.client_capabilities.workspace.code_lens is not None
102113
and self.parent.client_capabilities.workspace.code_lens.refresh_support
103114
):
104-
if not now:
105-
check_current_task_canceled(1)
106-
107-
self.parent.send_request("workspace/codeLens/refresh").result(self._refresh_timeout)
115+
self.parent.send_request("workspace/codeLens/refresh")

0 commit comments

Comments
 (0)