Skip to content

Reset readiness events so start_server can be entered more than once - #169

Open
Puliuvarthi.Mahesh (MaheshPulivarthi18) wants to merge 1 commit into
microsoft:mainfrom
MaheshPulivarthi18:fix/101-start-server-context-manager-not-idempot
Open

Puliuvarthi.Mahesh (MaheshPulivarthi18) wants to merge 1 commit into
microsoft:mainfrom
MaheshPulivarthi18:fix/101-start-server-context-manager-not-idempot

Conversation

@MaheshPulivarthi18

Copy link
Copy Markdown

Fixes #101.

Problem

Entering start_server() a second time on the same language server instance returned None for requests such as hover. The readiness asyncio.Event objects (server_ready, completions_available, etc.) are created once and stay set after the first session. The second session therefore starts with stale state. SyncLanguageServer also runs each session on a new event loop, so reusing an old Event (or calling clear() on it) risks a 'bound to a different event loop' error.

Change

In the seven servers that keep readiness events (clangd, Eclipse JDTLS, gopls, OmniSharp, rust-analyzer, Solargraph, TypeScript), the events are now recreated with a fresh asyncio.Event() before the server process starts. They are recreated again in the finally block after shutdown, so no set event remains between sessions.

I added tests/multilspy/test_multilspy_restart.py. It enters start_server() twice on one SyncLanguageServer for TypeScript and checks that both sessions return the same definition result. It also checks that the events are unset and replaced after the first session.

For the reviewer

  • I recreated the events rather than calling clear(), because of the event loop binding described above.
  • The same reset lines are repeated in each of the seven servers. A shared helper on the base class would be a reasonable follow-up, but I kept this change small and did not redesign the base class.
  • The new test covers only the TypeScript server. The other six edits are the same change, but they have no test of their own.
  • The event-state assertions in the test are supporting root-cause checks. The result-equality check is the user-visible behaviour from the issue.

Testing

New test tests/multilspy/test_multilspy_restart.py: one SyncLanguageServer (TypeScript) entered twice with with lsp.start_server().

  • Before the fix: fails. After the first session ends, server_ready is still set:
E            +  where True = is_set()
E            +    where is_set = <asyncio.locks.Event object at 0xffffa73a1a90 [set]>.is_set
tests/multilspy/test_multilspy_restart.py:31: AssertionError
FAILED tests/multilspy/test_multilspy_restart.py::test_sync_multilspy_typescript_start_server_twice
1 failed in 2.09s
  • After the fix: 1 passed in 3.78s
  • Existing tests: test_multilspy_python.py, test_sync_multilspy_python.py, test_multilspy_typescript.py, test_sync_file_thread.py and test_adapter_notifications.py gave 18 passed before the change, and 19 passed after it (the 18 plus the new test). They were run with .venv/bin on PATH so jedi-language-server is found.
  • Not run: the rust-analyzer, gopls, clangd, Solargraph, OmniSharp and Eclipse JDTLS suites, because those toolchains are not installed in my environment. The edits to those six servers are the same as the TypeScript one.

The readiness asyncio.Event objects stayed set after the first session and could be bound to a stale event loop, so a second start_server() on the same instance returned None. Recreate the events before startup and after shutdown in the clangd, JDTLS, gopls, OmniSharp, rust-analyzer, Solargraph and TypeScript servers. Add a restart test for the TypeScript server.
@MaheshPulivarthi18

Copy link
Copy Markdown
Author

@microsoft-github-policy-service agree

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

start_server context manager not idempotent

1 participant