From 64c90977b7b55972c403528057d7e3797f6f8d45 Mon Sep 17 00:00:00 2001 From: Nitorac Date: Mon, 23 Sep 2024 19:46:08 +0200 Subject: [PATCH] lsp: Use Thread instead of Process for Preview Server (#837) * Preview process: Replaced Process with Thread if running on Python 3.12+ * More meaningfull variable name + add type hints * Fixed types + store the HTTPServer to call shutdown() later * Removed unused imports * Removed unused line * Removed useless type wrapper * Restored terminate() call if Process does not have kill() * Removed Process part, fully replaced with Thread * Added changelog to track the issue * Updated preview_server docstring --- lib/esbonio/changes/790.fix.rst | 1 + lib/esbonio/esbonio/lsp/sphinx/__init__.py | 34 +++++++++------------- 2 files changed, 14 insertions(+), 21 deletions(-) create mode 100644 lib/esbonio/changes/790.fix.rst diff --git a/lib/esbonio/changes/790.fix.rst b/lib/esbonio/changes/790.fix.rst new file mode 100644 index 000000000..5e28a5749 --- /dev/null +++ b/lib/esbonio/changes/790.fix.rst @@ -0,0 +1 @@ +Changed the preview server to use a Thread instead of a Process to prevent deadlocks on certain systems by @Nitorac diff --git a/lib/esbonio/esbonio/lsp/sphinx/__init__.py b/lib/esbonio/esbonio/lsp/sphinx/__init__.py index 565f2114a..50e16fc7b 100644 --- a/lib/esbonio/esbonio/lsp/sphinx/__init__.py +++ b/lib/esbonio/esbonio/lsp/sphinx/__init__.py @@ -6,8 +6,8 @@ import typing import warnings from functools import partial -from multiprocessing import Process -from multiprocessing import Queue +from http.server import HTTPServer +from threading import Thread from typing import IO from typing import Any from typing import Dict @@ -92,9 +92,12 @@ def __init__(self, *args, **kwargs): self.sphinx_log: Optional[SphinxLogHandler] = None """Logging handler for sphinx messages.""" - self.preview_process: Optional[Process] = None + self.preview_runnable: Optional[Thread] = None """The process hosting the preview server.""" + self.preview_server: Optional[HTTPServer] = None + """The handle of HTTPServer running the preview server.""" + self.preview_port: Optional[int] = None """The port the preview server is running on.""" @@ -172,11 +175,8 @@ def _initialize_sphinx(self): ) def on_shutdown(self, *args): - if self.preview_process: - if not hasattr(self.preview_process, "kill"): - self.preview_process.terminate() - else: - self.preview_process.kill() + if self.preview_runnable and self.preview_server is not None: + self.preview_server.shutdown() def save(self, params: DidSaveTextDocumentParams): super().save(params) @@ -429,23 +429,15 @@ def preview(self, options: Dict[str, Any]) -> Dict[str, Any]: return {} - if not self.preview_process and IS_LINUX: + if not self.preview_runnable: self.logger.debug("Starting preview server.") server = make_preview_server(self.app.outdir) # type: ignore[arg-type] self.preview_port = server.server_port + # Remember the HTTPServer object to call shutdown when we want it to end + self.preview_server = server - self.preview_process = Process(target=server.serve_forever, daemon=True) - self.preview_process.start() - - if not self.preview_process and not IS_LINUX: - self.logger.debug("Starting preview server") - - q: Queue = Queue() - self.preview_process = Process( - target=start_preview_server, args=(q, self.app.outdir), daemon=True - ) - self.preview_process.start() - self.preview_port = q.get() + self.preview_runnable = Thread(target=server.serve_forever, daemon=True) + self.preview_runnable.start() if options.get("show", True): self.show_document(