|
2 | 2 |
|
3 | 3 | from __future__ import annotations
|
4 | 4 |
|
5 |
| -import asyncio |
| 5 | +import logging |
6 | 6 | import secrets
|
7 | 7 | import time
|
8 | 8 | from collections.abc import Callable
|
9 | 9 | from dataclasses import dataclass
|
10 | 10 | from typing import Any, Generic, TypeVar
|
11 | 11 |
|
| 12 | +import anyio |
| 13 | + |
12 | 14 | import mcp.types as types
|
13 | 15 | from mcp.types import AsyncOperationStatus
|
14 | 16 |
|
@@ -66,9 +68,9 @@ class BaseOperationManager(Generic[OperationT]):
|
66 | 68 |
|
67 | 69 | def __init__(self, *, token_generator: Callable[[str | None], str] | None = None):
|
68 | 70 | self._operations: dict[str, OperationT] = {}
|
69 |
| - self._cleanup_task: asyncio.Task[None] | None = None |
70 | 71 | self._cleanup_interval = 60 # Cleanup every 60 seconds
|
71 | 72 | self._token_generator = token_generator or self._default_token_generator
|
| 73 | + self._running = False |
72 | 74 |
|
73 | 75 | def _default_token_generator(self, session_id: str | None = None) -> str:
|
74 | 76 | """Default token generation using random tokens."""
|
@@ -105,31 +107,20 @@ def cleanup_expired(self) -> int:
|
105 | 107 | self._remove_operation(token)
|
106 | 108 | return len(expired_tokens)
|
107 | 109 |
|
108 |
| - async def start_cleanup_task(self) -> None: |
109 |
| - """Start the background cleanup task.""" |
110 |
| - if self._cleanup_task is None: |
111 |
| - self._cleanup_task = asyncio.create_task(self._cleanup_loop()) |
112 |
| - |
113 |
| - async def stop_cleanup_task(self) -> None: |
114 |
| - """Stop the background cleanup task.""" |
115 |
| - if self._cleanup_task: |
116 |
| - self._cleanup_task.cancel() |
117 |
| - try: |
118 |
| - await self._cleanup_task |
119 |
| - except asyncio.CancelledError: |
120 |
| - pass |
121 |
| - self._cleanup_task = None |
122 |
| - |
123 |
| - async def _cleanup_loop(self) -> None: |
| 110 | + async def stop_cleanup_loop(self) -> None: |
| 111 | + self._running = False |
| 112 | + |
| 113 | + async def cleanup_loop(self) -> None: |
124 | 114 | """Background task to clean up expired operations."""
|
125 |
| - while True: |
126 |
| - try: |
127 |
| - await asyncio.sleep(self._cleanup_interval) |
128 |
| - count = self.cleanup_expired() |
129 |
| - if count > 0: |
130 |
| - print(f"Cleaned up {count} expired operations") |
131 |
| - except asyncio.CancelledError: |
132 |
| - break |
| 115 | + if self._running: |
| 116 | + return |
| 117 | + self._running = True |
| 118 | + |
| 119 | + while self._running: |
| 120 | + await anyio.sleep(self._cleanup_interval) |
| 121 | + count = self.cleanup_expired() |
| 122 | + if count > 0: |
| 123 | + logging.debug(f"Cleaned up {count} expired operations") |
133 | 124 |
|
134 | 125 |
|
135 | 126 | class ClientAsyncOperationManager(BaseOperationManager[ClientAsyncOperation]):
|
@@ -292,32 +283,3 @@ def mark_input_completed(self, token: str) -> bool:
|
292 | 283 |
|
293 | 284 | operation.status = "working"
|
294 | 285 | return True
|
295 |
| - |
296 |
| - async def start_cleanup_task(self) -> None: |
297 |
| - """Start the background cleanup task.""" |
298 |
| - if self._cleanup_task is not None: |
299 |
| - return |
300 |
| - |
301 |
| - self._cleanup_task = asyncio.create_task(self._cleanup_loop()) |
302 |
| - |
303 |
| - async def stop_cleanup_task(self) -> None: |
304 |
| - """Stop the background cleanup task.""" |
305 |
| - if self._cleanup_task is not None: |
306 |
| - self._cleanup_task.cancel() |
307 |
| - try: |
308 |
| - await self._cleanup_task |
309 |
| - except asyncio.CancelledError: |
310 |
| - pass |
311 |
| - self._cleanup_task = None |
312 |
| - |
313 |
| - async def _cleanup_loop(self) -> None: |
314 |
| - """Background cleanup loop.""" |
315 |
| - while True: |
316 |
| - try: |
317 |
| - await asyncio.sleep(self._cleanup_interval) |
318 |
| - self.cleanup_expired_operations() |
319 |
| - except asyncio.CancelledError: |
320 |
| - break |
321 |
| - except Exception: |
322 |
| - # Log error but continue cleanup loop |
323 |
| - pass |
0 commit comments