Skip to content

Commit 0ea3a29

Browse files
committed
optimize namespace diagnostics and canceling diagnostics
1 parent 8c8935c commit 0ea3a29

File tree

6 files changed

+89
-43
lines changed

6 files changed

+89
-43
lines changed

robotcode/language_server/common/parts/diagnostics.py

Lines changed: 36 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -27,35 +27,41 @@ class PublishDiagnosticsEntry:
2727

2828
def __init__(
2929
self,
30-
document_uri: DocumentUri,
30+
uri: Uri,
3131
cancelation_token: CancelationToken,
3232
task_factory: Callable[..., asyncio.Task[Any]],
33+
done_callback: Callable[[PublishDiagnosticsEntry], Any],
3334
) -> None:
3435

35-
self._document_uri = document_uri
36+
self.uri = uri
3637

3738
self._task_factory = task_factory
39+
self.done_callback = done_callback
3840

3941
self._task: Optional[asyncio.Task[Any]] = None
4042

4143
self.cancel_token = cancelation_token
44+
self.done = False
4245

4346
@PublishDiagnosticsEntry._logger.call
4447
def create_task() -> None:
4548
self._task = self._task_factory()
4649

4750
if self._task is not None:
48-
self._task.set_name(f"Diagnostics for {self._document_uri}")
51+
self._task.set_name(f"Diagnostics for {self.uri}")
4952

5053
def _done(t: asyncio.Task[Any]) -> None:
5154
self._task = None
55+
self.done = True
56+
self.done_callback(self)
5257

5358
self._task.add_done_callback(_done)
5459

5560
self._timer_handle: asyncio.TimerHandle = asyncio.get_event_loop().call_later(DIAGNOSTICS_DEBOUNCE, create_task)
5661

5762
def __del__(self) -> None:
58-
self.cancel(_from_del=True)
63+
if self.task is not None:
64+
self.cancel(_from_del=True)
5965

6066
@property
6167
def task(self) -> Optional[asyncio.Task[Any]]:
@@ -65,14 +71,16 @@ def __str__(self) -> str:
6571
return self.__repr__()
6672

6773
def __repr__(self) -> str:
68-
return f"{type(self)}(document={repr(self._document_uri)}, task={repr(self.task)})"
74+
return f"{type(self)}(document={repr(self.uri)}, task={repr(self.task)}, done={self.done})"
6975

7076
@_logger.call(condition=lambda self, _from_del=False: not _from_del)
71-
def cancel(self, *, _from_del: Optional[bool] = False) -> None:
77+
def cancel(self, *, _from_del: Optional[bool] = False) -> Optional[asyncio.Task[None]]:
7278
self._timer_handle.cancel()
7379

7480
if self.task is None:
75-
return
81+
return None
82+
83+
self.cancel_token.cancel()
7684

7785
async def cancel() -> None:
7886
if self.task is None:
@@ -81,7 +89,6 @@ async def cancel() -> None:
8189
t = self.task
8290
self._task = None
8391
if not t.done():
84-
self.cancel_token.cancel()
8592
t.cancel()
8693
try:
8794
await t
@@ -90,7 +97,7 @@ async def cancel() -> None:
9097
except BaseException:
9198
pass
9299

93-
asyncio.create_task(cancel())
100+
return asyncio.create_task(cancel())
94101

95102

96103
@dataclass
@@ -107,7 +114,6 @@ def __init__(self, protocol: LanguageServerProtocol) -> None:
107114

108115
self._running_diagnostics: Dict[Uri, PublishDiagnosticsEntry] = {}
109116
self._task_lock = asyncio.Lock()
110-
self._start_lock_lock = asyncio.Lock()
111117

112118
self.parent.on_connection_lost.add(self.on_connection_lost)
113119
self.parent.on_shutdown.add(self.on_shutdown)
@@ -145,11 +151,13 @@ async def _cancel_all_tasks(self) -> None:
145151
self._cancel_entry(v)
146152

147153
@_logger.call(condition=lambda self, entry: entry is not None)
148-
def _cancel_entry(self, entry: Optional[PublishDiagnosticsEntry]) -> None:
154+
async def _cancel_entry(self, entry: Optional[PublishDiagnosticsEntry]) -> None:
149155
if entry is None:
150156
return
151-
152-
entry.cancel()
157+
if not entry.done:
158+
cancel_task = entry.cancel()
159+
if cancel_task is not None:
160+
await cancel_task
153161

154162
@language_id("robotframework")
155163
@_logger.call
@@ -165,24 +173,30 @@ async def on_did_save(self, sender: Any, document: TextDocument) -> None:
165173
@_logger.call
166174
async def on_did_close(self, sender: Any, document: TextDocument) -> None:
167175
async with self._task_lock:
168-
e = self._running_diagnostics.pop(document.uri, None)
169-
self._cancel_entry(e)
176+
self._cancel_entry(self._running_diagnostics.get(document.uri, None))
170177

171178
@_logger.call
172179
async def on_did_change(self, sender: Any, document: TextDocument) -> None:
173180
await self.start_publish_diagnostics_task(document)
174181

182+
@_logger.call
183+
def _delete_entry(self, e: PublishDiagnosticsEntry) -> None:
184+
if self._running_diagnostics[e.uri] == e:
185+
self._running_diagnostics.pop(e.uri, None)
186+
175187
@_logger.call
176188
async def start_publish_diagnostics_task(self, document: TextDocument) -> None:
189+
await self._cancel_entry(self._running_diagnostics.get(document.uri, None))
190+
177191
async with self._task_lock:
178-
self._cancel_entry(self._running_diagnostics.get(document.uri, None))
179192
cancelation_token = CancelationToken()
180193
self._running_diagnostics[document.uri] = PublishDiagnosticsEntry(
181-
document.document_uri,
194+
document.uri,
182195
cancelation_token,
183196
lambda: asyncio.create_task(
184197
self.publish_diagnostics(document.document_uri, cancelation_token),
185198
),
199+
self._delete_entry,
186200
)
187201

188202
@_logger.call
@@ -202,11 +216,14 @@ async def publish_diagnostics(self, document_uri: DocumentUri, cancelation_token
202216
callback_filter=language_id_filter(document),
203217
return_exceptions=True,
204218
):
219+
if cancelation_token.canceled:
220+
break
221+
205222
result = cast(DiagnosticsResult, result_any)
206223

207224
if isinstance(result, BaseException):
208-
if not isinstance(result, asyncio.CancelledError):
209-
self._logger.exception(result, exc_info=result)
225+
# if not isinstance(result, asyncio.CancelledError):
226+
self._logger.exception(result, exc_info=result)
210227
else:
211228

212229
diagnostics[result.key] = result.diagnostics if result.diagnostics else []

robotcode/language_server/robotframework/diagnostics/namespace.py

Lines changed: 25 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -573,6 +573,7 @@ async def invalidate(self) -> None:
573573
self._library_doc = None
574574
self._analyzed = False
575575
self._diagnostics = []
576+
self._finder = None
576577

577578
self.invalidated_callback(self)
578579

@@ -631,6 +632,9 @@ async def ensure_initialized(self) -> bool:
631632
if not self._initialized:
632633
async with self._initialize_lock:
633634
if not self._initialized:
635+
636+
self._logger.debug(f"ensure_initialized -> initialize {self.document}")
637+
634638
imports = await self.get_imports()
635639

636640
data_entry: Optional[Namespace.DataEntry] = None
@@ -675,8 +679,11 @@ async def ensure_initialized(self) -> bool:
675679

676680
self._initialized = True
677681

678-
if not self._initialized:
679-
raise Exception("Namespace not initialized")
682+
await self.get_library_doc()
683+
await self.get_libraries_matchers()
684+
await self.get_resources_matchers()
685+
await self.get_finder()
686+
680687
return self._initialized
681688

682689
@property
@@ -1096,9 +1103,12 @@ async def _analyze(self, cancelation_token: Optional[CancelationToken] = None) -
10961103
if not self._analyzed:
10971104
canceled = False
10981105
try:
1099-
self._diagnostics += await awaitable_run_in_thread(
1100-
Analyzer().get(self.model, self, cancelation_token)
1101-
)
1106+
result = await awaitable_run_in_thread(Analyzer().get(self.model, self, cancelation_token))
1107+
1108+
if cancelation_token is not None:
1109+
cancelation_token.throw_if_canceled()
1110+
1111+
self._diagnostics += result
11021112

11031113
lib_doc = await self.get_library_doc()
11041114

@@ -1124,24 +1134,23 @@ async def _analyze(self, cancelation_token: Optional[CancelationToken] = None) -
11241134
)
11251135
except asyncio.CancelledError:
11261136
canceled = True
1137+
self._logger.debug("analyzing canceled")
11271138
raise
11281139
finally:
11291140
self._analyzed = not canceled
1141+
self._logger.debug(
1142+
f"analyzed {self.document}" if self._analyzed else f"not analyzed {self.document}"
1143+
)
11301144

1131-
@_logger.call(condition=lambda self, name: self._finder is not None and name not in self._finder._cache)
1132-
async def find_keyword(self, name: Optional[str]) -> Optional[KeywordDoc]:
1145+
async def get_finder(self) -> KeywordFinder:
11331146
if self._finder is None:
11341147
await self.ensure_initialized()
1148+
self._finder = KeywordFinder(self)
1149+
return self._finder
11351150

1136-
self._finder = await self.create_finder()
1137-
1138-
return await self._finder.find_keyword(name)
1139-
1140-
@_logger.call
1141-
async def create_finder(self) -> KeywordFinder:
1142-
await self.ensure_initialized()
1143-
1144-
return KeywordFinder(self)
1151+
@_logger.call(condition=lambda self, name: self._finder is not None and name not in self._finder._cache)
1152+
async def find_keyword(self, name: Optional[str]) -> Optional[KeywordDoc]:
1153+
return await (await self.get_finder()).find_keyword(name)
11451154

11461155

11471156
class DiagnosticsEntry(NamedTuple):

robotcode/language_server/robotframework/parts/diagnostics.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,4 +159,6 @@ async def collect_namespace_diagnostics(
159159
if namespace is None:
160160
return DiagnosticsResult(self.collect_namespace_diagnostics, None)
161161

162+
await namespace.ensure_initialized()
163+
162164
return DiagnosticsResult(self.collect_namespace_diagnostics, await namespace.get_diagnostisc(cancelation_token))

robotcode/language_server/robotframework/parts/robocop_diagnostics.py

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,7 @@ def collect(
9292
from robocop.config import Config
9393
from robocop.rules import RuleSeverity
9494
from robocop.run import Robocop
95+
from robocop.utils.misc import is_suite_templated
9596

9697
result: List[Diagnostic] = []
9798

@@ -109,11 +110,30 @@ def collect(
109110
if extension_config.configurations:
110111
config.configure = set(extension_config.configurations)
111112

112-
analyser = Robocop(from_cli=False, config=config)
113+
class MyRobocop(Robocop): # type: ignore
114+
def run_check(self, ast_model, filename, source=None): # type: ignore
115+
found_issues = []
116+
self.register_disablers(filename, source)
117+
if self.disabler.file_disabled:
118+
return []
119+
templated = is_suite_templated(ast_model)
120+
for checker in self.checkers:
121+
cancelation_token.throw_if_canceled()
122+
123+
if checker.disabled:
124+
continue
125+
found_issues += [
126+
issue
127+
for issue in checker.scan_file(ast_model, filename, source, templated)
128+
if not self.disabler.is_rule_disabled(issue)
129+
]
130+
return found_issues
131+
132+
analyser = MyRobocop(from_cli=False, config=config)
113133
analyser.reload_config()
114134

115135
# TODO find a way to cancel the run_check
116-
issues = analyser.run_check(model, str(document.uri.to_path()), document.text)
136+
issues = analyser.run_check(model, str(document.uri.to_path()), document.text) # type: ignore
117137

118138
for issue in issues:
119139
d = Diagnostic(

robotcode/language_server/robotframework/parts/semantic_tokens.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -627,6 +627,7 @@ async def collect_threading(
627627
try:
628628
model = await self.parent.documents_cache.get_model(document)
629629
namespace = await self.parent.documents_cache.get_namespace(document)
630+
await namespace.ensure_initialized()
630631

631632
builtin_library_doc = next(
632633
(

robotcode/utils/async_tools.py

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -397,17 +397,14 @@ def __init__(self, _func: _TCallable) -> None:
397397

398398
class CancelationToken:
399399
def __init__(self) -> None:
400-
self._canceled = False
401-
self._lock = threading.RLock()
400+
self._canceled = asyncio.Event()
402401

403402
@property
404403
def canceled(self) -> bool:
405-
with self._lock:
406-
return self._canceled
404+
return self._canceled.is_set()
407405

408406
def cancel(self) -> None:
409-
with self._lock:
410-
self._canceled = True
407+
self._canceled.set()
411408

412409
def throw_if_canceled(self) -> bool:
413410
if self.canceled:

0 commit comments

Comments
 (0)