Skip to content

Commit ab918db

Browse files
committed
refactor: introduce asyncio.RLock
1 parent 9fd7cf0 commit ab918db

File tree

4 files changed

+122
-103
lines changed

4 files changed

+122
-103
lines changed

robotcode/language_server/common/parts/diagnostics.py

Lines changed: 12 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -376,6 +376,7 @@ async def run_workspace_diagnostics(self) -> None:
376376
f"collecting workspace diagnostics for for {len(documents)} "
377377
f"documents takes {time.monotonic() - start}s"
378378
)
379+
self._logger.info(f"{len(self.parent.documents)} documents loaded")
379380

380381
except (SystemExit, KeyboardInterrupt, asyncio.CancelledError):
381382
raise
@@ -416,8 +417,6 @@ async def cancel(t: asyncio.Task[Any]) -> None:
416417
except TimeoutError as e:
417418
raise RuntimeError("Can't cancel diagnostics task.") from e
418419

419-
# task.get_loop().call_soon_threadsafe(task.cancel)
420-
421420
data.version = document.version
422421
data.task = create_sub_task(
423422
self._get_diagnostics_for_document(document, data, debounce, send_diagnostics),
@@ -459,18 +458,17 @@ async def _get_diagnostics_for_document(
459458
if result.diagnostics is not None:
460459
collected_keys.append(result.key)
461460

462-
if data.entries:
463-
if send_diagnostics:
464-
self.parent.send_notification(
465-
"textDocument/publishDiagnostics",
466-
PublishDiagnosticsParams(
467-
uri=document.document_uri,
468-
version=document._version,
469-
diagnostics=[
470-
e for e in itertools.chain(*(i for i in data.entries.values() if i is not None))
471-
],
472-
),
473-
)
461+
if data.entries and send_diagnostics:
462+
self.parent.send_notification(
463+
"textDocument/publishDiagnostics",
464+
PublishDiagnosticsParams(
465+
uri=document.document_uri,
466+
version=document._version,
467+
diagnostics=[
468+
e for e in itertools.chain(*(i for i in data.entries.values() if i is not None))
469+
],
470+
),
471+
)
474472

475473
except asyncio.CancelledError:
476474
self._logger.debug(lambda: f"_get_diagnostics cancelled for {document}")

robotcode/language_server/robotframework/parts/discovering.py

Lines changed: 80 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -238,7 +238,6 @@ async def generate(suite: TestSuite) -> TestItem:
238238
else None,
239239
)
240240

241-
# await self.parent.diagnostics.ensure_workspace_loaded()
242241
await self.parent.robot_workspace.documents_loaded.wait()
243242

244243
start = time.monotonic()
@@ -247,107 +246,107 @@ async def generate(suite: TestSuite) -> TestItem:
247246

248247
workspace_path = Uri(workspace_folder).to_path()
249248
canceled = False
250-
with LOGGER.cache_only:
251-
try:
252-
config = await self.get_config(workspace_folder)
253-
rpa_mode = config.get_rpa_mode() if config is not None else None
254-
languages = await self.parent.documents_cache.get_workspace_languages(workspace_folder)
249+
LOGGER._cache_only = True
250+
try:
251+
config = await self.get_config(workspace_folder)
252+
rpa_mode = config.get_rpa_mode() if config is not None else None
253+
languages = await self.parent.documents_cache.get_workspace_languages(workspace_folder)
255254

256-
if paths is None and config is not None:
257-
paths = config.paths
255+
if paths is None and config is not None:
256+
paths = config.paths
258257

259-
if paths and len(paths):
258+
if paths and len(paths):
260259

261-
def normalize_paths(paths: List[str]) -> Iterator[str]:
260+
def normalize_paths(paths: List[str]) -> Iterator[str]:
262261

263-
for path in paths:
262+
for path in paths:
264263

265-
p = Path(path)
264+
p = Path(path)
266265

267-
if not p.is_absolute():
268-
p = Path(workspace_path, p)
266+
if not p.is_absolute():
267+
p = Path(workspace_path, p)
269268

270-
if p.exists():
271-
yield str(p)
269+
if p.exists():
270+
yield str(p)
272271

273-
def nonexisting_paths(paths: List[str]) -> Iterator[str]:
272+
def nonexisting_paths(paths: List[str]) -> Iterator[str]:
274273

275-
for path in paths:
274+
for path in paths:
276275

277-
p = Path(path)
276+
p = Path(path)
278277

279-
if not p.is_absolute():
280-
p = Path(workspace_path, p)
278+
if not p.is_absolute():
279+
p = Path(workspace_path, p)
281280

282-
if not p.exists():
283-
yield str(p)
281+
if not p.exists():
282+
yield str(p)
284283

285-
valid_paths = [i for i in normalize_paths(paths)]
284+
valid_paths = [i for i in normalize_paths(paths)]
286285

287-
if get_robot_version() >= (6, 0):
288-
builder = TestSuiteBuilder(
289-
included_suites=suites if suites else None,
290-
rpa=rpa_mode,
291-
lang=languages,
292-
)
293-
else:
294-
builder = TestSuiteBuilder(included_suites=suites if suites else None, rpa=rpa_mode)
286+
if get_robot_version() >= (6, 0):
287+
builder = TestSuiteBuilder(
288+
included_suites=suites if suites else None,
289+
rpa=rpa_mode,
290+
lang=languages,
291+
)
292+
else:
293+
builder = TestSuiteBuilder(included_suites=suites if suites else None, rpa=rpa_mode)
295294

296-
suite: Optional[TestSuite] = builder.build(*valid_paths) if valid_paths else None
297-
suite_item = [await generate(suite)] if suite else []
295+
suite: Optional[TestSuite] = builder.build(*valid_paths) if valid_paths else None
296+
suite_item = [await generate(suite)] if suite else []
298297

299-
return [
300-
TestItem(
301-
type="workspace",
302-
id=str(Path.cwd().resolve()),
303-
label=Path.cwd().name,
304-
longname=Path.cwd().name,
305-
uri=str(Uri.from_path(Path.cwd())),
306-
children=[
307-
*suite_item,
308-
*[
309-
TestItem(
310-
type="error",
311-
id=f"{i};ERROR",
312-
longname="error",
313-
label=i,
314-
error=f"Parsing '{i}' failed: File or directory to does not exist.",
315-
)
316-
for i in nonexisting_paths(paths)
317-
],
318-
],
319-
)
320-
]
321-
else:
322-
if get_robot_version() >= (6, 0):
323-
builder = TestSuiteBuilder(
324-
included_suites=suites if suites else None,
325-
rpa=rpa_mode,
326-
lang=languages,
327-
)
328-
else:
329-
builder = TestSuiteBuilder(included_suites=suites if suites else None, rpa=rpa_mode)
330-
return [await generate(builder.build(str(workspace_path)))]
331-
except (SystemExit, KeyboardInterrupt):
332-
raise
333-
except asyncio.CancelledError:
334-
canceled = True
335-
self._logger.info("Tests discovery canceled")
336-
raise
337-
except BaseException as e:
338-
self._logger.info(f"Failed to discover tests: {e}")
339298
return [
340299
TestItem(
341-
type="error",
342-
id=str(Uri.from_path(Path.cwd().resolve())),
343-
longname="error",
300+
type="workspace",
301+
id=str(Path.cwd().resolve()),
344302
label=Path.cwd().name,
345-
error=str(e),
303+
longname=Path.cwd().name,
304+
uri=str(Uri.from_path(Path.cwd())),
305+
children=[
306+
*suite_item,
307+
*[
308+
TestItem(
309+
type="error",
310+
id=f"{i};ERROR",
311+
longname="error",
312+
label=i,
313+
error=f"Parsing '{i}' failed: File or directory to does not exist.",
314+
)
315+
for i in nonexisting_paths(paths)
316+
],
317+
],
346318
)
347319
]
348-
finally:
349-
if not canceled:
350-
self._logger.info(f"Tests discovery took {time.monotonic() - start}s")
320+
else:
321+
if get_robot_version() >= (6, 0):
322+
builder = TestSuiteBuilder(
323+
included_suites=suites if suites else None,
324+
rpa=rpa_mode,
325+
lang=languages,
326+
)
327+
else:
328+
builder = TestSuiteBuilder(included_suites=suites if suites else None, rpa=rpa_mode)
329+
return [await generate(builder.build(str(workspace_path)))]
330+
except (SystemExit, KeyboardInterrupt):
331+
raise
332+
except asyncio.CancelledError:
333+
canceled = True
334+
self._logger.info("Tests discovery canceled")
335+
raise
336+
except BaseException as e:
337+
self._logger.info(f"Failed to discover tests: {e}")
338+
return [
339+
TestItem(
340+
type="error",
341+
id=str(Uri.from_path(Path.cwd().resolve())),
342+
longname="error",
343+
label=Path.cwd().name,
344+
error=str(e),
345+
)
346+
]
347+
finally:
348+
if not canceled:
349+
self._logger.info(f"Tests discovery took {time.monotonic() - start}s")
351350

352351
@rpc_method(name="robot/discovering/getTestsFromDocument", param_type=GetTestsParams)
353352
@threaded()

robotcode/utils/async_cache.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
from collections import defaultdict
22
from typing import Any, Awaitable, Callable, Dict, List, Optional, Tuple, TypeVar, cast
33

4-
from .async_tools import Lock
4+
from .async_tools import RLock
55

66
_T = TypeVar("_T")
77

@@ -16,7 +16,7 @@ class CacheEntry:
1616
def __init__(self) -> None:
1717
self.data: Any = None
1818
self.has_data: bool = False
19-
self.lock = Lock()
19+
self.lock = RLock()
2020

2121

2222
class AsyncSimpleLRUCache:
@@ -27,7 +27,7 @@ def __init__(self, max_items: Optional[int] = 128) -> None:
2727
self._order: Optional[List[Tuple[Any, ...]]] = None
2828
if self.max_items:
2929
self._order = []
30-
self._lock = Lock()
30+
self._lock = RLock()
3131

3232
async def has(self, *args: Any, **kwargs: Any) -> bool:
3333
key = self._make_key(*args, **kwargs)

robotcode/utils/async_tools.py

Lines changed: 27 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -466,12 +466,11 @@ def aaa(fut: asyncio.Future[Any]) -> None:
466466
warnings.warn(f"Lock {self} takes to long {threading.current_thread()}\n, try to cancel...")
467467
fut.cancel()
468468

469-
# h = fut.get_loop().call_later(120, aaa, "".join(traceback.format_stack()))
470469
h = fut.get_loop().call_later(120, aaa, fut)
471-
472-
await fut
473-
474-
h.cancel()
470+
try:
471+
await fut
472+
finally:
473+
h.cancel()
475474
finally:
476475
with self._lock:
477476
self._waiters.remove(fut)
@@ -541,6 +540,29 @@ def set_result(w: asyncio.Future[Any], ev: threading.Event) -> None:
541540
self._wake_up_first()
542541

543542

543+
class RLock(Lock):
544+
def __init__(self) -> None:
545+
super().__init__()
546+
self._task: Optional[asyncio.Task[Any]] = None
547+
self._depth = 0
548+
549+
async def acquire(self) -> bool:
550+
if self._task is None or self._task != asyncio.current_task():
551+
await super().acquire()
552+
self._task = asyncio.current_task()
553+
assert self._depth == 0
554+
self._depth += 1
555+
556+
return True
557+
558+
def release(self) -> None:
559+
if self._depth > 0:
560+
self._depth -= 1
561+
if self._depth == 0:
562+
super().release()
563+
self._task = None
564+
565+
544566
class FutureInfo:
545567
def __init__(self, future: asyncio.Future[Any]) -> None:
546568
self.task: weakref.ref[asyncio.Future[Any]] = weakref.ref(future)

0 commit comments

Comments
 (0)