Skip to content

Commit e9518d6

Browse files
committed
Ensure startup failures clean up server resources
1 parent b103a8b commit e9518d6

File tree

2 files changed

+55
-17
lines changed

2 files changed

+55
-17
lines changed

redisvl/mcp/server.py

Lines changed: 20 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -53,23 +53,26 @@ async def startup(self) -> None:
5353
schema=self.config.to_index_schema(),
5454
redis_url=self.config.redis_url,
5555
)
56-
57-
timeout = self.config.runtime.startup_timeout_seconds
58-
index_exists = await asyncio.wait_for(self._index.exists(), timeout=timeout)
59-
if not index_exists:
60-
if self.config.runtime.index_mode == "validate_only":
61-
raise ValueError(
62-
f"Index '{self.config.index.name}' does not exist for validate_only mode"
63-
)
64-
await asyncio.wait_for(self._index.create(), timeout=timeout)
65-
66-
# Vectorizer construction may perform provider-specific setup, so keep it
67-
# off the event loop and bound it with the same startup timeout.
68-
self._vectorizer = await asyncio.wait_for(
69-
asyncio.to_thread(self._build_vectorizer),
70-
timeout=timeout,
71-
)
72-
self._validate_vectorizer_dims()
56+
try:
57+
timeout = self.config.runtime.startup_timeout_seconds
58+
index_exists = await asyncio.wait_for(self._index.exists(), timeout=timeout)
59+
if not index_exists:
60+
if self.config.runtime.index_mode == "validate_only":
61+
raise ValueError(
62+
f"Index '{self.config.index.name}' does not exist for validate_only mode"
63+
)
64+
await asyncio.wait_for(self._index.create(), timeout=timeout)
65+
66+
# Vectorizer construction may perform provider-specific setup, so keep it
67+
# off the event loop and bound it with the same startup timeout.
68+
self._vectorizer = await asyncio.wait_for(
69+
asyncio.to_thread(self._build_vectorizer),
70+
timeout=timeout,
71+
)
72+
self._validate_vectorizer_dims()
73+
except Exception:
74+
await self.shutdown()
75+
raise
7376

7477
async def shutdown(self) -> None:
7578
"""Release owned vectorizer and Redis resources."""

tests/integration/test_mcp/test_server_startup.py

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import pytest
44

5+
from redisvl.index import AsyncSearchIndex
56
from redisvl.mcp.server import RedisVLMCPServer
67
from redisvl.mcp.settings import MCPSettings
78

@@ -141,6 +142,40 @@ async def test_server_fails_fast_on_vector_dimension_mismatch(
141142
await server.startup()
142143

143144

145+
@pytest.mark.asyncio
146+
async def test_server_startup_failure_disconnects_index(
147+
monkeypatch, mcp_config_path, worker_id
148+
):
149+
monkeypatch.setattr(
150+
"redisvl.mcp.server.resolve_vectorizer_class",
151+
lambda class_name: FakeVectorizer,
152+
)
153+
original_disconnect = AsyncSearchIndex.disconnect
154+
disconnect_called = False
155+
156+
async def tracked_disconnect(self):
157+
nonlocal disconnect_called
158+
disconnect_called = True
159+
await original_disconnect(self)
160+
161+
monkeypatch.setattr(
162+
"redisvl.mcp.server.AsyncSearchIndex.disconnect",
163+
tracked_disconnect,
164+
)
165+
settings = MCPSettings(
166+
config=mcp_config_path(
167+
index_name=f"mcp-startup-failure-{worker_id}",
168+
vector_dims=8,
169+
)
170+
)
171+
server = RedisVLMCPServer(settings)
172+
173+
with pytest.raises(ValueError, match="Vectorizer dims"):
174+
await server.startup()
175+
176+
assert disconnect_called is True
177+
178+
144179
@pytest.mark.asyncio
145180
async def test_server_shutdown_disconnects_owned_client(
146181
monkeypatch, mcp_config_path, worker_id

0 commit comments

Comments
 (0)