From 97130e7c13bb137575ca373b2ca6243a16f2a4f5 Mon Sep 17 00:00:00 2001 From: TheSmallKiwi Date: Fri, 13 Mar 2026 20:07:22 -0400 Subject: [PATCH] Fix HLSL language server leaving orphaned processes on shutdown The base _shutdown() calls process.terminate() directly on the subprocess, which on Windows with shell=True only kills the cmd.exe wrapper, leaving the shader-language-server binary running as an orphan. Override stop() in HlslLanguageServer to use psutil to terminate the full process tree before the standard shutdown. Co-Authored-By: Claude Opus 4.6 (1M context) --- .../language_servers/hlsl_language_server.py | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/src/solidlsp/language_servers/hlsl_language_server.py b/src/solidlsp/language_servers/hlsl_language_server.py index f578c3f3..5ac09c26 100644 --- a/src/solidlsp/language_servers/hlsl_language_server.py +++ b/src/solidlsp/language_servers/hlsl_language_server.py @@ -9,6 +9,7 @@ import pathlib import shutil from typing import Any, cast +import psutil from overrides import override from solidlsp.ls import LanguageServerDependencyProvider, LanguageServerDependencyProviderSinglePath, SolidLanguageServer @@ -213,6 +214,38 @@ class HlslLanguageServer(SolidLanguageServer): self.server.notify.initialized({}) + @override + def stop(self, shutdown_timeout: float = 2.0) -> None: + """Kill the shader-language-server process tree before the standard shutdown. + + The base _shutdown() calls process.terminate() directly on the subprocess, + which on Windows with shell=True only kills the cmd.exe wrapper, leaving + the actual shader-language-server binary running as an orphan. We use psutil + to terminate the full process tree first. + """ + process = self.server.process if self.server else None + if process and process.pid and process.returncode is None: + try: + parent = psutil.Process(process.pid) + children = parent.children(recursive=True) + for child in children: + try: + child.terminate() + except (psutil.NoSuchProcess, psutil.AccessDenied): + pass + psutil.wait_procs(children, timeout=2) + for child in children: + try: + if child.is_running(): + child.kill() + except (psutil.NoSuchProcess, psutil.AccessDenied): + pass + except (psutil.NoSuchProcess, psutil.AccessDenied): + pass + except Exception as e: + log.debug(f"Error cleaning up shader-language-server process tree: {e}") + super().stop(shutdown_timeout) + @override def is_ignored_dirname(self, dirname: str) -> bool: """Ignore Unity-specific directories that contain no user-authored shaders."""