crackerjack 0.31.10__py3-none-any.whl → 0.31.13__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.

Potentially problematic release.


This version of crackerjack might be problematic. Click here for more details.

Files changed (155) hide show
  1. crackerjack/CLAUDE.md +288 -705
  2. crackerjack/__main__.py +22 -8
  3. crackerjack/agents/__init__.py +0 -3
  4. crackerjack/agents/architect_agent.py +0 -43
  5. crackerjack/agents/base.py +1 -9
  6. crackerjack/agents/coordinator.py +2 -148
  7. crackerjack/agents/documentation_agent.py +109 -81
  8. crackerjack/agents/dry_agent.py +122 -97
  9. crackerjack/agents/formatting_agent.py +3 -16
  10. crackerjack/agents/import_optimization_agent.py +1174 -130
  11. crackerjack/agents/performance_agent.py +956 -188
  12. crackerjack/agents/performance_helpers.py +229 -0
  13. crackerjack/agents/proactive_agent.py +1 -48
  14. crackerjack/agents/refactoring_agent.py +516 -246
  15. crackerjack/agents/refactoring_helpers.py +282 -0
  16. crackerjack/agents/security_agent.py +393 -90
  17. crackerjack/agents/test_creation_agent.py +1776 -120
  18. crackerjack/agents/test_specialist_agent.py +59 -15
  19. crackerjack/agents/tracker.py +0 -102
  20. crackerjack/api.py +145 -37
  21. crackerjack/cli/handlers.py +48 -30
  22. crackerjack/cli/interactive.py +11 -11
  23. crackerjack/cli/options.py +66 -4
  24. crackerjack/code_cleaner.py +808 -148
  25. crackerjack/config/global_lock_config.py +110 -0
  26. crackerjack/config/hooks.py +43 -64
  27. crackerjack/core/async_workflow_orchestrator.py +247 -97
  28. crackerjack/core/autofix_coordinator.py +192 -109
  29. crackerjack/core/enhanced_container.py +46 -63
  30. crackerjack/core/file_lifecycle.py +549 -0
  31. crackerjack/core/performance.py +9 -8
  32. crackerjack/core/performance_monitor.py +395 -0
  33. crackerjack/core/phase_coordinator.py +281 -94
  34. crackerjack/core/proactive_workflow.py +9 -58
  35. crackerjack/core/resource_manager.py +501 -0
  36. crackerjack/core/service_watchdog.py +490 -0
  37. crackerjack/core/session_coordinator.py +4 -8
  38. crackerjack/core/timeout_manager.py +504 -0
  39. crackerjack/core/websocket_lifecycle.py +475 -0
  40. crackerjack/core/workflow_orchestrator.py +343 -209
  41. crackerjack/dynamic_config.py +50 -9
  42. crackerjack/errors.py +3 -4
  43. crackerjack/executors/async_hook_executor.py +63 -13
  44. crackerjack/executors/cached_hook_executor.py +14 -14
  45. crackerjack/executors/hook_executor.py +100 -37
  46. crackerjack/executors/hook_lock_manager.py +856 -0
  47. crackerjack/executors/individual_hook_executor.py +120 -86
  48. crackerjack/intelligence/__init__.py +0 -7
  49. crackerjack/intelligence/adaptive_learning.py +13 -86
  50. crackerjack/intelligence/agent_orchestrator.py +15 -78
  51. crackerjack/intelligence/agent_registry.py +12 -59
  52. crackerjack/intelligence/agent_selector.py +31 -92
  53. crackerjack/intelligence/integration.py +1 -41
  54. crackerjack/interactive.py +9 -9
  55. crackerjack/managers/async_hook_manager.py +25 -8
  56. crackerjack/managers/hook_manager.py +9 -9
  57. crackerjack/managers/publish_manager.py +57 -59
  58. crackerjack/managers/test_command_builder.py +6 -36
  59. crackerjack/managers/test_executor.py +9 -61
  60. crackerjack/managers/test_manager.py +17 -63
  61. crackerjack/managers/test_manager_backup.py +77 -127
  62. crackerjack/managers/test_progress.py +4 -23
  63. crackerjack/mcp/cache.py +5 -12
  64. crackerjack/mcp/client_runner.py +10 -10
  65. crackerjack/mcp/context.py +64 -6
  66. crackerjack/mcp/dashboard.py +14 -11
  67. crackerjack/mcp/enhanced_progress_monitor.py +55 -55
  68. crackerjack/mcp/file_monitor.py +72 -42
  69. crackerjack/mcp/progress_components.py +103 -84
  70. crackerjack/mcp/progress_monitor.py +122 -49
  71. crackerjack/mcp/rate_limiter.py +12 -12
  72. crackerjack/mcp/server_core.py +16 -22
  73. crackerjack/mcp/service_watchdog.py +26 -26
  74. crackerjack/mcp/state.py +15 -0
  75. crackerjack/mcp/tools/core_tools.py +95 -39
  76. crackerjack/mcp/tools/error_analyzer.py +6 -32
  77. crackerjack/mcp/tools/execution_tools.py +1 -56
  78. crackerjack/mcp/tools/execution_tools_backup.py +35 -131
  79. crackerjack/mcp/tools/intelligence_tool_registry.py +0 -36
  80. crackerjack/mcp/tools/intelligence_tools.py +2 -55
  81. crackerjack/mcp/tools/monitoring_tools.py +308 -145
  82. crackerjack/mcp/tools/proactive_tools.py +12 -42
  83. crackerjack/mcp/tools/progress_tools.py +23 -15
  84. crackerjack/mcp/tools/utility_tools.py +3 -40
  85. crackerjack/mcp/tools/workflow_executor.py +40 -60
  86. crackerjack/mcp/websocket/app.py +0 -3
  87. crackerjack/mcp/websocket/endpoints.py +206 -268
  88. crackerjack/mcp/websocket/jobs.py +213 -66
  89. crackerjack/mcp/websocket/server.py +84 -6
  90. crackerjack/mcp/websocket/websocket_handler.py +137 -29
  91. crackerjack/models/config_adapter.py +3 -16
  92. crackerjack/models/protocols.py +162 -3
  93. crackerjack/models/resource_protocols.py +454 -0
  94. crackerjack/models/task.py +3 -3
  95. crackerjack/monitoring/__init__.py +0 -0
  96. crackerjack/monitoring/ai_agent_watchdog.py +25 -71
  97. crackerjack/monitoring/regression_prevention.py +28 -87
  98. crackerjack/orchestration/advanced_orchestrator.py +44 -78
  99. crackerjack/orchestration/coverage_improvement.py +10 -60
  100. crackerjack/orchestration/execution_strategies.py +16 -16
  101. crackerjack/orchestration/test_progress_streamer.py +61 -53
  102. crackerjack/plugins/base.py +1 -1
  103. crackerjack/plugins/managers.py +22 -20
  104. crackerjack/py313.py +65 -21
  105. crackerjack/services/backup_service.py +467 -0
  106. crackerjack/services/bounded_status_operations.py +627 -0
  107. crackerjack/services/cache.py +7 -9
  108. crackerjack/services/config.py +35 -52
  109. crackerjack/services/config_integrity.py +5 -16
  110. crackerjack/services/config_merge.py +542 -0
  111. crackerjack/services/contextual_ai_assistant.py +17 -19
  112. crackerjack/services/coverage_ratchet.py +44 -73
  113. crackerjack/services/debug.py +25 -39
  114. crackerjack/services/dependency_monitor.py +52 -50
  115. crackerjack/services/enhanced_filesystem.py +14 -11
  116. crackerjack/services/file_hasher.py +1 -1
  117. crackerjack/services/filesystem.py +1 -12
  118. crackerjack/services/git.py +71 -47
  119. crackerjack/services/health_metrics.py +31 -27
  120. crackerjack/services/initialization.py +276 -428
  121. crackerjack/services/input_validator.py +760 -0
  122. crackerjack/services/log_manager.py +16 -16
  123. crackerjack/services/logging.py +7 -6
  124. crackerjack/services/metrics.py +43 -43
  125. crackerjack/services/pattern_cache.py +2 -31
  126. crackerjack/services/pattern_detector.py +26 -63
  127. crackerjack/services/performance_benchmarks.py +20 -45
  128. crackerjack/services/regex_patterns.py +2887 -0
  129. crackerjack/services/regex_utils.py +537 -0
  130. crackerjack/services/secure_path_utils.py +683 -0
  131. crackerjack/services/secure_status_formatter.py +534 -0
  132. crackerjack/services/secure_subprocess.py +605 -0
  133. crackerjack/services/security.py +47 -10
  134. crackerjack/services/security_logger.py +492 -0
  135. crackerjack/services/server_manager.py +109 -50
  136. crackerjack/services/smart_scheduling.py +8 -25
  137. crackerjack/services/status_authentication.py +603 -0
  138. crackerjack/services/status_security_manager.py +442 -0
  139. crackerjack/services/thread_safe_status_collector.py +546 -0
  140. crackerjack/services/tool_version_service.py +1 -23
  141. crackerjack/services/unified_config.py +36 -58
  142. crackerjack/services/validation_rate_limiter.py +269 -0
  143. crackerjack/services/version_checker.py +9 -40
  144. crackerjack/services/websocket_resource_limiter.py +572 -0
  145. crackerjack/slash_commands/__init__.py +52 -2
  146. crackerjack/tools/__init__.py +0 -0
  147. crackerjack/tools/validate_input_validator_patterns.py +262 -0
  148. crackerjack/tools/validate_regex_patterns.py +198 -0
  149. {crackerjack-0.31.10.dist-info → crackerjack-0.31.13.dist-info}/METADATA +197 -12
  150. crackerjack-0.31.13.dist-info/RECORD +178 -0
  151. crackerjack/cli/facade.py +0 -104
  152. crackerjack-0.31.10.dist-info/RECORD +0 -149
  153. {crackerjack-0.31.10.dist-info → crackerjack-0.31.13.dist-info}/WHEEL +0 -0
  154. {crackerjack-0.31.10.dist-info → crackerjack-0.31.13.dist-info}/entry_points.txt +0 -0
  155. {crackerjack-0.31.10.dist-info → crackerjack-0.31.13.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,475 @@
1
+ """WebSocket and network resource lifecycle management.
2
+
3
+ Provides comprehensive cleanup patterns for WebSocket connections,
4
+ HTTP clients, and network resources to prevent resource leaks.
5
+ """
6
+
7
+ import asyncio
8
+ import contextlib
9
+ import logging
10
+ import socket
11
+ import subprocess
12
+ import time
13
+ import typing as t
14
+
15
+ import aiohttp
16
+ import websockets
17
+
18
+ from .resource_manager import ManagedResource, ResourceManager
19
+
20
+
21
+ class ManagedWebSocketConnection(ManagedResource):
22
+ """WebSocket connection with automatic cleanup on error scenarios."""
23
+
24
+ def __init__(
25
+ self,
26
+ websocket: t.Any, # websockets protocols have import issues
27
+ manager: ResourceManager | None = None,
28
+ ) -> None:
29
+ super().__init__(manager)
30
+ self.websocket = websocket
31
+ self.logger = logging.getLogger(__name__)
32
+
33
+ async def cleanup(self) -> None:
34
+ """Clean up WebSocket connection."""
35
+ if not self._closed and not self.websocket.closed:
36
+ self._closed = True
37
+
38
+ try:
39
+ await asyncio.wait_for(self.websocket.close(), timeout=5.0)
40
+ except (TimeoutError, websockets.ConnectionClosed):
41
+ # Expected when connection is already closed or times out
42
+ pass
43
+ except Exception as e:
44
+ self.logger.warning(f"Error closing WebSocket connection: {e}")
45
+
46
+ async def send_safe(self, message: str) -> bool:
47
+ """Send message safely, returning True if successful."""
48
+ if self._closed or self.websocket.closed:
49
+ return False
50
+
51
+ try:
52
+ await self.websocket.send(message)
53
+ return True
54
+ except (websockets.ConnectionClosed, websockets.InvalidState):
55
+ await self.cleanup()
56
+ return False
57
+ except Exception as e:
58
+ self.logger.warning(f"Error sending WebSocket message: {e}")
59
+ await self.cleanup()
60
+ return False
61
+
62
+
63
+ class ManagedHTTPClient(ManagedResource):
64
+ """HTTP client session with automatic cleanup."""
65
+
66
+ def __init__(
67
+ self,
68
+ session: aiohttp.ClientSession,
69
+ manager: ResourceManager | None = None,
70
+ ) -> None:
71
+ super().__init__(manager)
72
+ self.session = session
73
+
74
+ async def cleanup(self) -> None:
75
+ """Clean up HTTP client session."""
76
+ if not self._closed and not self.session.closed:
77
+ self._closed = True
78
+
79
+ try:
80
+ await self.session.close()
81
+ except Exception as e:
82
+ logging.getLogger(__name__).warning(f"Error closing HTTP session: {e}")
83
+
84
+
85
+ class ManagedWebSocketServer(ManagedResource):
86
+ """WebSocket server with comprehensive lifecycle management."""
87
+
88
+ def __init__(
89
+ self,
90
+ port: int,
91
+ host: str = "127.0.0.1",
92
+ manager: ResourceManager | None = None,
93
+ ) -> None:
94
+ super().__init__(manager)
95
+ self.port = port
96
+ self.host = host
97
+ self.server: t.Any | None = None # websockets server type has import issues
98
+ self.connections: set[ManagedWebSocketConnection] = set()
99
+ self.logger = logging.getLogger(__name__)
100
+ self._server_task: asyncio.Task[t.Any] | None = None
101
+
102
+ async def start(
103
+ self,
104
+ handler: t.Callable[[t.Any], t.Awaitable[None]],
105
+ ) -> None:
106
+ """Start the WebSocket server."""
107
+ if self.server:
108
+ return
109
+
110
+ # Wrap handler to manage connections
111
+ async def managed_handler(
112
+ websocket: t.Any, # websocket protocol type
113
+ ) -> None:
114
+ managed_conn = ManagedWebSocketConnection(websocket, self.manager)
115
+ self.connections.add(managed_conn)
116
+
117
+ try:
118
+ await handler(websocket)
119
+ finally:
120
+ self.connections.discard(managed_conn)
121
+ await managed_conn.cleanup()
122
+
123
+ self.server = await websockets.serve(
124
+ managed_handler,
125
+ self.host,
126
+ self.port,
127
+ # Configure timeouts and limits
128
+ ping_interval=20,
129
+ ping_timeout=10,
130
+ close_timeout=10,
131
+ max_size=2**20, # 1MB
132
+ max_queue=32,
133
+ )
134
+
135
+ self.logger.info(f"WebSocket server started on {self.host}:{self.port}")
136
+
137
+ async def cleanup(self) -> None:
138
+ """Clean up WebSocket server and all connections."""
139
+ if self._closed:
140
+ return
141
+ self._closed = True
142
+
143
+ # Close all active connections
144
+ if self.connections:
145
+ close_tasks = [
146
+ asyncio.create_task(conn.cleanup()) for conn in list(self.connections)
147
+ ]
148
+
149
+ if close_tasks:
150
+ await asyncio.gather(*close_tasks, return_exceptions=True)
151
+
152
+ self.connections.clear()
153
+
154
+ # Close server
155
+ if self.server:
156
+ try:
157
+ self.server.close()
158
+ await self.server.wait_closed()
159
+ self.logger.info("WebSocket server closed successfully")
160
+ except Exception as e:
161
+ self.logger.warning(f"Error closing WebSocket server: {e}")
162
+ finally:
163
+ self.server = None
164
+
165
+ def get_connection_count(self) -> int:
166
+ """Get the number of active connections."""
167
+ return len([conn for conn in self.connections if not conn._closed])
168
+
169
+
170
+ class ManagedSubprocess(ManagedResource):
171
+ """Subprocess with enhanced lifecycle management and resource cleanup."""
172
+
173
+ def __init__(
174
+ self,
175
+ process: subprocess.Popen[bytes],
176
+ timeout: float = 30.0,
177
+ manager: ResourceManager | None = None,
178
+ ) -> None:
179
+ super().__init__(manager)
180
+ self.process = process
181
+ self.timeout = timeout
182
+ self.logger = logging.getLogger(__name__)
183
+ self._monitor_task: asyncio.Task[t.Any] | None = None
184
+
185
+ async def start_monitoring(self) -> None:
186
+ """Start monitoring the process for unexpected termination."""
187
+ if self._monitor_task:
188
+ return
189
+
190
+ self._monitor_task = asyncio.create_task(self._monitor_process())
191
+
192
+ async def _monitor_process(self) -> None:
193
+ """Monitor process health and log unexpected terminations."""
194
+ try:
195
+ while self.process.poll() is None:
196
+ await asyncio.sleep(5.0)
197
+
198
+ # Process has terminated
199
+ return_code = self.process.returncode
200
+ if return_code != 0:
201
+ self.logger.warning(
202
+ f"Process {self.process.pid} terminated with code {return_code}"
203
+ )
204
+ except asyncio.CancelledError:
205
+ pass
206
+ except Exception as e:
207
+ self.logger.warning(f"Error monitoring process: {e}")
208
+
209
+ async def cleanup(self) -> None:
210
+ """Clean up subprocess with graceful termination."""
211
+ if self._closed:
212
+ return
213
+ self._closed = True
214
+
215
+ # Cancel monitoring
216
+ if self._monitor_task and not self._monitor_task.done():
217
+ self._monitor_task.cancel()
218
+ try:
219
+ await self._monitor_task
220
+ except asyncio.CancelledError:
221
+ pass
222
+
223
+ # Clean up process
224
+ if self.process.poll() is None:
225
+ try:
226
+ # Try graceful termination first
227
+ self.process.terminate()
228
+
229
+ try:
230
+ self.process.wait(timeout=5.0)
231
+ self.logger.debug(
232
+ f"Process {self.process.pid} terminated gracefully"
233
+ )
234
+ except subprocess.TimeoutExpired:
235
+ # Force kill if graceful termination fails
236
+ self.process.kill()
237
+ try:
238
+ self.process.wait(timeout=2.0)
239
+ self.logger.warning(f"Process {self.process.pid} force killed")
240
+ except subprocess.TimeoutExpired:
241
+ self.logger.error(
242
+ f"Process {self.process.pid} did not terminate after force kill"
243
+ )
244
+
245
+ except ProcessLookupError:
246
+ # Process already terminated
247
+ pass
248
+ except Exception as e:
249
+ self.logger.warning(
250
+ f"Error cleaning up process {self.process.pid}: {e}"
251
+ )
252
+
253
+ def is_running(self) -> bool:
254
+ """Check if the process is still running."""
255
+ return not self._closed and self.process.poll() is None
256
+
257
+
258
+ class NetworkResourceManager:
259
+ """Manager for network-related resources with health monitoring."""
260
+
261
+ def __init__(self) -> None:
262
+ self.resource_manager = ResourceManager()
263
+ self.logger = logging.getLogger(__name__)
264
+
265
+ async def create_websocket_server(
266
+ self,
267
+ port: int,
268
+ host: str = "127.0.0.1",
269
+ ) -> ManagedWebSocketServer:
270
+ """Create a managed WebSocket server."""
271
+ server = ManagedWebSocketServer(port, host, self.resource_manager)
272
+ return server
273
+
274
+ async def create_http_client(
275
+ self,
276
+ timeout: aiohttp.ClientTimeout | None = None,
277
+ **kwargs: t.Any,
278
+ ) -> ManagedHTTPClient:
279
+ """Create a managed HTTP client session."""
280
+ timeout = timeout or aiohttp.ClientTimeout(total=30.0)
281
+ session = aiohttp.ClientSession(timeout=timeout, **kwargs)
282
+ return ManagedHTTPClient(session, self.resource_manager)
283
+
284
+ def create_subprocess(
285
+ self,
286
+ process: subprocess.Popen[bytes],
287
+ timeout: float = 30.0,
288
+ ) -> ManagedSubprocess:
289
+ """Create a managed subprocess."""
290
+ return ManagedSubprocess(process, timeout, self.resource_manager)
291
+
292
+ async def check_port_available(self, port: int, host: str = "127.0.0.1") -> bool:
293
+ """Check if a port is available for binding."""
294
+ try:
295
+ with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
296
+ sock.settimeout(1.0)
297
+ result = sock.connect_ex((host, port))
298
+ return result != 0 # Port is available if connection fails
299
+ except Exception:
300
+ return False
301
+
302
+ async def wait_for_port(
303
+ self,
304
+ port: int,
305
+ host: str = "127.0.0.1",
306
+ timeout: float = 30.0,
307
+ poll_interval: float = 0.5,
308
+ ) -> bool:
309
+ """Wait for a port to become available (service to start)."""
310
+ start_time = time.time()
311
+
312
+ while time.time() - start_time < timeout:
313
+ with contextlib.suppress(Exception):
314
+ with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
315
+ sock.settimeout(1.0)
316
+ result = sock.connect_ex((host, port))
317
+ if result == 0: # Connection successful
318
+ return True
319
+
320
+ await asyncio.sleep(poll_interval)
321
+
322
+ return False
323
+
324
+ async def cleanup_all(self) -> None:
325
+ """Clean up all managed network resources."""
326
+ await self.resource_manager.cleanup_all()
327
+
328
+ async def __aenter__(self) -> "NetworkResourceManager":
329
+ return self
330
+
331
+ async def __aexit__(
332
+ self,
333
+ exc_type: type[BaseException] | None,
334
+ exc_val: BaseException | None,
335
+ exc_tb: type[BaseException] | None,
336
+ ) -> None:
337
+ await self.cleanup_all()
338
+
339
+
340
+ # Context manager helpers for common network resource patterns
341
+ @contextlib.asynccontextmanager
342
+ async def with_websocket_server(
343
+ port: int,
344
+ handler: t.Callable[[t.Any], t.Awaitable[None]],
345
+ host: str = "127.0.0.1",
346
+ ):
347
+ """Context manager for a WebSocket server with automatic cleanup."""
348
+ async with NetworkResourceManager() as manager:
349
+ server = await manager.create_websocket_server(port, host)
350
+ try:
351
+ await server.start(handler)
352
+ yield server
353
+ finally:
354
+ await server.cleanup()
355
+
356
+
357
+ @contextlib.asynccontextmanager
358
+ async def with_http_client(**kwargs: t.Any):
359
+ """Context manager for an HTTP client with automatic cleanup."""
360
+ async with NetworkResourceManager() as manager:
361
+ client = await manager.create_http_client(**kwargs)
362
+ try:
363
+ yield client.session
364
+ finally:
365
+ await client.cleanup()
366
+
367
+
368
+ @contextlib.asynccontextmanager
369
+ async def with_managed_subprocess(
370
+ command: list[str],
371
+ timeout: float = 30.0,
372
+ **popen_kwargs: t.Any,
373
+ ):
374
+ """Context manager for a subprocess with automatic cleanup."""
375
+ async with NetworkResourceManager() as manager:
376
+ # Ensure we get bytes output for consistent type handling
377
+ process = subprocess.Popen[bytes](command, text=False, **popen_kwargs)
378
+ managed_proc = manager.create_subprocess(process, timeout)
379
+ try:
380
+ await managed_proc.start_monitoring()
381
+ yield managed_proc
382
+ finally:
383
+ await managed_proc.cleanup()
384
+
385
+
386
+ class WebSocketHealthMonitor:
387
+ """Health monitor for WebSocket connections and servers."""
388
+
389
+ def __init__(self, check_interval: float = 30.0) -> None:
390
+ self.check_interval = check_interval
391
+ self.monitored_servers: list[ManagedWebSocketServer] = []
392
+ self.logger = logging.getLogger(__name__)
393
+ self._monitor_task: asyncio.Task[t.Any] | None = None
394
+
395
+ def add_server(self, server: ManagedWebSocketServer) -> None:
396
+ """Add a server to health monitoring."""
397
+ self.monitored_servers.append(server)
398
+
399
+ def remove_server(self, server: ManagedWebSocketServer) -> None:
400
+ """Remove a server from health monitoring."""
401
+ if server in self.monitored_servers:
402
+ self.monitored_servers.remove(server)
403
+
404
+ async def start_monitoring(self) -> None:
405
+ """Start health monitoring."""
406
+ if self._monitor_task:
407
+ return
408
+
409
+ self._monitor_task = asyncio.create_task(self._monitor_loop())
410
+
411
+ async def stop_monitoring(self) -> None:
412
+ """Stop health monitoring."""
413
+ if self._monitor_task and not self._monitor_task.done():
414
+ self._monitor_task.cancel()
415
+ try:
416
+ await self._monitor_task
417
+ except asyncio.CancelledError:
418
+ pass
419
+
420
+ async def _monitor_loop(self) -> None:
421
+ """Main monitoring loop."""
422
+ try:
423
+ while True:
424
+ for server in self.monitored_servers.copy():
425
+ try:
426
+ await self._check_server_health(server)
427
+ except Exception as e:
428
+ self.logger.warning(f"Health check failed for server: {e}")
429
+
430
+ await asyncio.sleep(self.check_interval)
431
+
432
+ except asyncio.CancelledError:
433
+ pass
434
+
435
+ async def _check_server_health(self, server: ManagedWebSocketServer) -> None:
436
+ """Check health of a specific server."""
437
+ if server._closed:
438
+ self.remove_server(server)
439
+ return
440
+
441
+ connection_count = server.get_connection_count()
442
+
443
+ # Log health metrics
444
+ self.logger.debug(
445
+ f"Server {server.host}:{server.port} - "
446
+ f"Active connections: {connection_count}"
447
+ )
448
+
449
+ # Could add more sophisticated health checks here:
450
+ # - Memory usage monitoring
451
+ # - Connection rate limiting
452
+ # - Error rate monitoring
453
+ # - Automatic restart on failure
454
+
455
+
456
+ # Global network resource cleanup
457
+ _global_network_managers: list[NetworkResourceManager] = []
458
+
459
+
460
+ def register_network_manager(manager: NetworkResourceManager) -> None:
461
+ """Register a network resource manager for global cleanup."""
462
+ _global_network_managers.append(manager)
463
+
464
+
465
+ async def cleanup_all_network_resources() -> None:
466
+ """Clean up all globally registered network resource managers."""
467
+ cleanup_tasks = [
468
+ asyncio.create_task(manager.cleanup_all())
469
+ for manager in _global_network_managers
470
+ ]
471
+
472
+ if cleanup_tasks:
473
+ await asyncio.gather(*cleanup_tasks, return_exceptions=True)
474
+
475
+ _global_network_managers.clear()