Skip to content

Commit 39ee7d9

Browse files
authored
Merge pull request #936 from krassowski/fix/924-prevent-repetitive-error
Limit the shadow FS initialisation to three attempts
2 parents b576383 + 57aed8f commit 39ee7d9

File tree

2 files changed

+78
-9
lines changed

2 files changed

+78
-9
lines changed

python_packages/jupyter_lsp/jupyter_lsp/tests/test_virtual_documents_shadow.py

Lines changed: 54 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
1+
import logging
12
from pathlib import Path
23
from types import SimpleNamespace
34
from typing import List
45

56
import pytest
67

8+
from jupyter_lsp import LanguageServerManager
9+
710
from ..virtual_documents_shadow import (
811
EditableFile,
912
ShadowFilesystemError,
@@ -93,9 +96,16 @@ def shadow_path(tmpdir):
9396

9497
@pytest.fixture
9598
def manager():
96-
return SimpleNamespace(
97-
language_servers={"python-lsp-server": {"requires_documents_on_disk": True}}
98-
)
99+
manager = LanguageServerManager()
100+
manager.language_servers = {
101+
"python-lsp-server": {
102+
"requires_documents_on_disk": True,
103+
"argv": [],
104+
"languages": ["python"],
105+
"version": 2,
106+
}
107+
}
108+
return manager
99109

100110

101111
@pytest.mark.asyncio
@@ -198,3 +208,44 @@ def run_shadow(message):
198208
"params": {"textDocument": {"uri": ok_file_uri}},
199209
}
200210
)
211+
212+
213+
@pytest.fixture
214+
def forbidden_shadow_path(tmpdir):
215+
path = Path(tmpdir) / "no_permission_dir"
216+
path.mkdir()
217+
path.chmod(0o000)
218+
219+
yield path
220+
221+
# re-adjust the permissions, see https://github.com/pytest-dev/pytest/issues/7821
222+
path.chmod(0o755)
223+
224+
225+
@pytest.mark.asyncio
226+
async def test_io_failure(forbidden_shadow_path, manager, caplog):
227+
file_uri = (forbidden_shadow_path / "test.py").as_uri()
228+
229+
shadow = setup_shadow_filesystem(forbidden_shadow_path.as_uri())
230+
231+
def send_change():
232+
message = did_open(file_uri, "content")
233+
return shadow("client", message, "python-lsp-server", manager)
234+
235+
with caplog.at_level(logging.WARNING):
236+
assert await send_change() is None
237+
assert await send_change() is None
238+
# no message should be emitted during the first two attempts
239+
assert caplog.text == ""
240+
241+
# a wargning should be emitted on third failure
242+
with caplog.at_level(logging.WARNING):
243+
assert await send_change() is None
244+
assert "initialization of shadow filesystem failed three times" in caplog.text
245+
assert "PermissionError" in caplog.text
246+
caplog.clear()
247+
248+
# no message should be emitted in subsequent attempts
249+
with caplog.at_level(logging.WARNING):
250+
assert await send_change() is None
251+
assert caplog.text == ""

python_packages/jupyter_lsp/jupyter_lsp/virtual_documents_shadow.py

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
from concurrent.futures import ThreadPoolExecutor
33
from pathlib import Path
44
from shutil import rmtree
5+
from typing import List
56

67
from tornado.concurrent import run_on_executor
78
from tornado.gen import convert_yielded
@@ -111,6 +112,8 @@ def setup_shadow_filesystem(virtual_documents_uri: str):
111112
)
112113

113114
initialized = False
115+
failures: List[Exception] = []
116+
114117
shadow_filesystem = Path(file_uri_to_path(virtual_documents_uri))
115118

116119
@lsp_message_listener("client")
@@ -145,12 +148,27 @@ async def shadow_virtual_documents(scope, message, language_server, manager):
145148

146149
# initialization (/any file system operations) delayed until needed
147150
if not initialized:
148-
# create if does no exist (so that removal does not raise)
149-
shadow_filesystem.mkdir(parents=True, exist_ok=True)
150-
# remove with contents
151-
rmtree(str(shadow_filesystem))
152-
# create again
153-
shadow_filesystem.mkdir(parents=True, exist_ok=True)
151+
if len(failures) == 3:
152+
return
153+
try:
154+
# create if does no exist (so that removal does not raise)
155+
shadow_filesystem.mkdir(parents=True, exist_ok=True)
156+
# remove with contents
157+
rmtree(str(shadow_filesystem))
158+
# create again
159+
shadow_filesystem.mkdir(parents=True, exist_ok=True)
160+
except (OSError, PermissionError, FileNotFoundError) as e:
161+
failures.append(e)
162+
if len(failures) == 3:
163+
manager.log.warn(
164+
"[lsp] initialization of shadow filesystem failed three times"
165+
" check if the path set by `LanguageServerManager.virtual_documents_dir`"
166+
" or `JP_LSP_VIRTUAL_DIR` is correct; if this is happening with a server"
167+
" for which which you control (or wish to override) jupyter-lsp specification"
168+
" you can try switching `requires_documents_on_disk` off. The errors were: %s",
169+
failures,
170+
)
171+
return
154172
initialized = True
155173

156174
path = file_uri_to_path(uri)

0 commit comments

Comments
 (0)