drgnchan opened a new issue, #223:
URL: https://github.com/apache/doris-mcp-server/issues/223
## Bug description
Global connection-pool recovery can deadlock indefinitely when the pool is
unavailable, closed, or acquisition times out.
This was observed after a transient Doris connectivity failure during HTTP
server startup. The server continued running without a global pool, but every
later query—including `SELECT 1`—waited until the tool-level timeout. A fresh
`aiomysql` connection from the same container could execute `SELECT 1`
successfully, and restarting the container immediately restored service by
creating the pool during startup.
The locking problem is still present on current `master`
(`5daf1deb26bc0db02c19bf5ca1d070acea4cfab9`).
## Root cause
`DorisConnectionManager` aliases both lock names to the same non-reentrant
`asyncio.Lock`:
```python
self._recovery_lock = asyncio.Lock()
self.pool_recovery_lock = self._recovery_lock
```
`_recover_pool_with_lock()` acquires `_recovery_lock` and then calls
`_recover_pool()`:
```python
async def _recover_pool_with_lock(self) -> None:
async with self._recovery_lock:
if not self.pool_recovering:
await self._recover_pool()
```
However, `_recover_pool()` tries to acquire `pool_recovery_lock`, which is
the same lock:
```python
async def _recover_pool(self) -> None:
async with self.pool_recovery_lock:
...
```
Because `asyncio.Lock` is not reentrant, `_recover_pool()` waits forever for
a lock held by its caller.
The affected paths include:
- no global pool: `get_connection()` -> `_recover_pool_with_lock()`
- closed global pool: `get_connection()` -> `_recover_pool_with_lock()`
- pool acquisition timeout: `get_connection()` -> `_recover_pool_with_lock()`
## Observed behavior
The startup connectivity check encountered a transient failure:
```text
Database connectivity test failed: (2013, 'Lost connection to MySQL server
during query')
HTTP mode running without global database pool, will use token-bound
configurations
```
Later queries logged:
```text
Connection pool is not available, attempting recovery...
Query execution failed: Query timeout after 10 seconds
```
Notably, the expected recovery log below never appeared, because execution
deadlocked before entering the body of `_recover_pool()`:
```text
Attempting pool recovery (attempt 1/3)
```
## Minimal deterministic reproduction
This reproduces without a live Doris instance because the deadlock occurs
before any database operation:
```python
import asyncio
from doris_mcp_server.utils.db import DorisConnectionManager
async def main():
manager = object.__new__(DorisConnectionManager)
manager._recovery_lock = asyncio.Lock()
manager.pool_recovery_lock = manager._recovery_lock
manager.pool_recovering = False
# Raises TimeoutError; expected to enter recovery and complete/fail
normally.
await asyncio.wait_for(manager._recover_pool_with_lock(), timeout=0.1)
asyncio.run(main())
```
## Expected behavior
Recovery should have a single lock owner and should either recreate the pool
or return a bounded error. It must not deadlock.
## Suggested fix
Remove the nested acquisition. For example, make `_recover_pool()` the only
method responsible for acquiring the recovery lock, and have callers invoke it
directly (or make `_recover_pool_with_lock()` delegate without acquiring the
same lock).
Please also add regression tests covering:
1. recovery when `self.pool is None`;
2. recovery when the pool is closed;
3. recovery after acquisition timeout;
4. concurrent recovery requests, verifying bounded completion and only one
active recovery.
## Environment
- Transport: Streamable HTTP
- Deployment: Docker Compose, one worker
- Observed release: `0.6.1` / commit
`81305ffbf97482e29abf70dc0ba9be928ed39df5`
- Confirmed by source inspection on current `master`:
`5daf1deb26bc0db02c19bf5ca1d070acea4cfab9`
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: [email protected]
For queries about this service, please contact Infrastructure at:
[email protected]
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]