From caa0e3dc4958ef33512de0d20f0d3e7ecb4aa3bf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=82=B9=E6=B0=B8=E8=B5=AB?= <1259085392@qq.com> Date: Thu, 26 Mar 2026 03:51:37 +0900 Subject: [PATCH] fix: simplify lsp reader task lifecycle --- astrbot/_internal/protocols/lsp/client.py | 8 +++----- tests/unit/test_internal/test_lsp_client.py | 15 +++++++++++++++ 2 files changed, 18 insertions(+), 5 deletions(-) diff --git a/astrbot/_internal/protocols/lsp/client.py b/astrbot/_internal/protocols/lsp/client.py index a7f7451be..21b12e981 100644 --- a/astrbot/_internal/protocols/lsp/client.py +++ b/astrbot/_internal/protocols/lsp/client.py @@ -59,10 +59,6 @@ class AstrbotLspClient(BaseAstrbotLspClient): except Exception as exc: log.debug("Ignoring failed LSP reader task during teardown", exc_info=exc) - def _handle_reader_task_done(self, task: asyncio.Task[None]) -> None: - if self._reader_task is task: - self._reader_task = None - async def connect(self) -> None: """ Connect to configured LSP servers. @@ -101,7 +97,6 @@ class AstrbotLspClient(BaseAstrbotLspClient): # Start reading responses in the background. self._reader_task = asyncio.create_task(self._read_responses()) - self._reader_task.add_done_callback(self._handle_reader_task_done) # Send initialize request await self.send_request( @@ -235,6 +230,9 @@ class AstrbotLspClient(BaseAstrbotLspClient): if self._connected: self._connected = False log.warning("LSP reader task exited unexpectedly") + finally: + if self._reader_task is asyncio.current_task(): + self._reader_task = None async def _handle_notification(self, notification: dict[str, Any]) -> None: """Handle incoming LSP notifications.""" diff --git a/tests/unit/test_internal/test_lsp_client.py b/tests/unit/test_internal/test_lsp_client.py index acca10290..9387c1022 100644 --- a/tests/unit/test_internal/test_lsp_client.py +++ b/tests/unit/test_internal/test_lsp_client.py @@ -50,6 +50,21 @@ async def test_lsp_read_responses_unexpected_exit_disconnects_and_warns(): mock_log.warning.assert_called_once() +@pytest.mark.asyncio +async def test_lsp_read_responses_clears_reader_task_reference_on_exit(): + """Test _read_responses clears the stored task reference when it exits.""" + client = AstrbotLspClient() + client._connected = True + client._reader = FakeReader(AsyncMock(return_value=b"")) + + task = asyncio.create_task(client._read_responses()) + client._reader_task = task + + await task + + assert client._reader_task is None + + @pytest.mark.asyncio async def test_lsp_stop_reader_task_swallows_failed_reader_exceptions(): """Test reader teardown does not re-raise prior reader failures."""