Skip to content

Commit ab814cf

Browse files
committed
optimize locking and get tests from workspace
1 parent 56ffd6b commit ab814cf

File tree

8 files changed

+863
-864
lines changed

8 files changed

+863
-864
lines changed

CHANGELOG.md

Lines changed: 785 additions & 781 deletions
Large diffs are not rendered by default.

robotcode/jsonrpc2/protocol.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -717,6 +717,12 @@ async def handle_request(self, message: JsonRPCRequest) -> None:
717717

718718
def done(t: asyncio.Task[Any]) -> None:
719719
try:
720+
if not t.cancelled():
721+
ex = t.exception()
722+
if ex is not None:
723+
self._logger.exception(ex, exc_info=ex)
724+
raise JsonRPCErrorException(JsonRPCErrors.INTERNAL_ERROR, f"{type(ex).__name__}: {ex}") from ex
725+
720726
self.send_response(message.id, t.result())
721727
except asyncio.CancelledError:
722728
self._logger.debug(f"request message {repr(message)} canceled")

robotcode/language_server/common/parts/diagnostics.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -370,7 +370,10 @@ async def cancel(t: asyncio.Task[Any]) -> None:
370370
except asyncio.CancelledError:
371371
pass
372372

373-
asyncio.run_coroutine_threadsafe(cancel(task), loop=task.get_loop()).result(5)
373+
try:
374+
asyncio.run_coroutine_threadsafe(cancel(task), loop=task.get_loop()).result(600)
375+
except TimeoutError as e:
376+
raise RuntimeError("Can't cancel diagnostics task.") from e
374377

375378
# task.get_loop().call_soon_threadsafe(task.cancel)
376379

robotcode/language_server/robotframework/parts/code_action.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,7 @@ def calc_md() -> str:
138138
name + ("::" + args if args else ""),
139139
base_dir=basedir if basedir else ".",
140140
theme=theme,
141-
).result(10)
141+
).result(600)
142142

143143
self.send_response(200)
144144
self.send_header("Content-type", "text/html")

robotcode/language_server/robotframework/parts/completion.py

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

2626
from ....utils.async_itertools import async_chain, async_chain_iterator
27-
from ....utils.async_tools import threaded
2827
from ....utils.logging import LoggingDescriptor
2928
from ...common.decorators import language_id, trigger_characters
3029
from ...common.lsp_types import (
@@ -108,7 +107,6 @@ async def get_header_style(self, config: CompletionConfig) -> str:
108107
)
109108
# @all_commit_characters(['\n'])
110109
@language_id("robotframework")
111-
@threaded()
112110
@_logger.call
113111
async def collect(
114112
self, sender: Any, document: TextDocument, position: Position, context: Optional[CompletionContext]
@@ -130,7 +128,6 @@ async def collect(
130128
)
131129

132130
@language_id("robotframework")
133-
@threaded()
134131
@_logger.call
135132
async def resolve(self, sender: Any, completion_item: CompletionItem) -> CompletionItem:
136133
if completion_item.data is not None:

robotcode/language_server/robotframework/parts/discovering.py

Lines changed: 31 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
from typing import TYPE_CHECKING, Any, Callable, Iterator, List, Optional, cast
99

1010
from ....jsonrpc2.protocol import rpc_method
11-
from ....utils.async_tools import run_coroutine_in_thread
11+
from ....utils.async_tools import threaded
1212
from ....utils.logging import LoggingDescriptor
1313
from ....utils.uri import Uri
1414
from ...common.lsp_types import (
@@ -62,7 +62,7 @@ class FindTestCasesVisitor(AsyncVisitor):
6262
async def get(self, source: DocumentUri, model: ast.AST, base_name: Optional[str]) -> List[TestItem]:
6363
self._results: List[TestItem] = []
6464
self.source = source
65-
self.path = Uri(source).to_path().resolve()
65+
self.path = Uri(source).to_path()
6666
self.base_name = base_name
6767
await self.visit(model)
6868
return self._results
@@ -111,8 +111,15 @@ async def get_config(self, workspace_uri: str) -> Optional[RobotConfig]:
111111

112112
return await self.parent.workspace.get_configuration(RobotConfig, folder.uri)
113113

114-
async def _get_tests_from_workspace(
115-
self, workspace_folder: str, paths: Optional[List[str]], suites: Optional[List[str]]
114+
@rpc_method(name="robot/discovering/getTestsFromWorkspace", param_type=GetAllTestsParams)
115+
@threaded()
116+
async def get_tests_from_workspace(
117+
self,
118+
workspace_folder: str,
119+
paths: Optional[List[str]],
120+
suites: Optional[List[str]],
121+
*args: Any,
122+
**kwargs: Any,
116123
) -> List[TestItem]:
117124

118125
from robot.output.logger import LOGGER
@@ -138,7 +145,7 @@ async def _get_tests_from_workspace(
138145
def get_document_text(source: str) -> str:
139146
if self.parent._loop:
140147
doc = self.parent.documents.get_sync(Uri.from_path(source).normalized())
141-
if doc is not None and doc.opened_in_editor:
148+
if doc is not None:
142149
return doc.text_sync()
143150

144151
return source
@@ -258,8 +265,7 @@ def generate(suite: TestSuite) -> TestItem:
258265
children.append(
259266
TestItem(
260267
type="test",
261-
id=f"{Path(test.source).resolve() if test.source is not None else ''};"
262-
f"{test.longname};{test.lineno}",
268+
id=f"{test.source if test.source is not None else ''};" f"{test.longname};{test.lineno}",
263269
label=test.name,
264270
longname=test.longname,
265271
uri=str(Uri.from_path(test.source)) if test.source else None,
@@ -276,7 +282,7 @@ def generate(suite: TestSuite) -> TestItem:
276282

277283
return TestItem(
278284
type="suite",
279-
id=f"{Path(suite.source).resolve() if suite.source is not None else ''};{suite.longname}",
285+
id=f"{suite.source if suite.source is not None else ''};{suite.longname}",
280286
label=suite.name,
281287
longname=suite.longname,
282288
uri=str(Uri.from_path(suite.source)) if suite.source else None,
@@ -342,7 +348,7 @@ def nonexisting_paths(paths: List[str]) -> Iterator[str]:
342348
return [
343349
TestItem(
344350
type="workspace",
345-
id=str(Path.cwd().resolve()),
351+
id=str(Path.cwd()),
346352
label=Path.cwd().name,
347353
longname=Path.cwd().name,
348354
uri=str(Uri.from_path(Path.cwd())),
@@ -375,44 +381,30 @@ def nonexisting_paths(paths: List[str]) -> Iterator[str]:
375381
return [
376382
TestItem(
377383
type="error",
378-
id=str(Uri.from_path(Path.cwd().resolve())),
384+
id=str(Uri.from_path(Path.cwd())),
379385
longname="error",
380386
label=Path.cwd().name,
381387
error=str(e),
382388
)
383389
]
384390

385-
@rpc_method(name="robot/discovering/getTestsFromWorkspace", param_type=GetAllTestsParams)
386-
@_logger.call
387-
async def get_tests_from_workspace(
388-
self,
389-
workspace_folder: str,
390-
paths: Optional[List[str]],
391-
suites: Optional[List[str]],
392-
*args: Any,
393-
**kwargs: Any,
394-
) -> List[TestItem]:
395-
return await run_coroutine_in_thread(self._get_tests_from_workspace, workspace_folder, paths, suites)
396-
397391
@rpc_method(name="robot/discovering/getTestsFromDocument", param_type=GetTestsParams)
398-
@_logger.call
392+
@threaded()
399393
async def get_tests_from_document(
400394
self, text_document: TextDocumentIdentifier, base_name: Optional[str], *args: Any, **kwargs: Any
401395
) -> List[TestItem]:
402-
async def run() -> List[TestItem]:
403-
try:
404-
return await FindTestCasesVisitor().get(
405-
text_document.uri,
406-
await self.parent.documents_cache.get_model(
407-
await self.parent.documents.get_or_open_document(
408-
Uri(text_document.uri).to_path(), language_id="robotframework"
409-
)
410-
),
411-
base_name,
412-
)
413-
except (asyncio.CancelledError, SystemExit, KeyboardInterrupt):
414-
raise
415-
except BaseException:
416-
return []
417396

418-
return await run_coroutine_in_thread(run)
397+
try:
398+
return await FindTestCasesVisitor().get(
399+
text_document.uri,
400+
await self.parent.documents_cache.get_model(
401+
await self.parent.documents.get_or_open_document(
402+
Uri(text_document.uri).to_path(), language_id="robotframework"
403+
)
404+
),
405+
base_name,
406+
)
407+
except (asyncio.CancelledError, SystemExit, KeyboardInterrupt):
408+
raise
409+
except BaseException:
410+
return []

robotcode/language_server/robotframework/parts/references.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ def __init__(self, parent: RobotLanguageServerProtocol) -> None:
5656
async def cache_cleared(sender) -> None: # NOSONAR
5757
...
5858

59+
@language_id("robotframework")
5960
@threaded()
6061
async def document_did_change(self, sender: Any, document: TextDocument) -> None:
6162
await self._keyword_reference_cache.clear()

robotcode/utils/async_tools.py

Lines changed: 35 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@
1111
import weakref
1212
from collections import deque
1313
from concurrent.futures.thread import ThreadPoolExecutor
14-
from contextlib import asynccontextmanager
1514
from types import TracebackType
1615
from typing import (
1716
Any,
@@ -380,8 +379,8 @@ def __repr__(self) -> str:
380379
return f"<{res[1:-1]} [{extra}]>"
381380

382381
def is_set(self) -> bool:
383-
# with self._lock:
384-
return self._value
382+
with self._lock:
383+
return self._value
385384

386385
def set(self) -> None:
387386
with self._lock:
@@ -443,15 +442,15 @@ class Lock:
443442
def __init__(self) -> None:
444443
self._waiters: Optional[Deque[asyncio.Future[Any]]] = None
445444
self._locked = False
446-
self._lock = threading.Lock()
445+
self._lock = threading.RLock()
447446

448447
async def __aenter__(self) -> None:
449448
await self.acquire()
450449

451450
async def __aexit__(
452451
self, exc_type: Optional[Type[BaseException]], exc_val: Optional[BaseException], exc_tb: Optional[TracebackType]
453452
) -> None:
454-
await self.release()
453+
self.release()
455454

456455
def __repr__(self) -> str:
457456
res = super().__repr__()
@@ -460,24 +459,12 @@ def __repr__(self) -> str:
460459
extra = f"{extra}, waiters:{len(self._waiters)}"
461460
return f"<{res[1:-1]} [{extra}]>"
462461

463-
@asynccontextmanager
464-
async def __inner_lock(self) -> AsyncGenerator[Any, None]:
465-
# while not (b := self._lock.acquire(blocking=False)):
466-
# await asyncio.sleep(0)
467-
# try:
468-
# yield None
469-
# finally:
470-
# if b:
471-
# self._lock.release()
472-
with self._lock:
473-
yield None
474-
475462
@property
476463
def locked(self) -> bool:
477464
return self._locked
478465

479466
async def acquire(self) -> bool:
480-
async with self.__inner_lock():
467+
with self._lock:
481468
if not self._locked and (self._waiters is None or all(w.cancelled() for w in self._waiters)):
482469
self._locked = True
483470
return True
@@ -489,42 +476,49 @@ async def acquire(self) -> bool:
489476
self._waiters.append(fut)
490477

491478
try:
479+
try:
492480

493-
def aaa() -> None:
494-
warnings.warn(f"Lock takes to long {threading.current_thread()}")
481+
def aaa() -> None:
482+
warnings.warn(f"Lock takes to long {threading.current_thread()}")
495483

496-
h = fut.get_loop().call_later(320, aaa)
484+
h = fut.get_loop().call_later(60, aaa)
497485

498-
await fut
486+
await fut
499487

500-
h.cancel()
501-
finally:
502-
async with self.__inner_lock():
503-
if fut in self._waiters:
488+
h.cancel()
489+
finally:
490+
with self._lock:
504491
self._waiters.remove(fut)
505-
self._locked = True
492+
except asyncio.CancelledError:
493+
with self._lock:
494+
if not self._locked:
495+
self._wake_up_first()
496+
raise
497+
498+
with self._lock:
499+
self._locked = True
506500

507501
return True
508502

509-
async def release(self) -> None:
510-
async with self.__inner_lock():
511-
if self._waiters is None or len(self._waiters) == 0:
512-
if self._locked:
513-
self._locked = False
503+
def release(self) -> None:
504+
with self._lock:
505+
wake_up = False
506+
if self._locked:
507+
self._locked = False
508+
wake_up = True
514509

515-
await self._wake_up_next()
510+
if wake_up:
511+
self._wake_up_first()
516512

517-
async def _wake_up_next(self) -> None:
513+
def _wake_up_first(self) -> None:
518514
if not self._waiters:
519515
return
520516

521-
async with self.__inner_lock():
517+
with self._lock:
522518
try:
523519
fut = next(iter(self._waiters))
524520
except StopIteration:
525521
return
526-
if fut in self._waiters:
527-
self._waiters.remove(fut)
528522

529523
if fut.get_loop().is_running() and not fut.get_loop().is_closed():
530524
if fut.get_loop() == asyncio.get_running_loop():
@@ -551,10 +545,12 @@ def set_result(w: asyncio.Future[Any], ev: threading.Event) -> None:
551545
warnings.warn("Can't set future result.")
552546
break
553547

554-
await asyncio.sleep(0)
548+
time.sleep(0.001)
555549
else:
556550
warnings.warn(f"Future {repr(fut)} loop is closed")
557-
await self._wake_up_next()
551+
with self._lock:
552+
self._waiters.remove(fut)
553+
self._wake_up_first()
558554

559555

560556
class FutureInfo:

0 commit comments

Comments
 (0)