mcp-mesh 0.5.5__py3-none-any.whl → 0.5.7__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,236 +0,0 @@
1
- """
2
- Graceful Shutdown Manager for MCP Mesh agents.
3
-
4
- This utility class manages graceful shutdown functionality, including signal handlers,
5
- shutdown context management, and coordination with FastAPI lifecycle and registry.
6
- """
7
-
8
- import asyncio
9
- import logging
10
- import os
11
- import signal
12
- import sys
13
- from typing import Any, Optional
14
-
15
- logger = logging.getLogger(__name__)
16
-
17
-
18
- class GracefulShutdownManager:
19
- """Manages graceful shutdown for MCP Mesh agents."""
20
-
21
- def __init__(self):
22
- self._shutdown_requested: bool = False
23
- self._shutdown_context: dict[str, Any] = {}
24
-
25
- def set_shutdown_context(self, context: dict[str, Any]) -> None:
26
- """Set context for graceful shutdown (called from pipeline)."""
27
- self._shutdown_context.update(context)
28
-
29
- # Extract FastAPI app from context for coordinated shutdown
30
- fastapi_app = context.get("fastapi_app")
31
- if fastapi_app:
32
- self._shutdown_context["fastapi_app"] = fastapi_app
33
- logger.debug(
34
- f"🔧 FastAPI app stored for coordinated shutdown: {type(fastapi_app)}"
35
- )
36
-
37
- logger.debug(
38
- f"🔧 Shutdown context updated: agent_id={context.get('agent_id')}, registry_url={context.get('registry_url')}"
39
- )
40
-
41
- def install_signal_handlers(self) -> None:
42
- """Install signal handlers that set the shutdown flag."""
43
-
44
- def shutdown_signal_handler(signum, frame):
45
- """Handle shutdown signals by setting the shutdown flag."""
46
- logger.info(
47
- f"🚨 SIGNAL HANDLER: Received signal {signum}, setting shutdown flag"
48
- )
49
- self._shutdown_requested = True
50
-
51
- # Install handlers for common termination signals
52
- signal.signal(signal.SIGTERM, shutdown_signal_handler)
53
- signal.signal(signal.SIGINT, shutdown_signal_handler)
54
-
55
- logger.info("🛡️ Signal handlers installed for graceful shutdown")
56
-
57
- def is_shutdown_requested(self) -> bool:
58
- """Check if shutdown has been requested."""
59
- return self._shutdown_requested
60
-
61
- def perform_graceful_shutdown_from_main_thread(self) -> None:
62
- """Perform graceful shutdown from main thread (non-async context)."""
63
- try:
64
- # Check if we have FastAPI app for coordinated shutdown
65
- fastapi_app = self._shutdown_context.get("fastapi_app")
66
-
67
- if (
68
- fastapi_app
69
- and hasattr(fastapi_app, "state")
70
- and hasattr(fastapi_app.state, "shutdown_step")
71
- ):
72
- # Use FastAPI lifespan shutdown mechanism
73
- logger.info("🚨 Triggering coordinated FastAPI shutdown...")
74
- shutdown_step = fastapi_app.state.shutdown_step
75
-
76
- # Create minimal context and call the existing graceful shutdown
77
- loop = asyncio.new_event_loop()
78
- asyncio.set_event_loop(loop)
79
-
80
- try:
81
- loop.run_until_complete(
82
- shutdown_step._graceful_shutdown(fastapi_app)
83
- )
84
- logger.info("✅ FastAPI coordinated shutdown completed")
85
- return
86
- finally:
87
- loop.close()
88
-
89
- # Fallback: Direct registry call if no FastAPI coordination available
90
- logger.info("🚨 Using fallback direct registry shutdown...")
91
-
92
- # Get registry_url from context or environment (with default)
93
- registry_url = self._shutdown_context.get("registry_url")
94
- if not registry_url:
95
- registry_url = os.getenv(
96
- "MCP_MESH_REGISTRY_URL", "http://localhost:8000"
97
- )
98
- logger.debug(
99
- f"🔧 Using registry URL from environment/default: {registry_url}"
100
- )
101
-
102
- # Get agent_id from context or generate the same one used for registration
103
- agent_id = self._shutdown_context.get("agent_id")
104
- if not agent_id:
105
- agent_id = self._get_or_create_agent_id()
106
- logger.debug(f"🔧 Using agent ID from shared state: {agent_id}")
107
-
108
- if not registry_url or not agent_id:
109
- logger.warning(
110
- f"⚠️ Cannot perform graceful shutdown: missing registry_url={registry_url} or agent_id={agent_id}"
111
- )
112
- return
113
-
114
- # Create registry client and perform shutdown synchronously
115
- loop = asyncio.new_event_loop()
116
- asyncio.set_event_loop(loop)
117
-
118
- try:
119
- loop.run_until_complete(
120
- self._perform_graceful_shutdown_async(registry_url, agent_id)
121
- )
122
- finally:
123
- loop.close()
124
-
125
- except Exception as e:
126
- logger.error(f"❌ Graceful shutdown error: {e}")
127
-
128
- async def _perform_graceful_shutdown_async(
129
- self, registry_url: str, agent_id: str
130
- ) -> None:
131
- """Async graceful shutdown implementation."""
132
- try:
133
- # Create registry client for shutdown
134
- from _mcp_mesh.generated.mcp_mesh_registry_client.api_client import (
135
- ApiClient,
136
- )
137
- from _mcp_mesh.generated.mcp_mesh_registry_client.configuration import (
138
- Configuration,
139
- )
140
- from _mcp_mesh.shared.registry_client_wrapper import RegistryClientWrapper
141
-
142
- config = Configuration(host=registry_url)
143
- api_client = ApiClient(configuration=config)
144
- registry_wrapper = RegistryClientWrapper(api_client)
145
-
146
- # Perform graceful unregistration (this sends DELETE /heartbeats)
147
- success = await registry_wrapper.unregister_agent(agent_id)
148
- if success:
149
- logger.info(
150
- f"✅ Agent '{agent_id}' successfully unregistered from registry"
151
- )
152
- else:
153
- logger.warning(
154
- f"⚠️ Failed to unregister agent '{agent_id}' from registry"
155
- )
156
-
157
- except Exception as e:
158
- logger.error(f"❌ Graceful shutdown error: {e}")
159
-
160
- def _get_or_create_agent_id(self) -> str:
161
- """Get agent ID using the existing decorator registry function."""
162
- from mesh.decorators import _get_or_create_agent_id
163
-
164
- return _get_or_create_agent_id()
165
-
166
- def start_blocking_loop_with_shutdown_support(self, thread) -> None:
167
- """
168
- Start the main thread blocking loop with graceful shutdown support.
169
-
170
- This keeps the main thread alive while monitoring for shutdown signals
171
- and maintaining the server thread.
172
- """
173
- logger.info(
174
- "🔒 MAIN THREAD: Blocking to keep alive (prevents threading shutdown state)"
175
- )
176
-
177
- # Install signal handlers
178
- self.install_signal_handlers()
179
-
180
- try:
181
- while True:
182
- # Check shutdown flag (set by signal handlers)
183
- if self.is_shutdown_requested():
184
- logger.info(
185
- "🚨 MAIN THREAD: Shutdown requested, performing graceful cleanup..."
186
- )
187
- self.perform_graceful_shutdown_from_main_thread()
188
- break
189
-
190
- if thread.is_alive():
191
- thread.join(timeout=1) # Check every second
192
- else:
193
- logger.warning("⚠️ Server thread died, exiting...")
194
- break
195
- except KeyboardInterrupt:
196
- logger.info("👋 MAIN THREAD: Received interrupt, shutting down gracefully")
197
- self.perform_graceful_shutdown_from_main_thread()
198
- except Exception as e:
199
- logger.error(f"❌ MAIN THREAD: Error in blocking loop: {e}")
200
-
201
- logger.info("🏁 MAIN THREAD: Exiting blocking loop")
202
-
203
-
204
- # Global instance for backward compatibility with existing decorators.py usage
205
- _global_shutdown_manager = GracefulShutdownManager()
206
-
207
-
208
- def get_global_shutdown_manager() -> GracefulShutdownManager:
209
- """Get the global shutdown manager instance."""
210
- return _global_shutdown_manager
211
-
212
-
213
- # Convenience functions for backward compatibility
214
- def set_shutdown_context(context: dict[str, Any]) -> None:
215
- """Set context for graceful shutdown (global convenience function)."""
216
- _global_shutdown_manager.set_shutdown_context(context)
217
-
218
-
219
- def install_graceful_shutdown_handlers() -> None:
220
- """Install signal handlers that set the shutdown flag (global convenience function)."""
221
- _global_shutdown_manager.install_signal_handlers()
222
-
223
-
224
- def is_shutdown_requested() -> bool:
225
- """Check if shutdown has been requested (global convenience function)."""
226
- return _global_shutdown_manager.is_shutdown_requested()
227
-
228
-
229
- def perform_graceful_shutdown_from_main_thread() -> None:
230
- """Perform graceful shutdown from main thread (global convenience function)."""
231
- _global_shutdown_manager.perform_graceful_shutdown_from_main_thread()
232
-
233
-
234
- def start_blocking_loop_with_shutdown_support(thread) -> None:
235
- """Start the main thread blocking loop with graceful shutdown support (global convenience function)."""
236
- _global_shutdown_manager.start_blocking_loop_with_shutdown_support(thread)