mirror of
https://github.com/tiennm99/serena.git
synced 2026-10-13 06:15:43 +00:00
CI, added some memories
This commit is contained in:
1 parent
21de3c377e
commit
afea349d67
4 files changed
+85
-12
No files matched your search
@@ -55,9 +55,4 @@ jobs:
|
||||
run: uv pip install -e ".[dev]"
|
||||
- name: Test with pytest
|
||||
shell: bash
|
||||
run: |
|
||||
if [ "${{ runner.os }}" = "Linux" ]; then
|
||||
uv run pytest -vv -m "not java and not rust"
|
||||
else
|
||||
uv run pytest -vv
|
||||
fi
|
||||
run: uv run poe test
|
||||
@@ -0,0 +1,63 @@
|
||||
# Deadlock Analysis: MCP Symbolic Tools Timeout Issue
|
||||
|
||||
## Problem Summary
|
||||
Intermittent deadlocks occur when using symbolic tools (particularly `find_symbol`) through MCP clients. Successful calls are followed by calls that hang for 240 seconds before timing out. Once a timeout occurs, the language server calls will no longer respond.
|
||||
|
||||
## ROOT CAUSE DISCOVERED: Asyncio Event Loop Interference
|
||||
|
||||
### Critical Evidence from MCP Server Logs
|
||||
The smoking gun was found in the MCP server logs showing **unawaited coroutines**:
|
||||
|
||||
```
|
||||
INFO mcp.server.lowlevel.server:_handle_message:524 - Warning: RuntimeWarning: coroutine 'LanguageServer.request_full_symbol_tree' was never awaited
|
||||
```
|
||||
|
||||
This warning is being logged by the **MCP server's `_handle_message` method**, not SerenaAgent code, indicating coroutines are being created in the MCP server's asyncio context but never properly awaited.
|
||||
|
||||
### The Real Issue: Dual Asyncio Context Contamination
|
||||
|
||||
**Architecture Problem:**
|
||||
1. **MCP Server**: Runs its own asyncio event loop to handle incoming requests
|
||||
2. **SerenaAgent**: Creates its own asyncio event loop in separate thread for language serve, and even a second loop for the dashboard if so configured
|
||||
3. **Conflict**: When MCP server calls SerenaAgent tools, we have **two or three asyncio contexts interacting**
|
||||
|
||||
**Deadlock Mechanism:**
|
||||
```
|
||||
MCP Server (asyncio loop A)
|
||||
→ handles tool request
|
||||
→ calls SerenaAgent.find_symbol()
|
||||
→ SerenaAgent uses asyncio.run_coroutine_threadsafe()
|
||||
→ Creates coroutine in language server loop (loop B)
|
||||
→ BUT: Coroutine gets leaked into MCP's context and never awaited
|
||||
→ Dangling coroutines accumulate in MCP server's loop
|
||||
→ Eventually causes resource exhaustion/event loop blocking
|
||||
→ Language server appears to "hang" but it's actually MCP loop contamination
|
||||
```
|
||||
|
||||
### Discarded Hypotheses
|
||||
- **Not a timeout issue**: Language server actually works fine in isolation
|
||||
- **Not abandoned threads**: The threading mechanism works correctly
|
||||
- **Not LSP deadlock**: The TypeScript language server itself isn't hanging
|
||||
|
||||
The actual issue is **asyncio context bleeding** between MCP server and SerenaAgent.
|
||||
|
||||
### Why We Can't Reproduce Outside MCP
|
||||
- **Direct script calls**: No MCP server, no dual asyncio context
|
||||
- **Single event loop**: Only SerenaAgent's language server loop exists
|
||||
- **No async interference**: Clean, isolated execution
|
||||
- **MCP-specific**: Requires the exact async context interaction pattern
|
||||
|
||||
## Technical Details
|
||||
|
||||
### Key Files and Locations
|
||||
- `src/multilspy/language_server.py:1870` - `run_coroutine_threadsafe` creates coroutines that leak to MCP context
|
||||
- MCP server `_handle_message` - Where unawaited coroutine warnings appear
|
||||
|
||||
## Solution
|
||||
**Process Isolation** is the fundamental fix:
|
||||
1. **Separate processes**: MCP server, SerenaAgent and Dashboard in different processes
|
||||
2. **IPC communication**: Replace direct method calls with inter-process communication
|
||||
3. **Clean async boundaries**: Each process manages its own asyncio context
|
||||
4. **No coroutine leakage**: Complete isolation prevents context contamination
|
||||
|
||||
This explains why the deadlock is **MCP-specific** and doesn't occur in direct tool execution - it's fundamentally about asyncio context contamination between the MCP server and SerenaAgent.
|
||||
@@ -4,10 +4,24 @@
|
||||
|
||||
The following tasks should generally be executed using `uv run poe <task_name>`.
|
||||
|
||||
- `lint`: This is the **only** allowed command for linting. Run as `uv run poe lint`.
|
||||
- `format`: This is the **only** allowed command for formatting. Run as `uv run poe format`.
|
||||
- `type-check`: This is the **only** allowed command for type checking. Run as `uv run poe type-check`.
|
||||
- `test`: This is the preferred command for running tests (`uv run poe test [args]`). However, running tests directly with `uv run pytest [args]` is also permitted.
|
||||
- `test`: This is the preferred command for running tests (`uv run poe test [args]`). You can select subsets of tests with markers,
|
||||
the current markers are
|
||||
```toml
|
||||
markers = [
|
||||
"python: language server running for Python",
|
||||
"go: language server running for Go",
|
||||
"java: language server running for Java",
|
||||
"rust: language server running for Rust",
|
||||
"typescript: language server running for TypeScript",
|
||||
"php: language server running for PHP",
|
||||
"snapshot: snapshot tests for symbolic editing operations",
|
||||
"isolated_process: test runs with process isolated agent",
|
||||
]
|
||||
```
|
||||
By default, `uv run poe test` uses the markes set in the env var `PYTEST_MARKERS`, or, if it unset, uses `-m "not java and not rust and not isolated process"`.
|
||||
You can override this behavior by simply passing the `-m` option to `uv run poe test`, e.g. `uv run poe test -m "python or go"`.
|
||||
|
||||
For finishing a task, make sure format, type-check and test pass! Run them at the end of the task
|
||||
and if needed fix any issues that come up and run them again until they pass.
|
||||
+5
-4
@@ -106,9 +106,10 @@ exclude = "^build/|^docs/"
|
||||
PYDEVD_DISABLE_FILE_VALIDATION = "1"
|
||||
|
||||
[tool.poe.tasks]
|
||||
# Uses PYTEST_MARKERS env var for default markers (defaults to "not java and not rust")
|
||||
# Set PYTEST_MARKERS="" to run all tests
|
||||
test = "pytest test --color=yes -m \"${PYTEST_MARKERS:-not java and not rust}\""
|
||||
# Uses PYTEST_MARKERS env var for default markers
|
||||
# For custom markers, one can either adjust the env var or just use -m option in the command line,
|
||||
# as the second -m option will override the first one.
|
||||
test = "pytest test -vv -m \"${PYTEST_MARKERS:-not java and not rust and not isolated process}\""
|
||||
_black_check = "black --check --exclude src/multilspy/ src scripts test"
|
||||
_ruff_check = "ruff check --exclude .venv/ --exclude src/multilspy/ src scripts test"
|
||||
_black_format = "black --exclude .venv/|src/multilspy/ src scripts test"
|
||||
@@ -259,6 +260,6 @@ markers = [
|
||||
"rust: language server running for Rust",
|
||||
"typescript: language server running for TypeScript",
|
||||
"php: language server running for PHP",
|
||||
"snapshot: snapshot tests",
|
||||
"snapshot: snapshot tests for symbolic editing operations",
|
||||
"isolated_process: test runs with process isolated agent",
|
||||
]
|
||||
Reference in new issue
Block a user