Skip to content

Commit aee36d7

Browse files
committed
perf: massive overall speed improvements
Mainly from changing locks from async.Lock to threading.Lock. Extra: more timing statistics in log output
1 parent a92db4d commit aee36d7

File tree

15 files changed

+274
-203
lines changed

15 files changed

+274
-203
lines changed

robotcode/language_server/common/parts/diagnostics.py

Lines changed: 58 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -121,8 +121,14 @@ def __init__(self, protocol: LanguageServerProtocol) -> None:
121121
self._workspace_diagnostics_task: Optional[asyncio.Task[Any]] = None
122122

123123
self._diagnostics_loop: Optional[asyncio.AbstractEventLoop] = None
124+
self._single_diagnostics_loop: Optional[asyncio.AbstractEventLoop] = None
125+
124126
self._diagnostics_loop_lock = threading.RLock()
125127
self._diagnostics_started = threading.Event()
128+
self._single_diagnostics_started = threading.Event()
129+
130+
self._diagnostics_server_thread: Optional[threading.Thread] = None
131+
self._single_diagnostics_server_thread: Optional[threading.Thread] = None
126132

127133
self.parent.on_initialized.add(self.initialized)
128134

@@ -148,6 +154,15 @@ def diagnostics_loop(self) -> asyncio.AbstractEventLoop:
148154

149155
return self._diagnostics_loop
150156

157+
@property
158+
def single_diagnostics_loop(self) -> asyncio.AbstractEventLoop:
159+
if self._single_diagnostics_loop is None:
160+
self._ensure_diagnostics_thread_started()
161+
162+
assert self._single_diagnostics_loop is not None
163+
164+
return self._single_diagnostics_loop
165+
151166
def _run_diagnostics(self) -> None:
152167
loop = asyncio.new_event_loop()
153168
asyncio.set_event_loop(loop)
@@ -164,17 +179,39 @@ def _run_diagnostics(self) -> None:
164179
asyncio.set_event_loop(None)
165180
loop.close()
166181

182+
def _single_run_diagnostics(self) -> None:
183+
loop = asyncio.new_event_loop()
184+
asyncio.set_event_loop(loop)
185+
try:
186+
self._single_diagnostics_loop = loop
187+
self._single_diagnostics_started.set()
188+
189+
loop.slow_callback_duration = 10
190+
191+
loop.run_forever()
192+
_cancel_all_tasks(loop)
193+
loop.run_until_complete(loop.shutdown_asyncgens())
194+
finally:
195+
asyncio.set_event_loop(None)
196+
loop.close()
197+
167198
def _ensure_diagnostics_thread_started(self) -> None:
168199
with self._diagnostics_loop_lock:
169200
if self._diagnostics_loop is None:
170-
self._server_thread = threading.Thread(
201+
self._diagnostics_server_thread = threading.Thread(
171202
name="diagnostics_worker", target=self._run_diagnostics, daemon=True
172203
)
173204

174-
self._server_thread.start()
205+
self._diagnostics_server_thread.start()
206+
207+
self._single_diagnostics_server_thread = threading.Thread(
208+
name="single_diagnostics_worker", target=self._single_run_diagnostics, daemon=True
209+
)
210+
211+
self._single_diagnostics_server_thread.start()
175212

176-
if not self._diagnostics_started.wait(10):
177-
raise RuntimeError("Can't start diagnostics worker thread.")
213+
if not self._diagnostics_started.wait(10) or not self._single_diagnostics_started.wait(10):
214+
raise RuntimeError("Can't start diagnostics worker threads.")
178215

179216
def extend_capabilities(self, capabilities: ServerCapabilities) -> None:
180217
if (
@@ -280,7 +317,6 @@ async def run_workspace_diagnostics(self) -> None:
280317

281318
while True:
282319
try:
283-
284320
documents = [
285321
doc
286322
for doc in self.parent.documents.documents
@@ -296,8 +332,11 @@ async def run_workspace_diagnostics(self) -> None:
296332
await asyncio.sleep(1)
297333
continue
298334

335+
self._logger.info(f"start collecting workspace diagnostics for {len(documents)} documents")
336+
299337
done_something = False
300338

339+
start = time.monotonic()
301340
async with self.parent.window.progress(
302341
"Analyse workspace", cancellable=False, current=0, max=len(documents) + 1, start=False
303342
) as progress:
@@ -324,21 +363,31 @@ async def run_workspace_diagnostics(self) -> None:
324363
progress.report(current=i + 1)
325364

326365
await self.create_document_diagnostics_task(
327-
document, False, await self.get_diagnostics_mode(document.uri) == DiagnosticsMode.WORKSPACE
366+
document,
367+
False,
368+
False,
369+
await self.get_diagnostics_mode(document.uri) == DiagnosticsMode.WORKSPACE,
328370
)
329371

330372
if not done_something:
331373
await asyncio.sleep(1)
374+
375+
self._logger.info(
376+
f"collecting workspace diagnostics for for {len(documents)} "
377+
f"documents takes {time.monotonic() - start}s"
378+
)
379+
332380
except (SystemExit, KeyboardInterrupt, asyncio.CancelledError):
333381
raise
334382
except BaseException as e:
335383
self._logger.exception(f"Error in workspace diagnostics loop: {e}", exc_info=e)
336384

337385
def create_document_diagnostics_task(
338-
self, document: TextDocument, debounce: bool = True, send_diagnostics: bool = True
386+
self, document: TextDocument, single: bool, debounce: bool = True, send_diagnostics: bool = True
339387
) -> asyncio.Task[Any]:
340388
def done(t: asyncio.Task[Any]) -> None:
341389
self._logger.debug(lambda: f"diagnostics for {document} {'canceled' if t.cancelled() else 'ended'}")
390+
342391
if t.done() and not t.cancelled():
343392
ex = t.exception()
344393

@@ -372,7 +421,7 @@ async def cancel(t: asyncio.Task[Any]) -> None:
372421
data.version = document.version
373422
data.task = create_sub_task(
374423
self._get_diagnostics_for_document(document, data, debounce, send_diagnostics),
375-
loop=self.diagnostics_loop,
424+
loop=self.single_diagnostics_loop if single else self.diagnostics_loop,
376425
name=f"diagnostics ${document.uri}",
377426
)
378427

@@ -444,7 +493,7 @@ async def _text_document_diagnostic(
444493
if document is None:
445494
raise JsonRPCErrorException(ErrorCodes.SERVER_CANCELLED, f"Document {text_document!r} not found.")
446495

447-
self.create_document_diagnostics_task(document)
496+
self.create_document_diagnostics_task(document, True)
448497

449498
return RelatedFullDocumentDiagnosticReport([])
450499
except asyncio.CancelledError:

robotcode/language_server/common/parts/documents.py

Lines changed: 10 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,7 @@ async def _file_watcher(self, sender: Any, changes: List[FileEvent]) -> None:
111111
loop=self.parent.loop,
112112
)
113113
elif change.type == FileChangeType.CHANGED:
114-
await document.apply_full_change(
114+
document.apply_full_change(
115115
None, await self.read_document_text(document.uri, language_id_filter(document)), save=True
116116
)
117117
create_sub_task(
@@ -258,9 +258,9 @@ async def _text_document_did_open(self, text_document: TextDocumentItem, *args:
258258

259259
self._documents[uri] = document
260260
else:
261-
text_changed = await document.text() != normalized_text
261+
text_changed = document.text() != normalized_text
262262
if text_changed:
263-
await document.apply_full_change(text_document.version, normalized_text)
263+
document.apply_full_change(text_document.version, normalized_text)
264264

265265
document.opened_in_editor = True
266266

@@ -297,11 +297,11 @@ async def close_document(self, document: TextDocument, real_close: bool = False)
297297
async with self._lock:
298298
self._documents.pop(str(document.uri), None)
299299

300-
await document.clear()
300+
document.clear()
301301
del document
302302
else:
303303
document._version = None
304-
if await document.revert(None):
304+
if document.revert(None):
305305
create_sub_task(
306306
self.did_change(self, document, callback_filter=language_id_filter(document)), loop=self.parent.loop
307307
)
@@ -326,9 +326,9 @@ async def _text_document_did_save(
326326
if text is not None:
327327
normalized_text = self._normalize_line_endings(text)
328328

329-
text_changed = await document.text() != normalized_text
329+
text_changed = document.text() != normalized_text
330330
if text_changed:
331-
await document.save(None, text)
331+
document.save(None, text)
332332
create_sub_task(
333333
self.did_change(self, document, callback_filter=language_id_filter(document)),
334334
loop=self.parent.loop,
@@ -367,17 +367,15 @@ async def _text_document_did_change(
367367
)
368368
for content_change in content_changes:
369369
if sync_kind is None or sync_kind == TextDocumentSyncKind.NONE:
370-
await document.apply_none_change()
370+
document.apply_none_change()
371371
elif sync_kind == TextDocumentSyncKind.FULL and isinstance(
372372
content_change, TextDocumentContentTextChangeEvent
373373
):
374-
await document.apply_full_change(
375-
text_document.version, self._normalize_line_endings(content_change.text)
376-
)
374+
document.apply_full_change(text_document.version, self._normalize_line_endings(content_change.text))
377375
elif sync_kind == TextDocumentSyncKind.INCREMENTAL and isinstance(
378376
content_change, TextDocumentContentRangeChangeEvent
379377
):
380-
await document.apply_incremental_change(
378+
document.apply_incremental_change(
381379
text_document.version, content_change.range, self._normalize_line_endings(content_change.text)
382380
)
383381
else:

robotcode/language_server/common/parts/workspace.py

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -24,12 +24,7 @@
2424
)
2525

2626
from ....jsonrpc2.protocol import rpc_method
27-
from ....utils.async_tools import (
28-
Lock,
29-
async_event,
30-
async_tasking_event,
31-
create_sub_task,
32-
)
27+
from ....utils.async_tools import Lock, async_tasking_event, create_sub_task, threaded
3328
from ....utils.dataclasses import from_dict
3429
from ....utils.logging import LoggingDescriptor
3530
from ....utils.path import path_is_relative_to
@@ -388,11 +383,12 @@ async def _workspace_did_change_workspace_folders(
388383
for a in event.added:
389384
self._workspace_folders.append(WorkspaceFolder(a.name, Uri(a.uri), a.uri))
390385

391-
@async_event
386+
@async_tasking_event
392387
async def did_change_watched_files(sender, changes: List[FileEvent]) -> None: # NOSONAR
393388
...
394389

395390
@rpc_method(name="workspace/didChangeWatchedFiles", param_type=DidChangeWatchedFilesParams)
391+
@threaded()
396392
async def _workspace_did_change_watched_files(self, changes: List[FileEvent], *args: Any, **kwargs: Any) -> None:
397393
changes = [e for e in changes if not e.uri.endswith("/globalStorage")]
398394
if changes:

0 commit comments

Comments
 (0)