- Deleted `app_new.py`, which was previously the main entry point for the dashboard application, to streamline the codebase. - Consolidated the application initialization and callback registration logic into `main.py`, enhancing modularity and maintainability. - Updated the logging and error handling practices in `main.py` to ensure consistent application behavior and improved debugging capabilities. These changes simplify the application structure, aligning with project standards for modularity and maintainability.
149 lines
6.3 KiB
Python
149 lines
6.3 KiB
Python
"""
|
|
Module for managing network connection and reconnection logic for data collectors.
|
|
|
|
This module encapsulates the complexities of connecting, disconnecting,
|
|
and handling reconnection attempts to a data source, promoting a clean
|
|
separation of concerns within the data collector architecture.
|
|
"""
|
|
|
|
import asyncio
|
|
from typing import List, Any
|
|
from datetime import datetime
|
|
|
|
# from ..base_collector import DataType # Import from base_collector for now, will refactor later
|
|
from .collector_state_telemetry import CollectorStatus, CollectorStateAndTelemetry
|
|
from data.common.data_types import DataType
|
|
|
|
|
|
class ConnectionManager:
|
|
"""
|
|
Manages the connection, disconnection, and reconnection logic for a data collector.
|
|
"""
|
|
|
|
def __init__(self,
|
|
exchange_name: str,
|
|
component_name: str,
|
|
max_reconnect_attempts: int = 5,
|
|
reconnect_delay: float = 5.0,
|
|
logger=None,
|
|
state_telemetry: CollectorStateAndTelemetry = None):
|
|
|
|
self.exchange_name = exchange_name
|
|
self.component_name = component_name
|
|
self._max_reconnect_attempts = max_reconnect_attempts
|
|
self._reconnect_delay = reconnect_delay
|
|
self.logger = logger
|
|
self._state_telemetry = state_telemetry
|
|
|
|
self._connection = None # Placeholder for the actual connection object
|
|
self._reconnect_attempts = 0
|
|
|
|
def _log_debug(self, message: str) -> None:
|
|
if self._state_telemetry:
|
|
self._state_telemetry._log_debug(f"{self.component_name}: {message}")
|
|
elif self.logger:
|
|
self.logger.debug(f"{self.component_name}: {message}")
|
|
|
|
def _log_info(self, message: str) -> None:
|
|
if self._state_telemetry:
|
|
self._state_telemetry._log_info(f"{self.component_name}: {message}")
|
|
elif self.logger:
|
|
self.logger.info(f"{self.component_name}: {message}")
|
|
|
|
def _log_warning(self, message: str) -> None:
|
|
if self._state_telemetry:
|
|
self._state_telemetry._log_warning(f"{self.component_name}: {message}")
|
|
elif self.logger:
|
|
self.logger.warning(f"{self.component_name}: {message}")
|
|
|
|
def _log_error(self, message: str, exc_info: bool = False) -> None:
|
|
if self._state_telemetry:
|
|
self._state_telemetry._log_error(f"{self.component_name}: {message}", exc_info=exc_info)
|
|
elif self.logger:
|
|
self.logger.error(f"{self.component_name}: {message}", exc_info=exc_info)
|
|
|
|
async def connect(self, connect_logic: callable) -> bool:
|
|
"""
|
|
Establish connection to the data source using provided logic.
|
|
|
|
Args:
|
|
connect_logic: A callable (async function) that performs the actual connection.
|
|
|
|
Returns:
|
|
True if connection successful, False otherwise
|
|
"""
|
|
self._log_info(f"Connecting to {self.exchange_name} data source")
|
|
try:
|
|
success = await connect_logic()
|
|
if success:
|
|
self._connection = True # Indicate connection is established
|
|
self._state_telemetry.set_connection_uptime_start()
|
|
self._log_info(f"Successfully connected to {self.exchange_name}")
|
|
return True
|
|
else:
|
|
self._log_error(f"Failed to connect to {self.exchange_name}")
|
|
return False
|
|
except Exception as e:
|
|
self._log_error(f"Error during connection to {self.exchange_name}: {e}", exc_info=True)
|
|
return False
|
|
|
|
async def disconnect(self, disconnect_logic: callable) -> None:
|
|
"""
|
|
Disconnect from the data source using provided logic.
|
|
|
|
Args:
|
|
disconnect_logic: A callable (async function) that performs the actual disconnection.
|
|
"""
|
|
self._log_info(f"Disconnecting from {self.exchange_name} data source")
|
|
try:
|
|
if self._connection:
|
|
await disconnect_logic()
|
|
self._connection = None
|
|
self._log_info(f"Disconnected from {self.exchange_name}")
|
|
except Exception as e:
|
|
self._log_error(f"Error during disconnection from {self.exchange_name}: {e}", exc_info=True)
|
|
|
|
async def handle_connection_error(self, connect_logic: callable, subscribe_logic: callable, symbols: List[str], data_types: List[DataType]) -> bool:
|
|
"""
|
|
Handle connection errors and attempt reconnection.
|
|
|
|
Args:
|
|
connect_logic: Callable for connecting.
|
|
subscribe_logic: Callable for subscribing.
|
|
symbols: List of symbols to re-subscribe to.
|
|
data_types: List of data types to re-subscribe to.
|
|
|
|
Returns:
|
|
True if reconnection successful, False if max attempts exceeded
|
|
"""
|
|
self._reconnect_attempts += 1
|
|
|
|
if self._reconnect_attempts > self._max_reconnect_attempts:
|
|
self._log_error(f"Max reconnection attempts ({self._max_reconnect_attempts}) exceeded for {self.exchange_name}")
|
|
if self._state_telemetry:
|
|
self._state_telemetry.update_status(CollectorStatus.ERROR)
|
|
self._state_telemetry.set_should_be_running(False)
|
|
return False
|
|
|
|
if self._state_telemetry:
|
|
self._state_telemetry.update_status(CollectorStatus.RECONNECTING)
|
|
self._log_warning(f"Connection lost. Attempting reconnection {self._reconnect_attempts}/{self._max_reconnect_attempts} for {self.exchange_name}")
|
|
|
|
# Disconnect and wait before retrying
|
|
await self.disconnect(lambda: None) # Pass a no-op disconnect for internal use, actual disconnect handled by caller
|
|
await asyncio.sleep(self._reconnect_delay)
|
|
|
|
# Attempt to reconnect
|
|
try:
|
|
if await self.connect(connect_logic):
|
|
if await subscribe_logic(symbols, data_types):
|
|
self._log_info(f"Reconnection successful for {self.exchange_name}")
|
|
if self._state_telemetry:
|
|
self._state_telemetry.update_status(CollectorStatus.RUNNING)
|
|
self._reconnect_attempts = 0
|
|
return True
|
|
|
|
except Exception as e:
|
|
self._log_error(f"Reconnection attempt failed for {self.exchange_name}: {e}", exc_info=True)
|
|
|
|
return False |