Skip to content

Commit 043935a

Browse files
committed
optimize text document cache and data algorithms
1 parent 092ca97 commit 043935a

File tree

6 files changed

+99
-45
lines changed

6 files changed

+99
-45
lines changed

robotcode/language_server/common/parts/workspace.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -408,7 +408,7 @@ async def add_file_watchers(
408408

409409
def remove() -> None:
410410
if self._loop.is_running():
411-
asyncio.run_coroutine_threadsafe(self.remove_file_watcher_entry(entry), self._loop).result()
411+
asyncio.run_coroutine_threadsafe(self.remove_file_watcher_entry(entry), self._loop)
412412

413413
weakref.finalize(entry, remove)
414414

robotcode/language_server/common/text_document.py

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ def __init__(
5757
super().__init__()
5858

5959
self._lock = asyncio.Lock()
60+
6061
self._references: weakref.WeakSet[Any] = weakref.WeakSet()
6162

6263
self.document_uri = (
@@ -84,8 +85,6 @@ def __init__(
8485

8586
self._data: weakref.WeakKeyDictionary[Any, Any] = weakref.WeakKeyDictionary()
8687

87-
self._loop = asyncio.get_event_loop()
88-
8988
@property
9089
def references(self) -> weakref.WeakSet[Any]: # pragma: no cover
9190
return self._references
@@ -180,22 +179,24 @@ async def invalidate_data(self) -> None:
180179
async with self._lock:
181180
self._invalidate_data()
182181

183-
async def __remove_cache_entry_safe(self, ref: Any) -> None:
184-
async with self._lock:
185-
self._cache.pop(ref)
186-
187182
def __remove_cache_entry(self, ref: Any) -> None:
188-
if self._loop is not None and self._loop.is_running():
189-
asyncio.run_coroutine_threadsafe(self.__remove_cache_entry_safe(ref), self._loop)
183+
async def __remove_cache_entry_safe(_ref: Any) -> None:
184+
async with self._lock:
185+
self._cache.pop(_ref)
186+
187+
if self._lock.locked():
188+
asyncio.create_task(__remove_cache_entry_safe(ref))
190189
else:
191-
self._cache.pop(ref) # pragma: no cover
190+
self._cache.pop(ref)
192191

193-
def __get_cache_reference(self, entry: Callable[..., Any]) -> weakref.ref[Any]:
192+
def __get_cache_reference(self, entry: Callable[..., Any], /, *, add_remove: bool = True) -> weakref.ref[Any]:
194193

195194
if inspect.ismethod(entry):
196-
reference: weakref.ref[Any] = weakref.WeakMethod(cast(MethodType, entry), self.__remove_cache_entry)
195+
reference: weakref.ref[Any] = weakref.WeakMethod(
196+
cast(MethodType, entry), self.__remove_cache_entry if add_remove else None
197+
)
197198
else:
198-
reference = weakref.ref(entry, self.__remove_cache_entry)
199+
reference = weakref.ref(entry, self.__remove_cache_entry if add_remove else None)
199200

200201
return reference
201202

@@ -226,10 +227,9 @@ async def remove_cache_entry(
226227
self, entry: Union[Callable[[TextDocument], Awaitable[_T]], Callable[..., Awaitable[_T]]]
227228
) -> None:
228229
async with self._lock:
229-
if inspect.ismethod(entry):
230-
self._cache.pop(weakref.WeakMethod(cast(MethodType, entry)), None)
231-
else:
232-
self._cache.pop(weakref.ref(entry), None)
230+
self.__remove_cache_entry(self.__get_cache_reference(entry, add_remove=False))
231+
232+
await asyncio.sleep(0)
233233

234234
def set_data(self, key: Any, data: Any) -> None:
235235
self._data[key] = data

tests/robotcode/language_server/common/test_text_document.py

Lines changed: 62 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,3 @@
1-
import asyncio
2-
from asyncio.events import AbstractEventLoop
3-
from typing import Generator
4-
51
import pytest
62

73
from robotcode.language_server.common.lsp_types import Position, Range
@@ -11,13 +7,6 @@
117
)
128

139

14-
@pytest.fixture
15-
def event_loop() -> Generator[AbstractEventLoop, None, None]:
16-
loop = asyncio.new_event_loop()
17-
yield loop
18-
loop.close()
19-
20-
2110
@pytest.mark.asyncio
2211
async def test_apply_full_change_should_work() -> None:
2312
text = """first"""
@@ -141,17 +130,17 @@ async def test_lines_should_give_the_lines_of_the_document() -> None:
141130
assert document.lines == text.splitlines(True)
142131

143132

144-
class WeakReferencable:
145-
pass
146-
147-
148133
@pytest.mark.asyncio
149134
async def test_document_get_set_clear_data_should_work() -> None:
150135
text = """\
151136
first
152137
second
153138
third
154139
"""
140+
141+
class WeakReferencable:
142+
pass
143+
155144
key = WeakReferencable()
156145
data = "some data"
157146

@@ -162,6 +151,11 @@ async def test_document_get_set_clear_data_should_work() -> None:
162151
await document.clear()
163152
assert document.get_data(key, None) is None
164153

154+
document.set_data(key, data)
155+
assert document.get_data(key) == data
156+
await document.invalidate_data()
157+
assert document.get_data(key, None) is None
158+
165159

166160
@pytest.mark.asyncio
167161
async def test_document_get_set_cache_with_function_should_work() -> None:
@@ -170,16 +164,29 @@ async def test_document_get_set_cache_with_function_should_work() -> None:
170164
second
171165
third
172166
"""
167+
prefix = "1"
173168

174169
async def get_data(document: TextDocument, data: str) -> str:
175-
return "blah" + data
170+
return prefix + data
176171

177172
document = TextDocument(document_uri="file://test.robot", language_id="robotframework", version=1, text=text)
178173

179-
assert await document.get_cache(get_data, "data") == "blahdata"
174+
assert await document.get_cache(get_data, "data") == "1data"
175+
176+
prefix = "2"
177+
assert await document.get_cache(get_data, "data1") == "1data"
180178

181179
await document.remove_cache_entry(get_data)
182180

181+
assert await document.get_cache(get_data, "data2") == "2data2"
182+
183+
prefix = "3"
184+
assert await document.get_cache(get_data, "data3") == "2data2"
185+
186+
await document.invalidate_cache()
187+
188+
assert await document.get_cache(get_data, "data3") == "3data3"
189+
183190

184191
@pytest.mark.asyncio
185192
async def test_document_get_set_cache_with_method_should_work() -> None:
@@ -190,13 +197,48 @@ async def test_document_get_set_cache_with_method_should_work() -> None:
190197
"""
191198
document = TextDocument(document_uri="file://test.robot", language_id="robotframework", version=1, text=text)
192199

200+
prefix = "1"
201+
193202
class Dummy:
194203
async def get_data(self, document: TextDocument, data: str) -> str:
195-
return "blah" + data
204+
return prefix + data
196205

197206
dummy = Dummy()
198207

199-
assert await document.get_cache(dummy.get_data, "data") == "blahdata"
208+
assert await document.get_cache(dummy.get_data, "data") == "1data"
209+
210+
prefix = "2"
211+
assert await document.get_cache(dummy.get_data, "data1") == "1data"
212+
213+
await document.remove_cache_entry(dummy.get_data)
214+
215+
assert await document.get_cache(dummy.get_data, "data2") == "2data2"
216+
217+
prefix = "3"
218+
assert await document.get_cache(dummy.get_data, "data3") == "2data2"
219+
220+
await document.invalidate_cache()
221+
222+
assert await document.get_cache(dummy.get_data, "data3") == "3data3"
200223

201-
# await document.remove_cache_entry(dummy.get_data)
202224
del dummy
225+
226+
assert len(document._cache) == 0
227+
228+
229+
@pytest.mark.asyncio
230+
async def test_document_get_set_cache_with_lock_work() -> None:
231+
text = """\
232+
first
233+
second
234+
third
235+
"""
236+
prefix = "1"
237+
238+
async def get_data(document: TextDocument, data: str) -> str:
239+
await document.remove_cache_entry(get_data)
240+
return prefix + data
241+
242+
document = TextDocument(document_uri="file://test.robot", language_id="robotframework", version=1, text=text)
243+
244+
assert await document.get_cache(get_data, "data") == "1data"

tests/robotcode/language_server/robotframework/conftest.py

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import asyncio
2+
import logging
23
from pathlib import Path
34
from typing import Any, AsyncGenerator, Generator, cast
45

@@ -26,14 +27,24 @@
2627
from robotcode.utils.dataclasses import as_dict
2728

2829

29-
@pytest.fixture(scope="module")
30+
@pytest.fixture(scope="function")
3031
def event_loop() -> Generator[asyncio.AbstractEventLoop, None, None]:
32+
logging.info("create event_loop")
3133
loop = asyncio.new_event_loop()
32-
yield loop
33-
loop.close()
34+
try:
35+
yield loop
36+
finally:
37+
for t in asyncio.all_tasks(loop):
38+
logging.error(t)
39+
40+
logging.info("event_loop close")
41+
42+
# loop.close()
43+
44+
logging.info("event_loop closed")
3445

3546

36-
@pytest.fixture(scope="module")
47+
@pytest.fixture(scope="function")
3748
async def protocol(event_loop: asyncio.AbstractEventLoop) -> AsyncGenerator[RobotLanguageServerProtocol, None]:
3849
root_path = Path().resolve()
3950
server = RobotLanguageServer()
@@ -70,7 +81,7 @@ async def protocol(event_loop: asyncio.AbstractEventLoop) -> AsyncGenerator[Robo
7081
server.close()
7182

7283

73-
@pytest.fixture(scope="module")
84+
@pytest.fixture(scope="function")
7485
async def test_document(event_loop: asyncio.AbstractEventLoop, request: Any) -> AsyncGenerator[TextDocument, None]:
7586
data_path = Path(request.param)
7687
data = data_path.read_text()
@@ -81,4 +92,5 @@ async def test_document(event_loop: asyncio.AbstractEventLoop, request: Any) ->
8192
try:
8293
yield document
8394
finally:
84-
del document
95+
# del document
96+
pass

tests/robotcode/language_server/robotframework/parts/test_goto.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,8 @@
2222
indirect=["test_document"],
2323
ids=generate_test_id,
2424
)
25-
@pytest.mark.asyncio
2625
@pytest.mark.usefixtures("protocol")
26+
@pytest.mark.asyncio
2727
async def test_goto(
2828
protocol: RobotLanguageServerProtocol,
2929
test_document: TextDocument,

tests/robotcode/language_server/robotframework/parts/test_hover.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,8 @@
2222
indirect=["test_document"],
2323
ids=generate_test_id,
2424
)
25-
@pytest.mark.asyncio
2625
@pytest.mark.usefixtures("protocol")
26+
@pytest.mark.asyncio
2727
async def test_hover(
2828
protocol: RobotLanguageServerProtocol,
2929
test_document: TextDocument,

0 commit comments

Comments
 (0)