microsoft / microsoft/multilspy
Opening files in SyncLanguageServer may cause deadlocks
Nobody has claimed this yet.
- Dominant language
- Python
- Stars
- 610
- Forks
- 111
- PR merge metrics
- No merged PRs in 30d
Description
A quick investigation revealed that while start_server launches processing in a separate thread, open_file itself is not launched in the thread. The issue was resolved by executing open_file's processing in the thread, suggesting that internally, there may be operations that are not thread-safe.
I haven't tested all languages, but it occurs at least for Java and C++.
SyncLanguageServer should be modified to completely hide the existence of separate threads.
Environment
OS: Windows 11
Python: 3.13
Reproduction Code
Freezes within 10 times
from pathlib import Path
from multilspy import SyncLanguageServer
from multilspy.multilspy_config import MultilspyConfig
from multilspy.multilspy_logger import MultilspyLogger
def main():
path = Path("temp")
path.mkdir(exist_ok=True)
with open(path / "test.java", "w"):
pass
params = {
"code_language": "java",
}
config = MultilspyConfig.from_dict(params)
logger = MultilspyLogger()
for i in range(100):
print(f"start: {i}")
lsp = SyncLanguageServer.create(config, logger, str(path.absolute()))
with lsp.start_server():
with lsp.open_file("test.java"):
pass
# Changing the processing as described in the comment-out will avoid the problem.
# async def sub():
# with lsp.open_file("test.java"):
# pass
# asyncio.run_coroutine_threadsafe(sub(), lsp.loop).result(
# timeout=lsp.timeout
# )
print(f"end: {i}")
if __name__ == "__main__":
main()
I don't fully understand the internal processing, but the following fixes are likely candidates:
@contextmanager
def open_file(self, relative_file_path: str) -> Iterator[None]:
"""
Open a file in the Language Server. This is required before making any requests to the Language Server.
:param relative_file_path: The relative path of the file to open.
"""
f = self.language_server.open_file(relative_file_path)
async def enter():
f.__enter__()
async def exit():
f.__exit__(None, None, None)
asyncio.run_coroutine_threadsafe(enter(), lsp.loop).result(
timeout=lsp.timeout
)
try:
yield
finally:
asyncio.run_coroutine_threadsafe(exit(), lsp.loop).result(
timeout=lsp.timeout
)
Contributor guide
No contributing guide indexed for this repository
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start with SyncLanguageServer.start_server and open_file, then inspect how language_server.open_file is entered and how lsp.loop is used. Run the supplied Windows reproduction with Java and C++; done means open_file processing is hidden on the server thread and the repeated start/open/close cycle no longer freezes.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- python
- Domain
- devtools
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Stale
- Clarity
- Mostly clear
- Newbie friendliness
- 45/100