Skip to content

Commit 77db502

Browse files
committed
fix(langserver): preventing extensive calls to 'workspace/configuration' through caching
1 parent 3f3944f commit 77db502

File tree

4 files changed

+52
-46
lines changed

4 files changed

+52
-46
lines changed

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

Lines changed: 23 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,24 @@
1-
from __future__ import annotations
2-
3-
import asyncio
41
import threading
52
import uuid
63
import weakref
74
from concurrent.futures import Future
8-
from dataclasses import dataclass
95
from typing import (
106
TYPE_CHECKING,
117
Any,
128
Callable,
9+
ClassVar,
1310
Coroutine,
1411
Dict,
1512
Final,
1613
List,
1714
Mapping,
1815
NamedTuple,
1916
Optional,
20-
Protocol,
2117
Tuple,
2218
Type,
2319
TypeVar,
2420
Union,
2521
cast,
26-
runtime_checkable,
2722
)
2823

2924
from robotcode.core.concurrent import threaded
@@ -125,6 +120,9 @@ def __init__(self, name: str, uri: Uri, document_uri: DocumentUri) -> None:
125120
self.document_uri = document_uri
126121

127122

123+
_F = TypeVar("_F", bound=Callable[..., Any])
124+
125+
128126
def config_section(name: str) -> Callable[[_F], _F]:
129127
def decorator(func: _F) -> _F:
130128
setattr(func, "__config_section__", name)
@@ -133,26 +131,20 @@ def decorator(func: _F) -> _F:
133131
return decorator
134132

135133

136-
@runtime_checkable
137-
class HasConfigSection(Protocol):
138-
__config_section__: str
139-
140-
141-
@dataclass
134+
# @dataclass
142135
class ConfigBase(CamelSnakeMixin):
143-
pass
136+
__config_section__: ClassVar[str]
144137

145138

146139
_TConfig = TypeVar("_TConfig", bound=ConfigBase)
147-
_F = TypeVar("_F", bound=Callable[..., Any])
148140

149141

150142
class Workspace(LanguageServerProtocolPart):
151143
_logger: Final = LoggingDescriptor()
152144

153145
def __init__(
154146
self,
155-
parent: LanguageServerProtocol,
147+
parent: "LanguageServerProtocol",
156148
root_uri: Optional[str],
157149
root_path: Optional[str],
158150
workspace_folders: Optional[List[TypesWorkspaceFolder]] = None,
@@ -174,6 +166,7 @@ def __init__(
174166

175167
self.parent.on_shutdown.add(self.server_shutdown)
176168
self.parent.on_initialize.add(self.server_initialize)
169+
self._settings_cache: Dict[Tuple[Optional[WorkspaceFolder], str], ConfigBase] = {}
177170

178171
def server_initialize(self, sender: Any, initialization_options: Optional[Any] = None) -> None:
179172
if (
@@ -260,6 +253,7 @@ def did_change_configuration(sender, settings: Dict[str, Any]) -> None: # NOSON
260253
@threaded
261254
def _workspace_did_change_configuration(self, settings: Dict[str, Any], *args: Any, **kwargs: Any) -> None:
262255
self.settings = settings
256+
self._settings_cache.clear()
263257
self.did_change_configuration(self, settings)
264258

265259
@event
@@ -333,14 +327,6 @@ def _workspace_will_delete_files(self, files: List[FileDelete], *args: Any, **kw
333327
def _workspace_did_delete_files(self, files: List[FileDelete], *args: Any, **kwargs: Any) -> None:
334328
self.did_delete_files(self, [f.uri for f in files])
335329

336-
def get_configuration_async(
337-
self,
338-
section: Type[_TConfig],
339-
scope_uri: Union[str, Uri, None] = None,
340-
request: bool = True,
341-
) -> asyncio.Future[_TConfig]:
342-
return asyncio.wrap_future(self.get_configuration_future(section, scope_uri, request))
343-
344330
def get_configuration(
345331
self,
346332
section: Type[_TConfig],
@@ -357,6 +343,12 @@ def get_configuration_future(
357343
) -> Future[_TConfig]:
358344
result_future: Future[_TConfig] = Future()
359345

346+
scope = self.get_workspace_folder(scope_uri) if scope_uri is not None else None
347+
348+
if (scope, section.__config_section__) in self._settings_cache:
349+
result_future.set_result(cast(_TConfig, self._settings_cache[(scope, section.__config_section__)]))
350+
return result_future
351+
360352
def _get_configuration_done(f: Future[Optional[Any]]) -> None:
361353
try:
362354
if result_future.cancelled():
@@ -371,12 +363,14 @@ def _get_configuration_done(f: Future[Optional[Any]]) -> None:
371363
return
372364

373365
result = f.result()
374-
result_future.set_result(from_dict(result[0] if result else {}, section))
366+
r = from_dict(result[0] if result else {}, section)
367+
self._settings_cache[(scope, section.__config_section__)] = r
368+
result_future.set_result(r)
375369
except Exception as e:
376370
result_future.set_exception(e)
377371

378372
self.get_configuration_raw(
379-
section=cast(HasConfigSection, section).__config_section__,
373+
section=section.__config_section__,
380374
scope_uri=scope_uri,
381375
request=request,
382376
).add_done_callback(_get_configuration_done)
@@ -453,6 +447,10 @@ def _workspace_did_change_workspace_folders(
453447
for r in to_remove:
454448
self._workspace_folders.remove(r)
455449

450+
settings_to_remove = [k for k in self._settings_cache.keys() if k[0] == r]
451+
for k in settings_to_remove:
452+
self._settings_cache.pop(k, None)
453+
456454
for a in event.added:
457455
self._workspace_folders.append(WorkspaceFolder(a.name, Uri(a.uri), a.uri))
458456

packages/language_server/src/robotcode/language_server/robotframework/parts/references.py

Lines changed: 25 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -207,28 +207,35 @@ def _find_variable_references(
207207
def find_variable_references_in_file(
208208
self, doc: TextDocument, variable: VariableDefinition, include_declaration: bool = True
209209
) -> List[Location]:
210-
namespace = self.parent.documents_cache.get_namespace(doc)
210+
try:
211+
namespace = self.parent.documents_cache.get_namespace(doc)
211212

212-
if (
213-
variable.source
214-
and variable.source != str(doc.uri.to_path())
215-
and not any(e for e in (namespace.get_resources()).values() if e.library_doc.source == variable.source)
216-
and not any(
217-
e for e in namespace.get_imported_variables().values() if e.library_doc.source == variable.source
218-
)
219-
and not any(e for e in namespace.get_command_line_variables() if e.source == variable.source)
220-
):
221-
return []
213+
if (
214+
variable.source
215+
and variable.source != str(doc.uri.to_path())
216+
and not any(e for e in (namespace.get_resources()).values() if e.library_doc.source == variable.source)
217+
and not any(
218+
e for e in namespace.get_imported_variables().values() if e.library_doc.source == variable.source
219+
)
220+
and not any(e for e in namespace.get_command_line_variables() if e.source == variable.source)
221+
):
222+
return []
222223

223-
result = set()
224-
if include_declaration and variable.source:
225-
result.add(Location(str(Uri.from_path(variable.source)), variable.name_range))
224+
result = set()
225+
if include_declaration and variable.source:
226+
result.add(Location(str(Uri.from_path(variable.source)), variable.name_range))
226227

227-
refs = namespace.get_variable_references()
228-
if variable in refs:
229-
result |= refs[variable]
228+
refs = namespace.get_variable_references()
229+
if variable in refs:
230+
result |= refs[variable]
230231

231-
return list(result)
232+
return list(result)
233+
except (SystemExit, KeyboardInterrupt, CancelledError):
234+
raise
235+
except BaseException as e:
236+
self._logger.exception(e)
237+
238+
return []
232239

233240
@_logger.call
234241
def find_keyword_references_in_file(

tests/robotcode/language_server/robotframework/parts/conftest.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
import dataclasses
33
import shutil
44
from pathlib import Path
5-
from typing import AsyncIterator, Iterator, cast
5+
from typing import AsyncIterator, Iterator
66

77
import pytest
88
import pytest_asyncio
@@ -18,7 +18,6 @@
1818
)
1919
from robotcode.core.utils.dataclasses import as_dict
2020
from robotcode.language_server.common.parts.diagnostics import DiagnosticsMode
21-
from robotcode.language_server.common.parts.workspace import HasConfigSection
2221
from robotcode.language_server.common.text_document import TextDocument
2322
from robotcode.language_server.robotframework.configuration import AnalysisConfig, RobotCodeConfig, RobotConfig
2423
from robotcode.language_server.robotframework.protocol import (
@@ -73,7 +72,7 @@ async def protocol(request: pytest.FixtureRequest) -> AsyncIterator[RobotLanguag
7372
)
7473

7574
protocol.workspace.settings = {
76-
cast(HasConfigSection, RobotCodeConfig).__config_section__: as_dict(
75+
RobotCodeConfig.__config_section__: as_dict(
7776
RobotCodeConfig(
7877
robot=RobotConfig(
7978
python_path=["./lib", "./resources"],

vscode-client/extension.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,8 @@ export async function activateAsync(context: vscode.ExtensionContext): Promise<v
126126
"robotcode.analysis",
127127
"robotcode.workspace",
128128
"robotcode.documentationServer",
129+
"robotcode.completion",
130+
"robotcode.inlayHints",
129131
]) {
130132
for (const ws of vscode.workspace.workspaceFolders ?? []) {
131133
if (languageClientManger.clients.has(ws.uri.toString()))

0 commit comments

Comments
 (0)