From 73f6f2ceb2a21a63593eedc8a59a509c11510b85 Mon Sep 17 00:00:00 2001 From: Alex Carney Date: Sun, 24 Nov 2024 18:37:30 +0000 Subject: [PATCH 01/10] lsp: Bump pygls to v2.0a2 --- .pre-commit-config.yaml | 4 ++-- lib/esbonio/pyproject.toml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 2c71f93f9..febb8b627 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -45,8 +45,8 @@ repos: additional_dependencies: - aiosqlite - platformdirs - - pygls>=2a0 - - pytest_lsp>=0.3 + - pygls>=2a2 + - pytest_lsp>=1.0b2 - sphinx - tomli - types-docutils diff --git a/lib/esbonio/pyproject.toml b/lib/esbonio/pyproject.toml index f41b84320..4a08e1422 100644 --- a/lib/esbonio/pyproject.toml +++ b/lib/esbonio/pyproject.toml @@ -28,7 +28,7 @@ dependencies = [ "aiosqlite", "platformdirs", "docutils", - "pygls>=2.0a0", + "pygls>=2.0a2", "tomli ; python_version<'3.11'", "websockets", ] From 8a8e4bd0cdb47213ee20ef6d28e1d3e166cccadd Mon Sep 17 00:00:00 2001 From: Alex Carney Date: Sun, 24 Nov 2024 18:44:05 +0000 Subject: [PATCH 02/10] lsp: Use `concurrent.futures.Future` instead of `asyncio.Future` pygls in `v2.0a2` switched from using the low-level asyncio APIs to using the high-level one and surprisingly, this broke `esbonio`. The server would start ok, but wouldn't launch Sphinx processes, the preview command did nothing, it wouldn't even produce any log messages! That is, until the user changed a configuration setting. After changing a setting - any setting, the server suddenly springs into life, logs, previews, sphinx processes all of them would start working as if nothing was wrong. I eventually managed to figure out that the callbacks registered by the configuration system on the server's `ready` future were never being called. ``` self.server.ready.add_done_callback(self._notify_subscriptions) ``` By why would changing the asyncio API in use break these callbacks?! After spending some time with the debugger I eventually spotted the culprit ``` >>> asyncio.get_running_loop() <_UnixSelectorEventLoop running=True closed=False debug=False> >>> self.server.ready._loop <_UnixSelectorEventLoop running=False closed=False debug=False> ``` The server's `ready` future was using a different event loop and because the event loop is not running, when the future is resolved, the callbacks were never scheduled! While I cannot explain why this was not an issue before, I can explain why it is an issue now. The `ready` future is created in the constructor of the `EsbonioLanguageServer` class - before any event loop has been created and so it uses a new one based on the current event loop policy[1][2]. Unfortunately, when pygls later starts its main loop by calling `asyncio.run()` it creates a new event loop, orphaning the `ready` future in its unused event loop[3] Since we don't need to use the `ready` future asynchronously, the simplest fix is to convert it to future from the `concurrent.futures` module, removing the need for an event loop completely. [1]: https://github.com/python/cpython/blob/307c63358681d669ae39e5ecd814bded4a93443a/Lib/asyncio/futures.py#L79 [2]: https://github.com/python/cpython/blob/307c63358681d669ae39e5ecd814bded4a93443a/Lib/asyncio/events.py#L791 [3]: https://github.com/python/cpython/blob/307c63358681d669ae39e5ecd814bded4a93443a/Lib/asyncio/runners.py#L146 --- lib/esbonio/esbonio/server/server.py | 5 ++- lib/esbonio/tests/e2e/test_sphinx_manager.py | 45 +++++++++++--------- 2 files changed, 27 insertions(+), 23 deletions(-) diff --git a/lib/esbonio/esbonio/server/server.py b/lib/esbonio/esbonio/server/server.py index e21957aec..e177fcbab 100644 --- a/lib/esbonio/esbonio/server/server.py +++ b/lib/esbonio/esbonio/server/server.py @@ -7,6 +7,7 @@ import platform import traceback import typing +from concurrent.futures import Future from typing import TypeVar from uuid import uuid4 @@ -79,7 +80,7 @@ def __init__(self, logger: logging.Logger | None = None, *args, **kwargs): self._features: dict[type[LanguageFeature], LanguageFeature] = {} """The collection of language features registered with the server.""" - self._ready: asyncio.Future[bool] = asyncio.Future() + self._ready: Future[bool] = Future() """Indicates if the server is ready.""" self._tasks: set[asyncio.Task] = set() @@ -95,7 +96,7 @@ def __iter__(self): return iter(self._features.items()) @property - def ready(self) -> asyncio.Future: + def ready(self) -> Future[bool]: return self._ready @property diff --git a/lib/esbonio/tests/e2e/test_sphinx_manager.py b/lib/esbonio/tests/e2e/test_sphinx_manager.py index 4bb4d38c0..59480ae39 100644 --- a/lib/esbonio/tests/e2e/test_sphinx_manager.py +++ b/lib/esbonio/tests/e2e/test_sphinx_manager.py @@ -24,10 +24,13 @@ from esbonio.server.features.sphinx_manager import make_subprocess_sphinx_client if typing.TYPE_CHECKING: + from collections.abc import Coroutine from typing import Any from typing import Callable - ServerManager = Callable[[Any], tuple[EsbonioLanguageServer, SphinxManager]] + ServerManager = Callable[ + [Any], Coroutine[None, None, tuple[EsbonioLanguageServer, SphinxManager]] + ] @pytest.fixture @@ -57,7 +60,7 @@ async def server_manager(demo_workspace: Uri, docs_workspace): ) esbonio.add_feature(sphinx_manager) - def initialize(init_options): + async def initialize(init_options): # Initialize the server. esbonio.protocol._procedure_handler( lsp.InitializeRequest( @@ -76,6 +79,16 @@ def initialize(init_options): esbonio.protocol._procedure_handler( lsp.InitializedNotification(params=lsp.InitializedParams()) ) + + # Ensure that the server is ready + retry = 10 + while (not esbonio.ready.done()) and retry > 0: + await asyncio.sleep(0.5) + retry -= 1 + + if not esbonio.ready.done(): + raise RuntimeError("Server did not initialize") + return esbonio, sphinx_manager yield initialize @@ -89,7 +102,7 @@ async def test_get_client( ): """Ensure that we can create a SphinxClient correctly.""" - server, manager = server_manager( + _, manager = await server_manager( dict( esbonio=dict( sphinx=dict( @@ -103,8 +116,6 @@ async def test_get_client( ), ), ) - # Ensure that the server is ready - await server.ready result = await manager.get_client(demo_workspace / "index.rst") # At least for now, the first call to get_client will not return a client @@ -141,7 +152,7 @@ async def test_get_client_with_error( ): """Ensure that we correctly handle the case where there is an error with the client.""" - server, manager = server_manager( + _, manager = await server_manager( dict( esbonio=dict( sphinx=dict( @@ -150,8 +161,6 @@ async def test_get_client_with_error( ), ), ) - # Ensure that the server is ready - await server.ready result = await manager.get_client(demo_workspace / "index.rst") # At least for now, the first call to get_client will not return a client @@ -195,7 +204,7 @@ async def test_get_client_with_many_uris( """Ensure that when called in rapid succession, with many uris we only create a single client instance.""" - server, manager = server_manager( + _, manager = await server_manager( dict( esbonio=dict( sphinx=dict( @@ -210,9 +219,6 @@ async def test_get_client_with_many_uris( ), ) - # Ensure that the server is ready - await server.ready - src_uris = [Uri.for_file(f) for f in pathlib.Path(demo_workspace).glob("**/*.rst")] coros = [manager.get_client(s) for s in src_uris] @@ -226,7 +232,7 @@ async def test_get_client_with_many_uris( client = manager.clients[str(demo_workspace)] assert client is not None - assert client.state is None + assert client.state == ClientState.Starting # Now if we do the same again we should get the same client instance for each case. coros = [manager.get_client(s) for s in src_uris] @@ -254,7 +260,7 @@ async def test_get_client_with_many_uris_in_many_projects( """Ensure that when called in rapid succession, with many uris we only create a single client instance for each project.""" - server, manager = server_manager( + _, manager = await server_manager( dict( esbonio=dict( sphinx=dict( @@ -267,8 +273,7 @@ async def test_get_client_with_many_uris_in_many_projects( ), ), ), - ) # Ensure that the server is ready - await server.ready + ) src_uris = [Uri.for_file(f) for f in pathlib.Path(demo_workspace).glob("**/*.rst")] src_uris += [Uri.for_file(f) for f in pathlib.Path(docs_workspace).glob("**/*.rst")] @@ -284,11 +289,11 @@ async def test_get_client_with_many_uris_in_many_projects( demo_client = manager.clients[str(demo_workspace)] assert demo_client is not None - assert demo_client.state is None + assert demo_client.state == ClientState.Starting docs_client = manager.clients[str(docs_workspace)] assert docs_client is not None - assert docs_client.state is None + assert docs_client.state == ClientState.Starting # Now if we do the same again we should get the same client instance for each case. coros = [manager.get_client(s) for s in src_uris] @@ -309,7 +314,7 @@ async def test_updated_config( """Ensure that when the configuration affecting a Sphinx configuration is changed, the SphinxClient is recreated.""" - server, manager = server_manager( + server, manager = await server_manager( dict( esbonio=dict( sphinx=dict( @@ -323,8 +328,6 @@ async def test_updated_config( ), ), ) - # Ensure that the server is ready - await server.ready result = await manager.get_client(demo_workspace / "index.rst") # At least for now, the first call to get_client will not return a client From f2b276837e0a20c431f986bddddcc6f8a256193a Mon Sep 17 00:00:00 2001 From: Alex Carney Date: Tue, 26 Nov 2024 20:13:46 +0000 Subject: [PATCH 03/10] lsp: Rename `thread_pool_executor` to `thread_pool` --- lib/esbonio/esbonio/server/features/preview_manager/preview.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/esbonio/esbonio/server/features/preview_manager/preview.py b/lib/esbonio/esbonio/server/features/preview_manager/preview.py index 802a86bc6..61b19a852 100644 --- a/lib/esbonio/esbonio/server/features/preview_manager/preview.py +++ b/lib/esbonio/esbonio/server/features/preview_manager/preview.py @@ -128,4 +128,4 @@ def stop(self): def make_http_server( esbonio: server.EsbonioLanguageServer, config: PreviewConfig ) -> PreviewServer: - return PreviewServer(esbonio.logger, config, esbonio.thread_pool_executor) + return PreviewServer(esbonio.logger, config, esbonio.thread_pool) From 00797e2d1e79b2f2d8be4d1ccd33181497f6a298 Mon Sep 17 00:00:00 2001 From: Alex Carney Date: Tue, 26 Nov 2024 20:14:34 +0000 Subject: [PATCH 04/10] lsp: Refactor `WebviewServer` - Use new `pygls.io_` infrastructure - Use new `websockets.asyncio` API --- .../features/preview_manager/webview.py | 35 +++++++++---------- 1 file changed, 17 insertions(+), 18 deletions(-) diff --git a/lib/esbonio/esbonio/server/features/preview_manager/webview.py b/lib/esbonio/esbonio/server/features/preview_manager/webview.py index 408023988..086766389 100644 --- a/lib/esbonio/esbonio/server/features/preview_manager/webview.py +++ b/lib/esbonio/esbonio/server/features/preview_manager/webview.py @@ -1,22 +1,23 @@ from __future__ import annotations import asyncio -import json import logging import socket +import threading import typing from lsprotocol import types +from pygls.io_ import run_websocket from pygls.protocol import JsonRPCProtocol from pygls.protocol import default_converter from pygls.server import JsonRPCServer -from pygls.server import WebSocketTransportAdapter -from websockets.server import serve +from websockets.asyncio.server import serve from esbonio import server if typing.TYPE_CHECKING: - from websockets import WebSocketServer + from websockets.asyncio.server import Server as WebSocketServer + from websockets.asyncio.server import ServerConnection as WebSocketConnection from .config import PreviewConfig @@ -34,7 +35,6 @@ def __init__(self, logger: logging.Logger, config: PreviewConfig, *args, **kwarg self.config = config self.logger = logger.getChild("WebviewServer") - self.protocol._send_only_body = True self._connected = False self._ws_server: WebSocketServer | None = None @@ -121,31 +121,30 @@ def stop(self): async def _start_ws(self, host: str, port: int) -> None: """Actually, start the server.""" - async def connection(websocket): - loop = asyncio.get_running_loop() - transport = WebSocketTransportAdapter(websocket, loop) - - self.protocol.connection_made(transport) # type: ignore[arg-type] + async def connection(websocket: WebSocketConnection): self._connected = True self.logger.debug("Connected") - async for message in websocket: - self.protocol._procedure_handler( - json.loads(message, object_hook=self.protocol._deserialize_message) - ) + await run_websocket( + stop_event=threading.Event(), + websocket=websocket, + protocol=self.protocol, + logger=self.logger, + error_handler=self.report_server_error, + ) self.logger.debug("Connection lost") self._connected = False - async with serve( + self._ws_server = await serve( connection, host, port, # logger=self.logger.getChild("ws"), family=socket.AF_INET, # Use IPv4 only. - ) as ws_server: - self._ws_server = ws_server - await asyncio.Future() # run forever + ) + async with self._ws_server: + await self._ws_server.serve_forever() def make_ws_server( From 6bb4d68cb70c3dc40162ef84df67a12720194537 Mon Sep 17 00:00:00 2001 From: Alex Carney Date: Tue, 26 Nov 2024 20:16:43 +0000 Subject: [PATCH 05/10] lsp: Align to low-level API changes --- .../server/features/sphinx_manager/client_subprocess.py | 3 +-- lib/esbonio/tests/e2e/test_sphinx_manager.py | 7 +++---- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/lib/esbonio/esbonio/server/features/sphinx_manager/client_subprocess.py b/lib/esbonio/esbonio/server/features/sphinx_manager/client_subprocess.py index 4830b7d64..efb98d820 100644 --- a/lib/esbonio/esbonio/server/features/sphinx_manager/client_subprocess.py +++ b/lib/esbonio/esbonio/server/features/sphinx_manager/client_subprocess.py @@ -241,8 +241,7 @@ def _set_state(self, new_state: ClientState): async def stop(self): """Stop the client.""" - if self.state in {ClientState.Running, ClientState.Building}: - self.protocol.notify("exit", None) + self.protocol.notify("exit", None) # Give the agent a little time to close. await asyncio.sleep(0.5) diff --git a/lib/esbonio/tests/e2e/test_sphinx_manager.py b/lib/esbonio/tests/e2e/test_sphinx_manager.py index 59480ae39..2d4ade17d 100644 --- a/lib/esbonio/tests/e2e/test_sphinx_manager.py +++ b/lib/esbonio/tests/e2e/test_sphinx_manager.py @@ -13,7 +13,6 @@ import pytest_asyncio from lsprotocol import types as lsp from pygls import IS_WIN -from pygls.server import StdOutTransportAdapter from esbonio.server import EsbonioLanguageServer from esbonio.server import Uri @@ -50,7 +49,7 @@ async def server_manager(demo_workspace: Uri, docs_workspace): loop = asyncio.get_running_loop() esbonio = create_language_server(EsbonioLanguageServer, [], loop=loop) - esbonio.protocol.transport = StdOutTransportAdapter(io.BytesIO(), sys.stderr.buffer) + esbonio.protocol.set_writer(io.BytesIO()) project_manager = ProjectManager(esbonio) esbonio.add_feature(project_manager) @@ -62,7 +61,7 @@ async def server_manager(demo_workspace: Uri, docs_workspace): async def initialize(init_options): # Initialize the server. - esbonio.protocol._procedure_handler( + esbonio.protocol.handle_message( lsp.InitializeRequest( id=1, params=lsp.InitializeParams( @@ -76,7 +75,7 @@ async def initialize(init_options): ) ) - esbonio.protocol._procedure_handler( + esbonio.protocol.handle_message( lsp.InitializedNotification(params=lsp.InitializedParams()) ) From cf73d2f75957f76e5428cce33e732a2393f189f2 Mon Sep 17 00:00:00 2001 From: Alex Carney Date: Tue, 26 Nov 2024 20:19:42 +0000 Subject: [PATCH 06/10] code: Update bundled dependencies --- code/requirements-libs.in | 2 +- code/requirements-libs.txt | 175 +++++++++++++++++-------------------- 2 files changed, 80 insertions(+), 97 deletions(-) diff --git a/code/requirements-libs.in b/code/requirements-libs.in index 8fdcc6756..7a8c08367 100644 --- a/code/requirements-libs.in +++ b/code/requirements-libs.in @@ -12,6 +12,6 @@ aiosqlite docutils platformdirs -pygls>=2a0 +pygls>=2a2 tomli websockets diff --git a/code/requirements-libs.txt b/code/requirements-libs.txt index 41c4cd770..d59d74f65 100644 --- a/code/requirements-libs.txt +++ b/code/requirements-libs.txt @@ -28,21 +28,21 @@ exceptiongroup==1.2.2 \ --hash=sha256:3111b9d131c238bec2f8f516e123e14ba243563fb135d3fe885990585aa7795b \ --hash=sha256:47c2edf7c6738fafb49fd34290706d1a1a2f4d1c6df275526b62cbb4aa5393cc # via cattrs -lsprotocol==2024.0.0a2 \ - --hash=sha256:65c218418b850a62fa70aed1dfe4cfae5c09ef7d3f9710060a0c0a609c1c6cb2 \ - --hash=sha256:928785ded9b3c3c044c04b36893471585476dd6df274b6d296c7910e3d284c1a +lsprotocol==2024.0.0b1 \ + --hash=sha256:93785050ac155ae2be16b1ebfbd74c214feb3d3ef77b10399ce941e5ccef6ebd \ + --hash=sha256:d3667fb70894d361aa6c495c5c8a1b2e6a44be65ff84c21a9cbb67ebfb4830fd # via pygls platformdirs==4.3.6 \ --hash=sha256:357fb2acbc885b0419afd3ce3ed34564c13c9b95c89360cd9563f73aa5e2b907 \ --hash=sha256:73e575e1408ab8103900836b97580d5307456908a03e92031bab39e4554cc3fb # via -r requirements-libs.in -pygls==2.0.0a1 \ - --hash=sha256:934568567502bd535146ff383b81c6811e5b4acfdc6d513cbcafe672689bdcd3 \ - --hash=sha256:acbc284440daeb5aca7f12b785d250e7a55f1e3aada57d0e22cc9efe63e47c12 +pygls==2.0.0a2 \ + --hash=sha256:03e00634ed8d989918268aaa4b4a0c3ab857ea2d4ee94514a52efa5ddd6d5d9f \ + --hash=sha256:b202369321409343aa6440d73111d9fa0c22e580466ff1c7696b8358bb91f243 # via -r requirements-libs.in -tomli==2.0.2 \ - --hash=sha256:2ebe24485c53d303f690b0ec092806a085f07af5a5aa1464f3931eec36caaa38 \ - --hash=sha256:d46d457a85337051c36524bc5349dd91b1877838e2979ac5ced3e710ed8a60ed +tomli==2.1.0 \ + --hash=sha256:3f646cae2aec94e17d04973e4249548320197cfabdf130015d023de4b74d8ab8 \ + --hash=sha256:a5c57c3d1c56f5ccdf89f6523458f60ef716e210fc47c4cfb188c5ba473e0391 # via -r requirements-libs.in typing-extensions==4.12.2 \ --hash=sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d \ @@ -50,91 +50,74 @@ typing-extensions==4.12.2 \ # via # aiosqlite # cattrs -websockets==13.1 \ - --hash=sha256:004280a140f220c812e65f36944a9ca92d766b6cc4560be652a0a3883a79ed8a \ - --hash=sha256:035233b7531fb92a76beefcbf479504db8c72eb3bff41da55aecce3a0f729e54 \ - --hash=sha256:149e622dc48c10ccc3d2760e5f36753db9cacf3ad7bc7bbbfd7d9c819e286f23 \ - --hash=sha256:163e7277e1a0bd9fb3c8842a71661ad19c6aa7bb3d6678dc7f89b17fbcc4aeb7 \ - --hash=sha256:18503d2c5f3943e93819238bf20df71982d193f73dcecd26c94514f417f6b135 \ - --hash=sha256:1971e62d2caa443e57588e1d82d15f663b29ff9dfe7446d9964a4b6f12c1e700 \ - --hash=sha256:204e5107f43095012b00f1451374693267adbb832d29966a01ecc4ce1db26faf \ - --hash=sha256:2510c09d8e8df777177ee3d40cd35450dc169a81e747455cc4197e63f7e7bfe5 \ - --hash=sha256:25c35bf84bf7c7369d247f0b8cfa157f989862c49104c5cf85cb5436a641d93e \ - --hash=sha256:2f85cf4f2a1ba8f602298a853cec8526c2ca42a9a4b947ec236eaedb8f2dc80c \ - --hash=sha256:308e20f22c2c77f3f39caca508e765f8725020b84aa963474e18c59accbf4c02 \ - --hash=sha256:325b1ccdbf5e5725fdcb1b0e9ad4d2545056479d0eee392c291c1bf76206435a \ - --hash=sha256:327b74e915cf13c5931334c61e1a41040e365d380f812513a255aa804b183418 \ - --hash=sha256:346bee67a65f189e0e33f520f253d5147ab76ae42493804319b5716e46dddf0f \ - --hash=sha256:38377f8b0cdeee97c552d20cf1865695fcd56aba155ad1b4ca8779a5b6ef4ac3 \ - --hash=sha256:3c78383585f47ccb0fcf186dcb8a43f5438bd7d8f47d69e0b56f71bf431a0a68 \ - --hash=sha256:4059f790b6ae8768471cddb65d3c4fe4792b0ab48e154c9f0a04cefaabcd5978 \ - --hash=sha256:459bf774c754c35dbb487360b12c5727adab887f1622b8aed5755880a21c4a20 \ - --hash=sha256:463e1c6ec853202dd3657f156123d6b4dad0c546ea2e2e38be2b3f7c5b8e7295 \ - --hash=sha256:4676df3fe46956fbb0437d8800cd5f2b6d41143b6e7e842e60554398432cf29b \ - --hash=sha256:485307243237328c022bc908b90e4457d0daa8b5cf4b3723fd3c4a8012fce4c6 \ - --hash=sha256:48a2ef1381632a2f0cb4efeff34efa97901c9fbc118e01951ad7cfc10601a9bb \ - --hash=sha256:4b889dbd1342820cc210ba44307cf75ae5f2f96226c0038094455a96e64fb07a \ - --hash=sha256:586a356928692c1fed0eca68b4d1c2cbbd1ca2acf2ac7e7ebd3b9052582deefa \ - --hash=sha256:58cf7e75dbf7e566088b07e36ea2e3e2bd5676e22216e4cad108d4df4a7402a0 \ - --hash=sha256:5993260f483d05a9737073be197371940c01b257cc45ae3f1d5d7adb371b266a \ - --hash=sha256:5dd6da9bec02735931fccec99d97c29f47cc61f644264eb995ad6c0c27667238 \ - --hash=sha256:5f2e75431f8dc4a47f31565a6e1355fb4f2ecaa99d6b89737527ea917066e26c \ - --hash=sha256:5f9fee94ebafbc3117c30be1844ed01a3b177bb6e39088bc6b2fa1dc15572084 \ - --hash=sha256:61fc0dfcda609cda0fc9fe7977694c0c59cf9d749fbb17f4e9483929e3c48a19 \ - --hash=sha256:624459daabeb310d3815b276c1adef475b3e6804abaf2d9d2c061c319f7f187d \ - --hash=sha256:62d516c325e6540e8a57b94abefc3459d7dab8ce52ac75c96cad5549e187e3a7 \ - --hash=sha256:6548f29b0e401eea2b967b2fdc1c7c7b5ebb3eeb470ed23a54cd45ef078a0db9 \ - --hash=sha256:6d2aad13a200e5934f5a6767492fb07151e1de1d6079c003ab31e1823733ae79 \ - --hash=sha256:6d6855bbe70119872c05107e38fbc7f96b1d8cb047d95c2c50869a46c65a8e96 \ - --hash=sha256:70c5be9f416aa72aab7a2a76c90ae0a4fe2755c1816c153c1a2bcc3333ce4ce6 \ - --hash=sha256:730f42125ccb14602f455155084f978bd9e8e57e89b569b4d7f0f0c17a448ffe \ - --hash=sha256:7a43cfdcddd07f4ca2b1afb459824dd3c6d53a51410636a2c7fc97b9a8cf4842 \ - --hash=sha256:7bd6abf1e070a6b72bfeb71049d6ad286852e285f146682bf30d0296f5fbadfa \ - --hash=sha256:7c1e90228c2f5cdde263253fa5db63e6653f1c00e7ec64108065a0b9713fa1b3 \ - --hash=sha256:7c65ffa900e7cc958cd088b9a9157a8141c991f8c53d11087e6fb7277a03f81d \ - --hash=sha256:80c421e07973a89fbdd93e6f2003c17d20b69010458d3a8e37fb47874bd67d51 \ - --hash=sha256:82d0ba76371769d6a4e56f7e83bb8e81846d17a6190971e38b5de108bde9b0d7 \ - --hash=sha256:83f91d8a9bb404b8c2c41a707ac7f7f75b9442a0a876df295de27251a856ad09 \ - --hash=sha256:87c6e35319b46b99e168eb98472d6c7d8634ee37750d7693656dc766395df096 \ - --hash=sha256:8d23b88b9388ed85c6faf0e74d8dec4f4d3baf3ecf20a65a47b836d56260d4b9 \ - --hash=sha256:9156c45750b37337f7b0b00e6248991a047be4aa44554c9886fe6bdd605aab3b \ - --hash=sha256:91a0fa841646320ec0d3accdff5b757b06e2e5c86ba32af2e0815c96c7a603c5 \ - --hash=sha256:95858ca14a9f6fa8413d29e0a585b31b278388aa775b8a81fa24830123874678 \ - --hash=sha256:95df24ca1e1bd93bbca51d94dd049a984609687cb2fb08a7f2c56ac84e9816ea \ - --hash=sha256:9b37c184f8b976f0c0a231a5f3d6efe10807d41ccbe4488df8c74174805eea7d \ - --hash=sha256:9b6f347deb3dcfbfde1c20baa21c2ac0751afaa73e64e5b693bb2b848efeaa49 \ - --hash=sha256:9d75baf00138f80b48f1eac72ad1535aac0b6461265a0bcad391fc5aba875cfc \ - --hash=sha256:9ef8aa8bdbac47f4968a5d66462a2a0935d044bf35c0e5a8af152d58516dbeb5 \ - --hash=sha256:a11e38ad8922c7961447f35c7b17bffa15de4d17c70abd07bfbe12d6faa3e027 \ - --hash=sha256:a1b54689e38d1279a51d11e3467dd2f3a50f5f2e879012ce8f2d6943f00e83f0 \ - --hash=sha256:a3b3366087c1bc0a2795111edcadddb8b3b59509d5db5d7ea3fdd69f954a8878 \ - --hash=sha256:a569eb1b05d72f9bce2ebd28a1ce2054311b66677fcd46cf36204ad23acead8c \ - --hash=sha256:a7affedeb43a70351bb811dadf49493c9cfd1ed94c9c70095fd177e9cc1541fa \ - --hash=sha256:a9a396a6ad26130cdae92ae10c36af09d9bfe6cafe69670fd3b6da9b07b4044f \ - --hash=sha256:a9ab1e71d3d2e54a0aa646ab6d4eebfaa5f416fe78dfe4da2839525dc5d765c6 \ - --hash=sha256:a9cd1af7e18e5221d2878378fbc287a14cd527fdd5939ed56a18df8a31136bb2 \ - --hash=sha256:a9dcaf8b0cc72a392760bb8755922c03e17a5a54e08cca58e8b74f6902b433cf \ - --hash=sha256:b9d7439d7fab4dce00570bb906875734df13d9faa4b48e261c440a5fec6d9708 \ - --hash=sha256:bcc03c8b72267e97b49149e4863d57c2d77f13fae12066622dc78fe322490fe6 \ - --hash=sha256:c11d4d16e133f6df8916cc5b7e3e96ee4c44c936717d684a94f48f82edb7c92f \ - --hash=sha256:c1dca61c6db1166c48b95198c0b7d9c990b30c756fc2923cc66f68d17dc558fd \ - --hash=sha256:c518e84bb59c2baae725accd355c8dc517b4a3ed8db88b4bc93c78dae2974bf2 \ - --hash=sha256:c7934fd0e920e70468e676fe7f1b7261c1efa0d6c037c6722278ca0228ad9d0d \ - --hash=sha256:c7e72ce6bda6fb9409cc1e8164dd41d7c91466fb599eb047cfda72fe758a34a7 \ - --hash=sha256:c90d6dec6be2c7d03378a574de87af9b1efea77d0c52a8301dd831ece938452f \ - --hash=sha256:ceec59f59d092c5007e815def4ebb80c2de330e9588e101cf8bd94c143ec78a5 \ - --hash=sha256:cf1781ef73c073e6b0f90af841aaf98501f975d306bbf6221683dd594ccc52b6 \ - --hash=sha256:d04f13a1d75cb2b8382bdc16ae6fa58c97337253826dfe136195b7f89f661557 \ - --hash=sha256:d6d300f8ec35c24025ceb9b9019ae9040c1ab2f01cddc2bcc0b518af31c75c14 \ - --hash=sha256:d8dbb1bf0c0a4ae8b40bdc9be7f644e2f3fb4e8a9aca7145bfa510d4a374eeb7 \ - --hash=sha256:de58647e3f9c42f13f90ac7e5f58900c80a39019848c5547bc691693098ae1bd \ - --hash=sha256:deeb929efe52bed518f6eb2ddc00cc496366a14c726005726ad62c2dd9017a3c \ - --hash=sha256:df01aea34b6e9e33572c35cd16bae5a47785e7d5c8cb2b54b2acdb9678315a17 \ - --hash=sha256:e2620453c075abeb0daa949a292e19f56de518988e079c36478bacf9546ced23 \ - --hash=sha256:e4450fc83a3df53dec45922b576e91e94f5578d06436871dce3a6be38e40f5db \ - --hash=sha256:e54affdeb21026329fb0744ad187cf812f7d3c2aa702a5edb562b325191fcab6 \ - --hash=sha256:e9875a0143f07d74dc5e1ded1c4581f0d9f7ab86c78994e2ed9e95050073c94d \ - --hash=sha256:f1c3cf67185543730888b20682fb186fc8d0fa6f07ccc3ef4390831ab4b388d9 \ - --hash=sha256:f48c749857f8fb598fb890a75f540e3221d0976ed0bf879cf3c7eef34151acee \ - --hash=sha256:f779498eeec470295a2b1a5d97aa1bc9814ecd25e1eb637bd9d1c73a327387f6 +websockets==14.1 \ + --hash=sha256:00fe5da3f037041da1ee0cf8e308374e236883f9842c7c465aa65098b1c9af59 \ + --hash=sha256:01bb2d4f0a6d04538d3c5dfd27c0643269656c28045a53439cbf1c004f90897a \ + --hash=sha256:034feb9f4286476f273b9a245fb15f02c34d9586a5bc936aff108c3ba1b21beb \ + --hash=sha256:04a97aca96ca2acedf0d1f332c861c5a4486fdcba7bcef35873820f940c4231e \ + --hash=sha256:0d4290d559d68288da9f444089fd82490c8d2744309113fc26e2da6e48b65da6 \ + --hash=sha256:1288369a6a84e81b90da5dbed48610cd7e5d60af62df9851ed1d1d23a9069f10 \ + --hash=sha256:14839f54786987ccd9d03ed7f334baec0f02272e7ec4f6e9d427ff584aeea8b4 \ + --hash=sha256:1d045cbe1358d76b24d5e20e7b1878efe578d9897a25c24e6006eef788c0fdf0 \ + --hash=sha256:1f874ba705deea77bcf64a9da42c1f5fc2466d8f14daf410bc7d4ceae0a9fcb0 \ + --hash=sha256:205f672a6c2c671a86d33f6d47c9b35781a998728d2c7c2a3e1cf3333fcb62b7 \ + --hash=sha256:2177ee3901075167f01c5e335a6685e71b162a54a89a56001f1c3e9e3d2ad250 \ + --hash=sha256:219c8187b3ceeadbf2afcf0f25a4918d02da7b944d703b97d12fb01510869078 \ + --hash=sha256:25225cc79cfebc95ba1d24cd3ab86aaa35bcd315d12fa4358939bd55e9bd74a5 \ + --hash=sha256:3630b670d5057cd9e08b9c4dab6493670e8e762a24c2c94ef312783870736ab9 \ + --hash=sha256:368a05465f49c5949e27afd6fbe0a77ce53082185bbb2ac096a3a8afaf4de52e \ + --hash=sha256:36ebd71db3b89e1f7b1a5deaa341a654852c3518ea7a8ddfdf69cc66acc2db1b \ + --hash=sha256:39450e6215f7d9f6f7bc2a6da21d79374729f5d052333da4d5825af8a97e6735 \ + --hash=sha256:398b10c77d471c0aab20a845e7a60076b6390bfdaac7a6d2edb0d2c59d75e8d8 \ + --hash=sha256:3c3deac3748ec73ef24fc7be0b68220d14d47d6647d2f85b2771cb35ea847aa1 \ + --hash=sha256:3f14a96a0034a27f9d47fd9788913924c89612225878f8078bb9d55f859272b0 \ + --hash=sha256:3fc753451d471cff90b8f467a1fc0ae64031cf2d81b7b34e1811b7e2691bc4bc \ + --hash=sha256:414ffe86f4d6f434a8c3b7913655a1a5383b617f9bf38720e7c0799fac3ab1c6 \ + --hash=sha256:449d77d636f8d9c17952628cc7e3b8faf6e92a17ec581ec0c0256300717e1512 \ + --hash=sha256:4b6caec8576e760f2c7dd878ba817653144d5f369200b6ddf9771d64385b84d4 \ + --hash=sha256:4d4fc827a20abe6d544a119896f6b78ee13fe81cbfef416f3f2ddf09a03f0e2e \ + --hash=sha256:5a42d3ecbb2db5080fc578314439b1d79eef71d323dc661aa616fb492436af5d \ + --hash=sha256:5b918d288958dc3fa1c5a0b9aa3256cb2b2b84c54407f4813c45d52267600cd3 \ + --hash=sha256:5ef440054124728cc49b01c33469de06755e5a7a4e83ef61934ad95fc327fbb0 \ + --hash=sha256:660c308dabd2b380807ab64b62985eaccf923a78ebc572bd485375b9ca2b7dc7 \ + --hash=sha256:6a6c9bcf7cdc0fd41cc7b7944447982e8acfd9f0d560ea6d6845428ed0562058 \ + --hash=sha256:6d24fc337fc055c9e83414c94e1ee0dee902a486d19d2a7f0929e49d7d604b09 \ + --hash=sha256:7048eb4415d46368ef29d32133134c513f507fff7d953c18c91104738a68c3b3 \ + --hash=sha256:77569d19a13015e840b81550922056acabc25e3f52782625bc6843cfa034e1da \ + --hash=sha256:8149a0f5a72ca36720981418eeffeb5c2729ea55fa179091c81a0910a114a5d2 \ + --hash=sha256:836bef7ae338a072e9d1863502026f01b14027250a4545672673057997d5c05a \ + --hash=sha256:8621a07991add373c3c5c2cf89e1d277e49dc82ed72c75e3afc74bd0acc446f0 \ + --hash=sha256:87e31011b5c14a33b29f17eb48932e63e1dcd3fa31d72209848652310d3d1f0d \ + --hash=sha256:88cf9163ef674b5be5736a584c999e98daf3aabac6e536e43286eb74c126b9c7 \ + --hash=sha256:8fda642151d5affdee8a430bd85496f2e2517be3a2b9d2484d633d5712b15c56 \ + --hash=sha256:90b5d9dfbb6d07a84ed3e696012610b6da074d97453bd01e0e30744b472c8179 \ + --hash=sha256:90f4c7a069c733d95c308380aae314f2cb45bd8a904fb03eb36d1a4983a4993f \ + --hash=sha256:9481a6de29105d73cf4515f2bef8eb71e17ac184c19d0b9918a3701c6c9c4f23 \ + --hash=sha256:9607b9a442392e690a57909c362811184ea429585a71061cd5d3c2b98065c199 \ + --hash=sha256:9777564c0a72a1d457f0848977a1cbe15cfa75fa2f67ce267441e465717dcf1a \ + --hash=sha256:a032855dc7db987dff813583d04f4950d14326665d7e714d584560b140ae6b8b \ + --hash=sha256:a0adf84bc2e7c86e8a202537b4fd50e6f7f0e4a6b6bf64d7ccb96c4cd3330b29 \ + --hash=sha256:a35f704be14768cea9790d921c2c1cc4fc52700410b1c10948511039be824aac \ + --hash=sha256:a3dfff83ca578cada2d19e665e9c8368e1598d4e787422a460ec70e531dbdd58 \ + --hash=sha256:a4c805c6034206143fbabd2d259ec5e757f8b29d0a2f0bf3d2fe5d1f60147a4a \ + --hash=sha256:a655bde548ca98f55b43711b0ceefd2a88a71af6350b0c168aa77562104f3f45 \ + --hash=sha256:ad2ab2547761d79926effe63de21479dfaf29834c50f98c4bf5b5480b5838434 \ + --hash=sha256:b1f3628a0510bd58968c0f60447e7a692933589b791a6b572fcef374053ca280 \ + --hash=sha256:b7e7ea2f782408c32d86b87a0d2c1fd8871b0399dd762364c731d86c86069a78 \ + --hash=sha256:bc6ccf7d54c02ae47a48ddf9414c54d48af9c01076a2e1023e3b486b6e72c707 \ + --hash=sha256:bea45f19b7ca000380fbd4e02552be86343080120d074b87f25593ce1700ad58 \ + --hash=sha256:cc1fc87428c1d18b643479caa7b15db7d544652e5bf610513d4a3478dbe823d0 \ + --hash=sha256:cd7c11968bc3860d5c78577f0dbc535257ccec41750675d58d8dc66aa47fe52c \ + --hash=sha256:ceada5be22fa5a5a4cdeec74e761c2ee7db287208f54c718f2df4b7e200b8d4a \ + --hash=sha256:cf5201a04550136ef870aa60ad3d29d2a59e452a7f96b94193bee6d73b8ad9a9 \ + --hash=sha256:d9fd19ecc3a4d5ae82ddbfb30962cf6d874ff943e56e0c81f5169be2fda62979 \ + --hash=sha256:ddaa4a390af911da6f680be8be4ff5aaf31c4c834c1a9147bc21cbcbca2d4370 \ + --hash=sha256:df174ece723b228d3e8734a6f2a6febbd413ddec39b3dc592f5a4aa0aff28098 \ + --hash=sha256:e0744623852f1497d825a49a99bfbec9bea4f3f946df6eb9d8a2f0c37a2fec2e \ + --hash=sha256:e5dc25a9dbd1a7f61eca4b7cb04e74ae4b963d658f9e4f9aad9cd00b688692c8 \ + --hash=sha256:e7591d6f440af7f73c4bd9404f3772bfee064e639d2b6cc8c94076e71b2471c1 \ + --hash=sha256:eb6d38971c800ff02e4a6afd791bbe3b923a9a57ca9aeab7314c21c84bf9ff05 \ + --hash=sha256:ed907449fe5e021933e46a3e65d651f641975a768d0649fee59f10c2985529ed \ + --hash=sha256:f6cf0ad281c979306a6a34242b371e90e891bce504509fb6bb5246bbbf31e7b6 \ + --hash=sha256:f95ba34d71e2fa0c5d225bde3b3bdb152e957150100e75c86bc7f3964c450d89 # via -r requirements-libs.in From b52c071b60c7f79c8eebb3fb60a6b0ba5b01f14d Mon Sep 17 00:00:00 2001 From: Alex Carney Date: Sun, 1 Dec 2024 15:35:45 +0000 Subject: [PATCH 07/10] vscode: Add launch configuration for debugging test cases --- .vscode/launch.json | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/.vscode/launch.json b/.vscode/launch.json index fdc52dd46..4d8510d85 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -48,5 +48,13 @@ "justMyCode": false, "subProcess": true, }, + { + "name": "Python: Debug Test", + "type": "debugpy", + "request": "launch", + "purpose": ["debug-test"], + "justMyCode": false, + "subProcess": true + } ], } From ed184a3a84f2653c1648679161811f4c21c0b812 Mon Sep 17 00:00:00 2001 From: Alex Carney Date: Sun, 1 Dec 2024 15:37:22 +0000 Subject: [PATCH 08/10] lsp: Ensure database connections are closed during shutdown --- .../esbonio/server/features/project_manager/manager.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/esbonio/esbonio/server/features/project_manager/manager.py b/lib/esbonio/esbonio/server/features/project_manager/manager.py index 9747fffa4..dfc04af0c 100644 --- a/lib/esbonio/esbonio/server/features/project_manager/manager.py +++ b/lib/esbonio/esbonio/server/features/project_manager/manager.py @@ -17,6 +17,10 @@ def __init__(self, *args, **kwargs): self.projects: dict[str, Project] = {} """Holds active project instances""" + async def shutdown(self, params: None): + for project in self.projects.values(): + await project.close() + def register_project(self, scope: str, dbpath: str | pathlib.Path): """Register a project.""" self.logger.debug("Registered project for scope '%s': '%s'", scope, dbpath) From 9dfafc1ab57f4b02f352e17d61fa9e24dffffc35 Mon Sep 17 00:00:00 2001 From: Alex Carney Date: Sun, 1 Dec 2024 16:36:30 +0000 Subject: [PATCH 09/10] docs: Fix logging configuration example --- docs/lsp/reference/configuration.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/lsp/reference/configuration.rst b/docs/lsp/reference/configuration.rst index ceeec65fd..113632e5a 100644 --- a/docs/lsp/reference/configuration.rst +++ b/docs/lsp/reference/configuration.rst @@ -196,7 +196,7 @@ This sets the default log level to ``debug`` and dials back or redirects the out "level": "info" }, "esbonio.PreviewServer": { - "filename": "http.log", + "filepath": "http.log", "stderr": false }, "esbonio.WebviewServer": { From 2aa873bb9077eba2ade788a5a3e29df366a431c4 Mon Sep 17 00:00:00 2001 From: Alex Carney Date: Sun, 1 Dec 2024 17:22:29 +0000 Subject: [PATCH 10/10] lsp: Stop suppressing TimeoutError --- lib/esbonio/tests/e2e/conftest.py | 6 +----- lib/esbonio/tests/e2e/test_e2e_diagnostics.py | 6 +----- lib/esbonio/tests/server/conftest.py | 6 +----- 3 files changed, 3 insertions(+), 15 deletions(-) diff --git a/lib/esbonio/tests/e2e/conftest.py b/lib/esbonio/tests/e2e/conftest.py index 4000ef38c..dfb347dcb 100644 --- a/lib/esbonio/tests/e2e/conftest.py +++ b/lib/esbonio/tests/e2e/conftest.py @@ -86,8 +86,4 @@ async def client(lsp_client: LanguageClient, uri_for, tmp_path_factory): yield # Teardown - try: - await asyncio.wait_for(lsp_client.shutdown_session(), timeout=2.0) - except (asyncio.TimeoutError, TimeoutError): - # HACK: Working around openlawlibrary/pygls#433 - print("Gave up waiting for process to exit") + await asyncio.wait_for(lsp_client.shutdown_session(), timeout=2.0) diff --git a/lib/esbonio/tests/e2e/test_e2e_diagnostics.py b/lib/esbonio/tests/e2e/test_e2e_diagnostics.py index 01a62b652..6cb1da352 100644 --- a/lib/esbonio/tests/e2e/test_e2e_diagnostics.py +++ b/lib/esbonio/tests/e2e/test_e2e_diagnostics.py @@ -172,11 +172,7 @@ async def pub_client(lsp_client: LanguageClient, uri_for, tmp_path_factory): yield # Teardown - try: - await asyncio.wait_for(lsp_client.shutdown_session(), timeout=2.0) - except (asyncio.TimeoutError, TimeoutError): - # HACK: Working around openlawlibrary/pygls#433 - print("Gave up waiting for process to exit") + await asyncio.wait_for(lsp_client.shutdown_session(), timeout=2.0) @pytest.mark.asyncio(loop_scope="module") diff --git a/lib/esbonio/tests/server/conftest.py b/lib/esbonio/tests/server/conftest.py index d9f1062c9..1c4f3998f 100644 --- a/lib/esbonio/tests/server/conftest.py +++ b/lib/esbonio/tests/server/conftest.py @@ -41,8 +41,4 @@ async def client(request, uri_for, lsp_client: LanguageClient): yield # Teardown - try: - await asyncio.wait_for(lsp_client.shutdown_session(), timeout=2.0) - except (asyncio.TimeoutError, TimeoutError): - # HACK: Working around openlawlibrary/pygls#433 - print("Gave up waiting for process to exit") + await asyncio.wait_for(lsp_client.shutdown_session(), timeout=2.0)