mcp-proxy-adapter 6.6.9__py3-none-any.whl → 6.7.0__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.
- mcp_proxy_adapter/api/app.py +60 -15
- mcp_proxy_adapter/commands/__init__.py +4 -0
- mcp_proxy_adapter/commands/registration_status_command.py +119 -0
- mcp_proxy_adapter/core/app_runner.py +29 -2
- mcp_proxy_adapter/core/async_proxy_registration.py +285 -0
- mcp_proxy_adapter/core/signal_handler.py +170 -0
- mcp_proxy_adapter/examples/config_builder.py +405 -61
- mcp_proxy_adapter/examples/generate_config.py +21 -16
- mcp_proxy_adapter/main.py +37 -1
- mcp_proxy_adapter/version.py +1 -1
- {mcp_proxy_adapter-6.6.9.dist-info → mcp_proxy_adapter-6.7.0.dist-info}/METADATA +1 -1
- {mcp_proxy_adapter-6.6.9.dist-info → mcp_proxy_adapter-6.7.0.dist-info}/RECORD +15 -12
- {mcp_proxy_adapter-6.6.9.dist-info → mcp_proxy_adapter-6.7.0.dist-info}/WHEEL +0 -0
- {mcp_proxy_adapter-6.6.9.dist-info → mcp_proxy_adapter-6.7.0.dist-info}/entry_points.txt +0 -0
- {mcp_proxy_adapter-6.6.9.dist-info → mcp_proxy_adapter-6.7.0.dist-info}/top_level.txt +0 -0
mcp_proxy_adapter/api/app.py
CHANGED
@@ -159,18 +159,24 @@ def create_lifespan(config_path: Optional[str] = None):
|
|
159
159
|
# Determine registration URL using unified logic
|
160
160
|
early_server_url = _determine_registration_url(config)
|
161
161
|
try:
|
162
|
-
from mcp_proxy_adapter.core.
|
163
|
-
|
162
|
+
from mcp_proxy_adapter.core.async_proxy_registration import (
|
163
|
+
initialize_async_registration,
|
164
|
+
start_async_registration,
|
164
165
|
)
|
165
166
|
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
167
|
+
# Initialize async registration manager
|
168
|
+
async_manager = initialize_async_registration(config)
|
169
|
+
logger.info(
|
170
|
+
"🔍 Initialized async registration manager with server_url: %s",
|
171
|
+
early_server_url,
|
172
|
+
)
|
173
|
+
|
174
|
+
# Start async registration in background thread
|
175
|
+
start_async_registration(early_server_url)
|
176
|
+
logger.info("🚀 Started async proxy registration and heartbeat thread")
|
177
|
+
|
172
178
|
except Exception as e:
|
173
|
-
logger.error(f"Failed to
|
179
|
+
logger.error(f"Failed to initialize async registration: {e}")
|
174
180
|
|
175
181
|
# Initialize system using unified logic (may perform registration)
|
176
182
|
if config_path:
|
@@ -194,6 +200,27 @@ def create_lifespan(config_path: Optional[str] = None):
|
|
194
200
|
|
195
201
|
# Determine registration URL using unified logic
|
196
202
|
server_url = _determine_registration_url(final_config)
|
203
|
+
|
204
|
+
# Update async registration manager with final server URL
|
205
|
+
try:
|
206
|
+
from mcp_proxy_adapter.core.async_proxy_registration import (
|
207
|
+
get_async_registration_manager,
|
208
|
+
)
|
209
|
+
|
210
|
+
async_manager = get_async_registration_manager()
|
211
|
+
if async_manager:
|
212
|
+
async_manager.set_server_url(server_url)
|
213
|
+
logger.info(f"🔍 Updated async registration manager with final server_url: {server_url}")
|
214
|
+
|
215
|
+
# Get current status
|
216
|
+
status = async_manager.get_status()
|
217
|
+
logger.info(f"📊 Registration status: {status}")
|
218
|
+
else:
|
219
|
+
logger.warning("Async registration manager not available")
|
220
|
+
|
221
|
+
except Exception as e:
|
222
|
+
logger.error(f"Failed to update async registration manager: {e}")
|
223
|
+
|
197
224
|
try:
|
198
225
|
print("🔍 Registration server_url resolved to (print):", server_url)
|
199
226
|
except Exception:
|
@@ -228,12 +255,30 @@ def create_lifespan(config_path: Optional[str] = None):
|
|
228
255
|
# Shutdown events
|
229
256
|
logger.info("Application shutting down")
|
230
257
|
|
231
|
-
#
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
258
|
+
# Stop async registration manager (this will also unregister)
|
259
|
+
try:
|
260
|
+
from mcp_proxy_adapter.core.async_proxy_registration import (
|
261
|
+
stop_async_registration,
|
262
|
+
get_registration_status,
|
263
|
+
)
|
264
|
+
|
265
|
+
# Get final status before stopping
|
266
|
+
final_status = get_registration_status()
|
267
|
+
logger.info(f"📊 Final registration status: {final_status}")
|
268
|
+
|
269
|
+
# Stop async registration (this will also unregister)
|
270
|
+
stop_async_registration()
|
271
|
+
logger.info("✅ Async registration manager stopped successfully")
|
272
|
+
|
273
|
+
except Exception as e:
|
274
|
+
logger.error(f"❌ Failed to stop async registration: {e}")
|
275
|
+
|
276
|
+
# Fallback to old unregistration method
|
277
|
+
unregistration_success = await unregister_from_proxy()
|
278
|
+
if unregistration_success:
|
279
|
+
logger.info("✅ Fallback proxy unregistration completed successfully")
|
280
|
+
else:
|
281
|
+
logger.warning("⚠️ Fallback proxy unregistration failed or was disabled")
|
237
282
|
|
238
283
|
return lifespan
|
239
284
|
|
@@ -24,6 +24,9 @@ from mcp_proxy_adapter.commands.echo_command import EchoCommand
|
|
24
24
|
from mcp_proxy_adapter.commands.proxy_registration_command import (
|
25
25
|
ProxyRegistrationCommand,
|
26
26
|
)
|
27
|
+
from mcp_proxy_adapter.commands.registration_status_command import (
|
28
|
+
RegistrationStatusCommand,
|
29
|
+
)
|
27
30
|
|
28
31
|
__all__ = [
|
29
32
|
"Command",
|
@@ -43,4 +46,5 @@ __all__ = [
|
|
43
46
|
"RoleTestCommand",
|
44
47
|
"EchoCommand",
|
45
48
|
"ProxyRegistrationCommand",
|
49
|
+
"RegistrationStatusCommand",
|
46
50
|
]
|
@@ -0,0 +1,119 @@
|
|
1
|
+
"""
|
2
|
+
Registration Status Command
|
3
|
+
|
4
|
+
This command provides information about the current proxy registration status,
|
5
|
+
including async registration state, heartbeat status, and statistics.
|
6
|
+
|
7
|
+
Author: Vasiliy Zdanovskiy
|
8
|
+
email: vasilyvz@gmail.com
|
9
|
+
"""
|
10
|
+
|
11
|
+
from typing import Dict, Any
|
12
|
+
from dataclasses import dataclass
|
13
|
+
|
14
|
+
from mcp_proxy_adapter.commands.base import Command
|
15
|
+
from mcp_proxy_adapter.commands.result import SuccessResult
|
16
|
+
from mcp_proxy_adapter.core.logging import logger
|
17
|
+
|
18
|
+
|
19
|
+
@dataclass
|
20
|
+
class RegistrationStatusCommandResult(SuccessResult):
|
21
|
+
"""Result of registration status command."""
|
22
|
+
|
23
|
+
status: Dict[str, Any]
|
24
|
+
message: str = "Registration status retrieved successfully"
|
25
|
+
|
26
|
+
def to_dict(self) -> Dict[str, Any]:
|
27
|
+
"""Convert result to dictionary."""
|
28
|
+
return {
|
29
|
+
"success": True,
|
30
|
+
"status": self.status,
|
31
|
+
"message": self.message,
|
32
|
+
}
|
33
|
+
|
34
|
+
@classmethod
|
35
|
+
def get_schema(cls) -> Dict[str, Any]:
|
36
|
+
"""Get JSON schema for result."""
|
37
|
+
return {
|
38
|
+
"type": "object",
|
39
|
+
"properties": {
|
40
|
+
"success": {
|
41
|
+
"type": "boolean",
|
42
|
+
"description": "Whether the command was successful",
|
43
|
+
},
|
44
|
+
"status": {
|
45
|
+
"type": "object",
|
46
|
+
"description": "Registration status information",
|
47
|
+
"properties": {
|
48
|
+
"state": {"type": "string", "description": "Current registration state"},
|
49
|
+
"server_key": {"type": "string", "description": "Server key if registered"},
|
50
|
+
"last_attempt": {"type": "number", "description": "Timestamp of last registration attempt"},
|
51
|
+
"last_success": {"type": "number", "description": "Timestamp of last successful registration"},
|
52
|
+
"last_error": {"type": "string", "description": "Last error message"},
|
53
|
+
"attempt_count": {"type": "integer", "description": "Total registration attempts"},
|
54
|
+
"success_count": {"type": "integer", "description": "Total successful registrations"},
|
55
|
+
"heartbeat_enabled": {"type": "boolean", "description": "Whether heartbeat is enabled"},
|
56
|
+
"heartbeat_interval": {"type": "integer", "description": "Heartbeat interval in seconds"},
|
57
|
+
"thread_alive": {"type": "boolean", "description": "Whether registration thread is alive"},
|
58
|
+
},
|
59
|
+
},
|
60
|
+
"message": {"type": "string", "description": "Result message"},
|
61
|
+
},
|
62
|
+
"required": ["success", "status", "message"],
|
63
|
+
}
|
64
|
+
|
65
|
+
|
66
|
+
class RegistrationStatusCommand(Command):
|
67
|
+
"""Command to get proxy registration status."""
|
68
|
+
|
69
|
+
name = "registration_status"
|
70
|
+
descr = "Get current proxy registration status and statistics"
|
71
|
+
category = "proxy"
|
72
|
+
author = "Vasiliy Zdanovskiy"
|
73
|
+
email = "vasilyvz@gmail.com"
|
74
|
+
|
75
|
+
async def execute(self, **kwargs) -> RegistrationStatusCommandResult:
|
76
|
+
"""
|
77
|
+
Execute registration status command.
|
78
|
+
|
79
|
+
Returns:
|
80
|
+
RegistrationStatusCommandResult with current status
|
81
|
+
"""
|
82
|
+
logger.info("Executing registration status command")
|
83
|
+
|
84
|
+
try:
|
85
|
+
from mcp_proxy_adapter.core.async_proxy_registration import (
|
86
|
+
get_registration_status,
|
87
|
+
)
|
88
|
+
|
89
|
+
status = get_registration_status()
|
90
|
+
|
91
|
+
logger.info(f"Registration status retrieved: {status}")
|
92
|
+
|
93
|
+
return RegistrationStatusCommandResult(
|
94
|
+
status=status,
|
95
|
+
message="Registration status retrieved successfully"
|
96
|
+
)
|
97
|
+
|
98
|
+
except Exception as e:
|
99
|
+
logger.error(f"Failed to get registration status: {e}")
|
100
|
+
|
101
|
+
error_status = {
|
102
|
+
"state": "error",
|
103
|
+
"error": str(e),
|
104
|
+
"thread_alive": False
|
105
|
+
}
|
106
|
+
|
107
|
+
return RegistrationStatusCommandResult(
|
108
|
+
status=error_status,
|
109
|
+
message=f"Failed to get registration status: {e}"
|
110
|
+
)
|
111
|
+
|
112
|
+
@classmethod
|
113
|
+
def get_schema(cls) -> Dict[str, Any]:
|
114
|
+
"""Get JSON schema for command parameters."""
|
115
|
+
return {
|
116
|
+
"type": "object",
|
117
|
+
"properties": {},
|
118
|
+
"additionalProperties": False,
|
119
|
+
}
|
@@ -16,6 +16,7 @@ from typing import Dict, Any, List, Optional
|
|
16
16
|
from fastapi import FastAPI
|
17
17
|
|
18
18
|
from mcp_proxy_adapter.core.logging import get_logger
|
19
|
+
from mcp_proxy_adapter.core.signal_handler import setup_signal_handling, is_shutdown_requested
|
19
20
|
|
20
21
|
logger = get_logger("app_runner")
|
21
22
|
|
@@ -250,6 +251,30 @@ class ApplicationRunner:
|
|
250
251
|
print(f" - {error}", file=sys.stderr)
|
251
252
|
sys.exit(1)
|
252
253
|
|
254
|
+
# Setup signal handling for graceful shutdown
|
255
|
+
def shutdown_callback():
|
256
|
+
"""Callback for graceful shutdown with proxy unregistration."""
|
257
|
+
print("\n🛑 Graceful shutdown initiated...")
|
258
|
+
try:
|
259
|
+
from mcp_proxy_adapter.core.async_proxy_registration import (
|
260
|
+
stop_async_registration,
|
261
|
+
get_registration_status,
|
262
|
+
)
|
263
|
+
|
264
|
+
# Get final status
|
265
|
+
final_status = get_registration_status()
|
266
|
+
print(f"📊 Final registration status: {final_status}")
|
267
|
+
|
268
|
+
# Stop async registration (this will unregister from proxy)
|
269
|
+
stop_async_registration()
|
270
|
+
print("✅ Proxy unregistration completed")
|
271
|
+
|
272
|
+
except Exception as e:
|
273
|
+
print(f"❌ Error during shutdown: {e}")
|
274
|
+
|
275
|
+
setup_signal_handling(shutdown_callback)
|
276
|
+
print("🔧 Signal handling configured for graceful shutdown")
|
277
|
+
|
253
278
|
# Setup hooks
|
254
279
|
self.setup_hooks()
|
255
280
|
|
@@ -276,14 +301,16 @@ class ApplicationRunner:
|
|
276
301
|
import asyncio
|
277
302
|
|
278
303
|
print(f"🚀 Starting server on {host}:{port}")
|
279
|
-
print("
|
304
|
+
print("🛑 Use Ctrl+C or send SIGTERM for graceful shutdown")
|
280
305
|
print("=" * 60)
|
281
306
|
|
282
307
|
# Run with hypercorn
|
283
308
|
asyncio.run(hypercorn.asyncio.serve(self.app, **server_kwargs))
|
284
309
|
|
285
310
|
except KeyboardInterrupt:
|
286
|
-
print("\n🛑 Server stopped by user")
|
311
|
+
print("\n🛑 Server stopped by user (Ctrl+C)")
|
312
|
+
if is_shutdown_requested():
|
313
|
+
print("✅ Graceful shutdown completed")
|
287
314
|
except Exception as e:
|
288
315
|
print(f"\n❌ Failed to start server: {e}", file=sys.stderr)
|
289
316
|
sys.exit(1)
|
@@ -0,0 +1,285 @@
|
|
1
|
+
"""
|
2
|
+
Asynchronous proxy registration with heartbeat functionality.
|
3
|
+
|
4
|
+
This module provides automatic proxy registration in a separate thread
|
5
|
+
with continuous heartbeat monitoring and automatic re-registration on failures.
|
6
|
+
|
7
|
+
Author: Vasiliy Zdanovskiy
|
8
|
+
email: vasilyvz@gmail.com
|
9
|
+
"""
|
10
|
+
|
11
|
+
import asyncio
|
12
|
+
import time
|
13
|
+
import threading
|
14
|
+
from typing import Dict, Any, Optional
|
15
|
+
from dataclasses import dataclass
|
16
|
+
from enum import Enum
|
17
|
+
|
18
|
+
from mcp_proxy_adapter.core.logging import logger
|
19
|
+
from mcp_proxy_adapter.core.proxy_registration import ProxyRegistrationManager
|
20
|
+
|
21
|
+
|
22
|
+
class RegistrationState(Enum):
|
23
|
+
"""Registration state enumeration."""
|
24
|
+
DISABLED = "disabled"
|
25
|
+
NOT_REGISTERED = "not_registered"
|
26
|
+
REGISTERING = "registering"
|
27
|
+
REGISTERED = "registered"
|
28
|
+
HEARTBEAT_FAILED = "heartbeat_failed"
|
29
|
+
ERROR = "error"
|
30
|
+
|
31
|
+
|
32
|
+
@dataclass
|
33
|
+
class RegistrationStatus:
|
34
|
+
"""Registration status information."""
|
35
|
+
state: RegistrationState
|
36
|
+
server_key: Optional[str] = None
|
37
|
+
last_attempt: Optional[float] = None
|
38
|
+
last_success: Optional[float] = None
|
39
|
+
last_error: Optional[str] = None
|
40
|
+
attempt_count: int = 0
|
41
|
+
success_count: int = 0
|
42
|
+
|
43
|
+
|
44
|
+
class AsyncProxyRegistrationManager:
|
45
|
+
"""
|
46
|
+
Asynchronous proxy registration manager with heartbeat functionality.
|
47
|
+
|
48
|
+
Runs registration and heartbeat in a separate thread, automatically
|
49
|
+
handling re-registration on failures and continuous monitoring.
|
50
|
+
"""
|
51
|
+
|
52
|
+
def __init__(self, config: Dict[str, Any]):
|
53
|
+
"""
|
54
|
+
Initialize the async proxy registration manager.
|
55
|
+
|
56
|
+
Args:
|
57
|
+
config: Application configuration
|
58
|
+
"""
|
59
|
+
self.config = config
|
60
|
+
self.registration_manager = ProxyRegistrationManager(config)
|
61
|
+
self.status = RegistrationStatus(state=RegistrationState.NOT_REGISTERED)
|
62
|
+
self._thread: Optional[threading.Thread] = None
|
63
|
+
self._stop_event = threading.Event()
|
64
|
+
self._loop: Optional[asyncio.AbstractEventLoop] = None
|
65
|
+
|
66
|
+
# Get heartbeat configuration
|
67
|
+
heartbeat_config = config.get("registration", {}).get("heartbeat", {})
|
68
|
+
self.heartbeat_enabled = heartbeat_config.get("enabled", True)
|
69
|
+
self.heartbeat_interval = heartbeat_config.get("interval", 30)
|
70
|
+
self.heartbeat_timeout = heartbeat_config.get("timeout", 10)
|
71
|
+
self.retry_attempts = heartbeat_config.get("retry_attempts", 3)
|
72
|
+
self.retry_delay = heartbeat_config.get("retry_delay", 5)
|
73
|
+
|
74
|
+
logger.info(f"AsyncProxyRegistrationManager initialized: heartbeat_enabled={self.heartbeat_enabled}, interval={self.heartbeat_interval}s")
|
75
|
+
|
76
|
+
def is_enabled(self) -> bool:
|
77
|
+
"""Check if proxy registration is enabled."""
|
78
|
+
return self.registration_manager.is_enabled()
|
79
|
+
|
80
|
+
def set_server_url(self, server_url: str):
|
81
|
+
"""Set the server URL for registration."""
|
82
|
+
self.registration_manager.set_server_url(server_url)
|
83
|
+
logger.info(f"Server URL set for async registration: {server_url}")
|
84
|
+
|
85
|
+
def start(self):
|
86
|
+
"""Start the async registration and heartbeat thread."""
|
87
|
+
if not self.is_enabled():
|
88
|
+
logger.info("Proxy registration is disabled, not starting async manager")
|
89
|
+
self.status.state = RegistrationState.DISABLED
|
90
|
+
return
|
91
|
+
|
92
|
+
if self._thread and self._thread.is_alive():
|
93
|
+
logger.warning("Async registration thread is already running")
|
94
|
+
return
|
95
|
+
|
96
|
+
logger.info("Starting async proxy registration and heartbeat thread")
|
97
|
+
self._stop_event.clear()
|
98
|
+
self._thread = threading.Thread(target=self._run_async_loop, daemon=True)
|
99
|
+
self._thread.start()
|
100
|
+
|
101
|
+
def stop(self):
|
102
|
+
"""Stop the async registration and heartbeat thread."""
|
103
|
+
if not self._thread or not self._thread.is_alive():
|
104
|
+
return
|
105
|
+
|
106
|
+
logger.info("Stopping async proxy registration and heartbeat thread")
|
107
|
+
self._stop_event.set()
|
108
|
+
|
109
|
+
# Unregister if registered
|
110
|
+
if self.status.state == RegistrationState.REGISTERED:
|
111
|
+
try:
|
112
|
+
# Run unregistration in the thread's event loop
|
113
|
+
if self._loop and not self._loop.is_closed():
|
114
|
+
future = asyncio.run_coroutine_threadsafe(
|
115
|
+
self.registration_manager.unregister_server(),
|
116
|
+
self._loop
|
117
|
+
)
|
118
|
+
future.result(timeout=10)
|
119
|
+
logger.info("Successfully unregistered from proxy during shutdown")
|
120
|
+
except Exception as e:
|
121
|
+
logger.error(f"Failed to unregister during shutdown: {e}")
|
122
|
+
|
123
|
+
self._thread.join(timeout=5)
|
124
|
+
if self._thread.is_alive():
|
125
|
+
logger.warning("Async registration thread did not stop gracefully")
|
126
|
+
|
127
|
+
def _run_async_loop(self):
|
128
|
+
"""Run the async event loop in the thread."""
|
129
|
+
try:
|
130
|
+
self._loop = asyncio.new_event_loop()
|
131
|
+
asyncio.set_event_loop(self._loop)
|
132
|
+
self._loop.run_until_complete(self._async_main())
|
133
|
+
except Exception as e:
|
134
|
+
logger.error(f"Async registration loop error: {e}")
|
135
|
+
finally:
|
136
|
+
if self._loop and not self._loop.is_closed():
|
137
|
+
self._loop.close()
|
138
|
+
|
139
|
+
async def _async_main(self):
|
140
|
+
"""Main async loop for registration and heartbeat."""
|
141
|
+
logger.info("Async registration loop started")
|
142
|
+
|
143
|
+
# Initial registration attempt
|
144
|
+
await self._attempt_registration()
|
145
|
+
|
146
|
+
# Main loop
|
147
|
+
while not self._stop_event.is_set():
|
148
|
+
try:
|
149
|
+
if self.status.state == RegistrationState.REGISTERED:
|
150
|
+
if self.heartbeat_enabled:
|
151
|
+
# Perform heartbeat
|
152
|
+
await self._perform_heartbeat()
|
153
|
+
else:
|
154
|
+
# Just wait if heartbeat is disabled
|
155
|
+
await asyncio.sleep(self.heartbeat_interval)
|
156
|
+
else:
|
157
|
+
# Try to register
|
158
|
+
await self._attempt_registration()
|
159
|
+
# Wait before retry
|
160
|
+
await asyncio.sleep(self.retry_delay)
|
161
|
+
|
162
|
+
except Exception as e:
|
163
|
+
logger.error(f"Error in async registration loop: {e}")
|
164
|
+
self.status.state = RegistrationState.ERROR
|
165
|
+
self.status.last_error = str(e)
|
166
|
+
await asyncio.sleep(self.retry_delay)
|
167
|
+
|
168
|
+
logger.info("Async registration loop stopped")
|
169
|
+
|
170
|
+
async def _attempt_registration(self):
|
171
|
+
"""Attempt to register with the proxy."""
|
172
|
+
if self.status.state == RegistrationState.REGISTERING:
|
173
|
+
return # Already registering
|
174
|
+
|
175
|
+
self.status.state = RegistrationState.REGISTERING
|
176
|
+
self.status.attempt_count += 1
|
177
|
+
self.status.last_attempt = time.time()
|
178
|
+
|
179
|
+
logger.info(f"Attempting proxy registration (attempt #{self.status.attempt_count})")
|
180
|
+
|
181
|
+
try:
|
182
|
+
success = await self.registration_manager.register_server()
|
183
|
+
|
184
|
+
if success:
|
185
|
+
self.status.state = RegistrationState.REGISTERED
|
186
|
+
self.status.last_success = time.time()
|
187
|
+
self.status.success_count += 1
|
188
|
+
self.status.last_error = None
|
189
|
+
logger.info(f"✅ Proxy registration successful (attempt #{self.status.attempt_count})")
|
190
|
+
else:
|
191
|
+
self.status.state = RegistrationState.NOT_REGISTERED
|
192
|
+
self.status.last_error = "Registration returned False"
|
193
|
+
logger.warning(f"❌ Proxy registration failed (attempt #{self.status.attempt_count}): Registration returned False")
|
194
|
+
|
195
|
+
except Exception as e:
|
196
|
+
self.status.state = RegistrationState.NOT_REGISTERED
|
197
|
+
self.status.last_error = str(e)
|
198
|
+
logger.error(f"❌ Proxy registration failed (attempt #{self.status.attempt_count}): {e}")
|
199
|
+
|
200
|
+
async def _perform_heartbeat(self):
|
201
|
+
"""Perform heartbeat to the proxy."""
|
202
|
+
try:
|
203
|
+
# Use the registration manager's heartbeat functionality
|
204
|
+
success = await self.registration_manager.heartbeat()
|
205
|
+
|
206
|
+
if success:
|
207
|
+
logger.debug("💓 Heartbeat successful")
|
208
|
+
self.status.last_success = time.time()
|
209
|
+
else:
|
210
|
+
logger.warning("💓 Heartbeat failed")
|
211
|
+
self.status.state = RegistrationState.HEARTBEAT_FAILED
|
212
|
+
self.status.last_error = "Heartbeat failed"
|
213
|
+
|
214
|
+
# Try to re-register after heartbeat failure
|
215
|
+
logger.info("Attempting re-registration after heartbeat failure")
|
216
|
+
await self._attempt_registration()
|
217
|
+
|
218
|
+
except Exception as e:
|
219
|
+
logger.error(f"💓 Heartbeat error: {e}")
|
220
|
+
self.status.state = RegistrationState.HEARTBEAT_FAILED
|
221
|
+
self.status.last_error = str(e)
|
222
|
+
|
223
|
+
# Try to re-register after heartbeat error
|
224
|
+
logger.info("Attempting re-registration after heartbeat error")
|
225
|
+
await self._attempt_registration()
|
226
|
+
|
227
|
+
# Wait for next heartbeat
|
228
|
+
await asyncio.sleep(self.heartbeat_interval)
|
229
|
+
|
230
|
+
def get_status(self) -> Dict[str, Any]:
|
231
|
+
"""Get current registration status."""
|
232
|
+
return {
|
233
|
+
"state": self.status.state.value,
|
234
|
+
"server_key": self.status.server_key,
|
235
|
+
"last_attempt": self.status.last_attempt,
|
236
|
+
"last_success": self.status.last_success,
|
237
|
+
"last_error": self.status.last_error,
|
238
|
+
"attempt_count": self.status.attempt_count,
|
239
|
+
"success_count": self.status.success_count,
|
240
|
+
"heartbeat_enabled": self.heartbeat_enabled,
|
241
|
+
"heartbeat_interval": self.heartbeat_interval,
|
242
|
+
"thread_alive": self._thread.is_alive() if self._thread else False
|
243
|
+
}
|
244
|
+
|
245
|
+
|
246
|
+
# Global instance
|
247
|
+
_async_registration_manager: Optional[AsyncProxyRegistrationManager] = None
|
248
|
+
|
249
|
+
|
250
|
+
def get_async_registration_manager() -> Optional[AsyncProxyRegistrationManager]:
|
251
|
+
"""Get the global async registration manager instance."""
|
252
|
+
return _async_registration_manager
|
253
|
+
|
254
|
+
|
255
|
+
def initialize_async_registration(config: Dict[str, Any]) -> AsyncProxyRegistrationManager:
|
256
|
+
"""Initialize the global async registration manager."""
|
257
|
+
global _async_registration_manager
|
258
|
+
_async_registration_manager = AsyncProxyRegistrationManager(config)
|
259
|
+
return _async_registration_manager
|
260
|
+
|
261
|
+
|
262
|
+
def start_async_registration(server_url: str):
|
263
|
+
"""Start async registration with the given server URL."""
|
264
|
+
global _async_registration_manager
|
265
|
+
if _async_registration_manager:
|
266
|
+
_async_registration_manager.set_server_url(server_url)
|
267
|
+
_async_registration_manager.start()
|
268
|
+
else:
|
269
|
+
logger.error("Async registration manager not initialized")
|
270
|
+
|
271
|
+
|
272
|
+
def stop_async_registration():
|
273
|
+
"""Stop async registration."""
|
274
|
+
global _async_registration_manager
|
275
|
+
if _async_registration_manager:
|
276
|
+
_async_registration_manager.stop()
|
277
|
+
|
278
|
+
|
279
|
+
def get_registration_status() -> Dict[str, Any]:
|
280
|
+
"""Get current registration status."""
|
281
|
+
global _async_registration_manager
|
282
|
+
if _async_registration_manager:
|
283
|
+
return _async_registration_manager.get_status()
|
284
|
+
else:
|
285
|
+
return {"state": "not_initialized"}
|