Skip to content

Commit 596e4f5

Browse files
committed
optimize workspace diagnostics
1 parent 2c6c11c commit 596e4f5

File tree

4 files changed

+97
-34
lines changed

4 files changed

+97
-34
lines changed

robotcode/language_server/common/parts/diagnostics.py

Lines changed: 48 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@
1414
async_tasking_event,
1515
async_tasking_event_iterator,
1616
create_sub_task,
17-
threaded,
1817
)
1918
from ....utils.logging import LoggingDescriptor
2019
from ....utils.uri import Uri
@@ -92,6 +91,7 @@ def __init__(self, protocol: LanguageServerProtocol) -> None:
9291
self._current_workspace_task: Optional[asyncio.Task[WorkspaceDiagnosticReport]] = None
9392
self.in_get_document_diagnostics = Event(True)
9493
self.in_get_workspace_diagnostics = Event(True)
94+
self._collect_full_diagnostics = False
9595

9696
def extend_capabilities(self, capabilities: ServerCapabilities) -> None:
9797
if (
@@ -106,12 +106,17 @@ def extend_capabilities(self, capabilities: ServerCapabilities) -> None:
106106
work_done_progress=True,
107107
)
108108

109-
@async_tasking_event_iterator
110-
async def collect(sender, document: TextDocument) -> DiagnosticsResult: # NOSONAR
111-
...
109+
@property
110+
def collect_full_diagnostics(self) -> bool:
111+
return self._collect_full_diagnostics
112+
113+
async def set_collect_full_diagnostics(self, value: bool) -> None:
114+
if self._collect_full_diagnostics != value:
115+
self._collect_full_diagnostics = value
116+
await self.refresh()
112117

113118
@async_tasking_event_iterator
114-
async def collect_stage2(sender, document: TextDocument) -> DiagnosticsResult: # NOSONAR
119+
async def collect(sender, document: TextDocument, full: bool) -> DiagnosticsResult: # NOSONAR
115120
...
116121

117122
@async_tasking_event
@@ -153,6 +158,8 @@ async def ensure_workspace_loaded(self) -> None:
153158
await self.on_workspace_loaded(self)
154159

155160
async def get_document_diagnostics(self, document: TextDocument) -> RelatedFullDocumentDiagnosticReport:
161+
if self.collect_full_diagnostics:
162+
return await document.get_cache(self.__get_full_document_diagnostics)
156163
return await document.get_cache(self.__get_document_diagnostics)
157164

158165
async def __get_document_diagnostics(self, document: TextDocument) -> RelatedFullDocumentDiagnosticReport:
@@ -163,6 +170,29 @@ async def __get_document_diagnostics(self, document: TextDocument) -> RelatedFul
163170
async for result_any in self.collect(
164171
self,
165172
document,
173+
full=False,
174+
callback_filter=language_id_filter(document),
175+
return_exceptions=True,
176+
):
177+
result = cast(DiagnosticsResult, result_any)
178+
179+
if isinstance(result, BaseException):
180+
if not isinstance(result, asyncio.CancelledError):
181+
self._logger.exception(result, exc_info=result)
182+
else:
183+
diagnostics.extend(result.diagnostics or [])
184+
185+
return RelatedFullDocumentDiagnosticReport(items=diagnostics, result_id=str(uuid.uuid4()))
186+
187+
async def __get_full_document_diagnostics(self, document: TextDocument) -> RelatedFullDocumentDiagnosticReport:
188+
await self.ensure_workspace_loaded()
189+
190+
diagnostics: List[Diagnostic] = []
191+
192+
async for result_any in self.collect(
193+
self,
194+
document,
195+
full=True,
166196
callback_filter=language_id_filter(document),
167197
return_exceptions=True,
168198
):
@@ -177,7 +207,6 @@ async def __get_document_diagnostics(self, document: TextDocument) -> RelatedFul
177207
return RelatedFullDocumentDiagnosticReport(items=diagnostics, result_id=str(uuid.uuid4()))
178208

179209
@rpc_method(name="textDocument/diagnostic", param_type=DocumentDiagnosticParams)
180-
@threaded()
181210
async def _text_document_diagnostic(
182211
self,
183212
text_document: TextDocumentIdentifier,
@@ -236,7 +265,6 @@ async def _get_diagnostics() -> Optional[RelatedFullDocumentDiagnosticReport]:
236265
self._logger.debug(lambda: f"textDocument/diagnostic ready {text_document}")
237266

238267
@rpc_method(name="workspace/diagnostic", param_type=WorkspaceDiagnosticParams)
239-
@threaded()
240268
async def _workspace_diagnostic(
241269
self,
242270
identifier: Optional[str],
@@ -272,7 +300,9 @@ async def _get_diagnostics() -> WorkspaceDiagnosticReport:
272300

273301
async def _get_partial_diagnostics() -> WorkspaceDiagnosticReport:
274302
async with self.parent.window.progress(
275-
"Analyse Workspace", progress_token=work_done_token, cancellable=False
303+
f"Analyse {'full ' if self.collect_full_diagnostics else ''} Workspace",
304+
progress_token=work_done_token,
305+
cancellable=False,
276306
) as progress:
277307

278308
async def _task(doc: TextDocument) -> None:
@@ -285,7 +315,7 @@ async def _task(doc: TextDocument) -> None:
285315
else:
286316
name = path.relative_to(folder.uri.to_path())
287317

288-
progress.report(f"Analyse {name}")
318+
progress.report(f"Analyse {'full ' if self.collect_full_diagnostics else ''} {name}")
289319

290320
doc_result = await self.get_document_diagnostics(doc)
291321

@@ -332,7 +362,9 @@ async def _task(doc: TextDocument) -> None:
332362
task = create_sub_task(_get_diagnostics() if partial_result_token is None else _get_partial_diagnostics())
333363
self._current_workspace_task = task
334364
try:
335-
return await task
365+
result = await task
366+
await self.set_collect_full_diagnostics(True)
367+
return result
336368
except asyncio.CancelledError as e:
337369
self._logger.debug("workspace/diagnostic canceled")
338370
raise JsonRPCErrorException(
@@ -350,6 +382,12 @@ def cancel_workspace_diagnostics(self) -> None:
350382
if self._current_workspace_task is not None and not self._current_workspace_task.done():
351383
self._current_workspace_task.cancel()
352384

385+
def cancel_document_diagnostics(self, document: TextDocument) -> None:
386+
task = self._current_document_tasks.get(document, None)
387+
if task is not None:
388+
self._logger.critical(lambda: f"textDocument/diagnostic canceled {document}")
389+
task.cancel()
390+
353391
async def get_analysis_progress_mode(self, uri: Uri) -> AnalysisProgressMode:
354392
for e in await self.on_get_analysis_progress_mode(self, uri):
355393
if e is not None:

robotcode/language_server/robotframework/parts/diagnostics.py

Lines changed: 24 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -43,18 +43,23 @@ def __init__(self, parent: RobotLanguageServerProtocol) -> None:
4343

4444
parent.diagnostics.collect.add(self.collect_namespace_diagnostics)
4545

46-
parent.diagnostics.collect_stage2.add(self.collect_unused_references)
46+
parent.diagnostics.collect.add(self.collect_unused_references)
4747

4848
parent.documents_cache.namespace_invalidated.add(self.namespace_invalidated)
4949

5050
@language_id("robotframework")
5151
async def namespace_invalidated(self, sender: Any, document: TextDocument) -> None:
5252
self.parent.diagnostics.cancel_workspace_diagnostics()
53+
self.parent.diagnostics.cancel_document_diagnostics(document)
54+
await self.parent.diagnostics.set_collect_full_diagnostics(False)
5355

5456
@language_id("robotframework")
5557
@threaded()
5658
@_logger.call
57-
async def collect_namespace_diagnostics(self, sender: Any, document: TextDocument) -> DiagnosticsResult:
59+
async def collect_namespace_diagnostics(self, sender: Any, document: TextDocument, full: bool) -> DiagnosticsResult:
60+
return await document.get_cache(self._collect_namespace_diagnostics, full)
61+
62+
async def _collect_namespace_diagnostics(self, document: TextDocument, full: bool) -> DiagnosticsResult:
5863
try:
5964
namespace = await self.parent.documents_cache.get_namespace(document)
6065
if namespace is None:
@@ -120,7 +125,10 @@ def _create_error_from_token(self, token: Token, source: Optional[str] = None) -
120125
@language_id("robotframework")
121126
@threaded()
122127
@_logger.call
123-
async def collect_token_errors(self, sender: Any, document: TextDocument) -> DiagnosticsResult:
128+
async def collect_token_errors(self, sender: Any, document: TextDocument, full: bool) -> DiagnosticsResult:
129+
return await document.get_cache(self._collect_token_errors, full)
130+
131+
async def _collect_token_errors(self, document: TextDocument, full: bool) -> DiagnosticsResult:
124132
from robot.errors import VariableError
125133
from robot.parsing.lexer.tokens import Token
126134

@@ -186,7 +194,10 @@ async def collect_token_errors(self, sender: Any, document: TextDocument) -> Dia
186194
@language_id("robotframework")
187195
@threaded()
188196
@_logger.call
189-
async def collect_model_errors(self, sender: Any, document: TextDocument) -> DiagnosticsResult:
197+
async def collect_model_errors(self, sender: Any, document: TextDocument, full: bool) -> DiagnosticsResult:
198+
return await document.get_cache(self._collect_model_errors, full)
199+
200+
async def _collect_model_errors(self, document: TextDocument, full: bool) -> DiagnosticsResult:
190201

191202
from ..utils.ast_utils import HasError, HasErrors
192203
from ..utils.async_ast import iter_nodes
@@ -235,7 +246,15 @@ async def collect_model_errors(self, sender: Any, document: TextDocument) -> Dia
235246
@language_id("robotframework")
236247
@threaded()
237248
@_logger.call
238-
async def collect_unused_references(self, sender: Any, document: TextDocument) -> DiagnosticsResult:
249+
async def collect_unused_references(self, sender: Any, document: TextDocument, full: bool) -> DiagnosticsResult:
250+
if not full:
251+
return DiagnosticsResult(self.collect_unused_references, [])
252+
return await document.get_cache(self._collect_unused_references, full)
253+
254+
async def _collect_unused_references(self, document: TextDocument, full: bool) -> DiagnosticsResult:
255+
if not full:
256+
return DiagnosticsResult(self.collect_unused_references, [])
257+
239258
try:
240259
namespace = await self.parent.documents_cache.get_namespace(document)
241260
if namespace is None:

robotcode/language_server/robotframework/parts/robocop_diagnostics.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ async def get_config(self, document: TextDocument) -> Optional[RoboCopConfig]:
4747
@language_id("robotframework")
4848
@threaded()
4949
@_logger.call
50-
async def collect_diagnostics(self, sender: Any, document: TextDocument) -> DiagnosticsResult:
50+
async def collect_diagnostics(self, sender: Any, document: TextDocument, full: bool) -> DiagnosticsResult:
5151

5252
workspace_folder = self.parent.workspace.get_workspace_folder(document.uri)
5353
if workspace_folder is not None:

robotcode/utils/async_tools.py

Lines changed: 24 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -494,7 +494,7 @@ def run() -> _T:
494494
try:
495495
running_tasks = asyncio.all_tasks(loop)
496496
if running_tasks:
497-
loop.run_until_complete(asyncio.gather(*running_tasks, loop=loop, return_exceptions=True))
497+
loop.run_until_complete(asyncio.gather(*running_tasks, return_exceptions=True))
498498

499499
loop.run_until_complete(loop.shutdown_asyncgens())
500500
finally:
@@ -601,27 +601,25 @@ def __repr__(self) -> str:
601601
return f"<{res[1:-1]} [{extra}]>"
602602

603603
def _wake_up_next(self) -> None:
604-
while self._waiters:
605-
with self._lock:
604+
with self._lock:
605+
while self._waiters:
606606
waiter = self._waiters.popleft()
607607

608-
if not waiter.done():
609-
if waiter._loop == asyncio.get_running_loop():
610-
if not waiter.done():
611-
waiter.set_result(True)
612-
else:
613-
if waiter._loop.is_running():
614-
615-
def s(w: asyncio.Future[Any]) -> None:
616-
if not w.done():
617-
w.set_result(True)
618-
619-
if not waiter.done():
620-
waiter._loop.call_soon_threadsafe(s, waiter)
621-
else:
622-
warnings.warn("Loop is not running.")
608+
if not waiter.done():
609+
if waiter._loop == asyncio.get_running_loop():
623610
if not waiter.done():
624611
waiter.set_result(True)
612+
else:
613+
if waiter._loop.is_running():
614+
615+
def s(w: asyncio.Future[Any]) -> None:
616+
if w._loop.is_running() and not w.done():
617+
w.set_result(True)
618+
619+
if not waiter.done():
620+
waiter._loop.call_soon_threadsafe(s, waiter)
621+
else:
622+
warnings.warn("Loop is not running.")
625623

626624
def locked(self) -> bool:
627625
with self._lock:
@@ -688,6 +686,10 @@ async def acquire(self, timeout: Optional[float] = None) -> bool:
688686
def release(self) -> None:
689687
self._block.release()
690688

689+
@property
690+
def locked(self) -> bool:
691+
return self._block.locked()
692+
691693
async def __aenter__(self) -> None:
692694
await self.acquire()
693695

@@ -734,6 +736,10 @@ async def __inner_lock(self) -> AsyncGenerator[Any, None]:
734736
# with self._lock:
735737
# yield None
736738

739+
@property
740+
def locked(self) -> bool:
741+
return self._locked
742+
737743
async def acquire(self) -> bool:
738744
async with self.__inner_lock():
739745
if not self._locked and (self._waiters is None or all(w.cancelled() for w in self._waiters)):

0 commit comments

Comments
 (0)