From 365cb8e9d23e3c3c3033a97e0f03f9f608f25147 Mon Sep 17 00:00:00 2001 From: Michael Panchenko Date: Sun, 29 Jun 2025 00:55:20 +0200 Subject: [PATCH] LS: more extensive and async shutdown logic The C# process did not terminate properly --- src/solidlsp/ls.py | 40 ++++++++++++++++++++++++++++++++++++---- 1 file changed, 36 insertions(+), 4 deletions(-) diff --git a/src/solidlsp/ls.py b/src/solidlsp/ls.py index 6b0acc4a..4d07f2c5 100644 --- a/src/solidlsp/ls.py +++ b/src/solidlsp/ls.py @@ -6,6 +6,7 @@ import os import pathlib import pickle import re +import subprocess import threading from abc import ABC, abstractmethod from collections import defaultdict @@ -343,15 +344,46 @@ class SolidLanguageServer(ABC): # Stage 1: Graceful Termination Request # Send LSP shutdown and close stdin to signal no more input. try: - self.server.shutdown() + self.logger.log("Sending LSP shutdown request...", logging.DEBUG) + # Use a thread to timeout the LSP shutdown call since it can hang + shutdown_thread = threading.Thread(target=self.server.shutdown) + shutdown_thread.daemon = True + shutdown_thread.start() + shutdown_thread.join(timeout=2.0) # 2 second timeout for LSP shutdown + + if shutdown_thread.is_alive(): + self.logger.log("LSP shutdown request timed out, proceeding to terminate...", logging.DEBUG) + else: + self.logger.log("LSP shutdown request completed.", logging.DEBUG) + if process.stdin and not process.stdin.is_closing(): process.stdin.close() - except Exception: - pass # Ignore errors here, we are proceeding to terminate anyway. + self.logger.log("Stage 1 shutdown complete.", logging.DEBUG) + except Exception as e: + self.logger.log(f"Exception during graceful shutdown: {e}", logging.DEBUG) + # Ignore errors here, we are proceeding to terminate anyway. - # Stage 2: Terminate and Concurrently Drain stdout/stderr + # Stage 2: Terminate and Wait for Process to Exit + self.logger.log(f"Terminating process {process.pid}, current status: {process.poll()}", logging.DEBUG) process.terminate() + # Stage 3: Wait for process termination with timeout + try: + self.logger.log(f"Waiting for process {process.pid} to terminate...", logging.DEBUG) + exit_code = process.wait(timeout=timeout) + self.logger.log(f"Language server process terminated successfully with exit code {exit_code}.", logging.INFO) + except subprocess.TimeoutExpired: + # If termination failed, forcefully kill the process + self.logger.log(f"Process {process.pid} termination timed out, killing process forcefully...", logging.WARNING) + process.kill() + try: + exit_code = process.wait(timeout=2.0) + self.logger.log(f"Language server process killed successfully with exit code {exit_code}.", logging.INFO) + except subprocess.TimeoutExpired: + self.logger.log(f"Process {process.pid} could not be killed within timeout.", logging.ERROR) + except Exception as e: + self.logger.log(f"Error during process shutdown: {e}", logging.ERROR) + @contextmanager def start_server(self) -> Iterator["SolidLanguageServer"]: self.start()