Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[wip] defer initialization until event loop is available #852

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions python_packages/jupyter_lsp/jupyter_lsp/handlers.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@ class LanguageServerWebSocketHandler( # type: ignore

language_server = None # type: Optional[Text]

def open(self, language_server):
async def open(self, language_server):
await self.manager.ready()
self.language_server = language_server
self.manager.subscribe(self)
self.log.debug("[{}] Opened a handler".format(self.language_server))
Expand All @@ -51,8 +52,10 @@ class LanguageServersHandler(BaseHandler):
def initialize(self, *args, **kwargs):
super().initialize(*args, **kwargs)

def get(self):
async def get(self):
"""finish with the JSON representations of the sessions"""
await self.manager.ready()

response = {
"version": 2,
"sessions": {
Expand Down
11 changes: 11 additions & 0 deletions python_packages/jupyter_lsp/jupyter_lsp/manager.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
""" A configurable frontend for stdio-based Language Servers
"""
import asyncio
import os
import traceback
from typing import Dict, Text, Tuple, cast
Expand Down Expand Up @@ -74,6 +75,10 @@ class LanguageServerManager(LanguageServerManagerAPI):
"""
).tag(config=True)

_ready = Bool(
help="""Whether the manager has been initialized""", default_value=False
)

all_listeners = List_(trait=LoadableCallable).tag(config=True)
server_listeners = List_(trait=LoadableCallable).tag(config=True)
client_listeners = List_(trait=LoadableCallable).tag(config=True)
Expand Down Expand Up @@ -111,6 +116,12 @@ def initialize(self, *args, **kwargs):
self.init_language_servers()
self.init_listeners()
self.init_sessions()
self._ready = True

async def ready(self):
while not self._ready: # pragma: no cover
asyncio.sleep(0.1)
return True

def init_language_servers(self) -> None:
"""determine the final language server configuration."""
Expand Down
40 changes: 25 additions & 15 deletions python_packages/jupyter_lsp/jupyter_lsp/serverextension.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,43 +8,53 @@
from .handlers import add_handlers
from .manager import LanguageServerManager
from .paths import normalized_uri
from .virtual_documents_shadow import setup_shadow_filesystem


async def initialize(nbapp, virtual_documents_uri): # pragma: no cover
"""Perform lazy initialization."""
import concurrent.futures

from .virtual_documents_shadow import setup_shadow_filesystem

manager = nbapp.language_server_manager

with concurrent.futures.ThreadPoolExecutor() as pool:
await nbapp.io_loop.run_in_executor(pool, manager.initialize)

setup_shadow_filesystem(virtual_documents_uri=virtual_documents_uri)

nbapp.log.debug(
"[lsp] The following Language Servers will be available: {}".format(
json.dumps(manager.language_servers, indent=2, sort_keys=True)
)
)


def load_jupyter_server_extension(nbapp):
"""create a LanguageServerManager and add handlers"""
nbapp.add_traits(language_server_manager=traitlets.Instance(LanguageServerManager))
manager = nbapp.language_server_manager = LanguageServerManager(parent=nbapp)
manager.initialize()

contents = nbapp.contents_manager
page_config = nbapp.web_app.settings.setdefault("page_config_data", {})

root_uri = ""
virtual_documents_uri = ""

# try to set the rootUri from the contents manager path
if hasattr(contents, "root_dir"):
root_uri = normalized_uri(contents.root_dir)
page_config["rootUri"] = root_uri
nbapp.log.debug("[lsp] rootUri will be %s", root_uri)

virtual_documents_uri = normalized_uri(
Path(contents.root_dir) / manager.virtual_documents_dir
)
page_config["virtualDocumentsUri"] = virtual_documents_uri
nbapp.log.debug("[lsp] virtualDocumentsUri will be %s", virtual_documents_uri)
else: # pragma: no cover
page_config["rootUri"] = ""
page_config["virtualDocumentsUri"] = ""
nbapp.log.warn(
"[lsp] %s did not appear to have a root_dir, could not set rootUri",
contents,
)
page_config.update(rootUri=root_uri, virtualDocumentsUri=virtual_documents_uri)

add_handlers(nbapp)

nbapp.log.debug(
"[lsp] The following Language Servers will be available: {}".format(
json.dumps(manager.language_servers, indent=2, sort_keys=True)
)
)

setup_shadow_filesystem(virtual_documents_uri=virtual_documents_uri)
nbapp.io_loop.call_later(0, initialize, nbapp, virtual_documents_uri)
Original file line number Diff line number Diff line change
Expand Up @@ -24,5 +24,5 @@ class JuliaLanguageServer(ShellSpec):
issues="https://github.com/julia-vscode/LanguageServer.jl/issues",
),
install=dict(julia='using Pkg; Pkg.add("LanguageServer")'),
config_schema=load_config_schema(key)
config_schema=load_config_schema(key),
)
2 changes: 1 addition & 1 deletion python_packages/jupyter_lsp/jupyter_lsp/specs/pyright.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,5 +19,5 @@ class PyrightLanguageServer(NodeModuleSpec):
yarn="yarn add --dev {}".format(key),
jlpm="jlpm add --dev {}".format(key),
),
config_schema=load_config_schema(key)
config_schema=load_config_schema(key),
)
Original file line number Diff line number Diff line change
Expand Up @@ -37,5 +37,5 @@ class TypescriptLanguageServer(NodeModuleSpec):
yarn="yarn add --dev {}".format(key),
jlpm="jlpm add --dev {}".format(key),
),
config_schema=load_config_schema(key)
config_schema=load_config_schema(key),
)
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ async def all_listener(
assert len(manager._listeners["client"]) == 2
assert len(manager._listeners["all"]) == 2

ws_handler.open(known_server)
await ws_handler.open(known_server)

await ws_handler.on_message(jsonrpc_init_msg)

Expand Down
26 changes: 13 additions & 13 deletions python_packages/jupyter_lsp/jupyter_lsp/tests/test_session.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@
from ..schema import SERVERS_RESPONSE


def assert_status_set(handler, expected_statuses, language_server=None):
handler.get()
async def assert_status_set(handler, expected_statuses, language_server=None):
await handler.get()
payload = handler._payload

errors = list(SERVERS_RESPONSE.iter_errors(payload))
Expand All @@ -28,13 +28,13 @@ async def test_start_known(known_server, handlers, jsonrpc_init_msg):

manager.initialize()

assert_status_set(handler, {"not_started"})
await assert_status_set(handler, {"not_started"})

ws_handler.open(known_server)
await ws_handler.open(known_server)
session = manager.sessions[ws_handler.language_server]
assert session.process is not None

assert_status_set(handler, {"started"}, known_server)
await assert_status_set(handler, {"started"}, known_server)

await ws_handler.on_message(jsonrpc_init_msg)

Expand All @@ -50,8 +50,8 @@ async def test_start_known(known_server, handlers, jsonrpc_init_msg):
assert not session.handlers
assert not session.process

assert_status_set(handler, {"stopped"}, known_server)
assert_status_set(handler, {"stopped", "not_started"})
await assert_status_set(handler, {"stopped"}, known_server)
await assert_status_set(handler, {"stopped", "not_started"})


@pytest.mark.asyncio
Expand All @@ -61,18 +61,18 @@ async def test_start_unknown(known_unknown_server, handlers, jsonrpc_init_msg):
manager = handler.manager
manager.initialize()

assert_status_set(handler, {"not_started"})
await assert_status_set(handler, {"not_started"})

ws_handler.open(known_unknown_server)
await ws_handler.open(known_unknown_server)

assert_status_set(handler, {"not_started"})
await assert_status_set(handler, {"not_started"})

await ws_handler.on_message(jsonrpc_init_msg)
assert_status_set(handler, {"not_started"})
await assert_status_set(handler, {"not_started"})
ws_handler.on_close()

assert not manager.sessions.get(ws_handler.language_server)
assert_status_set(handler, {"not_started"})
await assert_status_set(handler, {"not_started"})


@pytest.mark.asyncio
Expand All @@ -92,7 +92,7 @@ async def test_ping(handlers):

assert ws_handler._ping_sent is False

ws_handler.open(a_server)
await ws_handler.open(a_server)

assert ws_handler.ping_callback is not None and ws_handler.ping_callback.is_running
await asyncio.sleep(ws_handler.ping_interval * 3)
Expand Down