async def process_callbacks(self, message: WebsocketMessage) -> None: if self.callbacks is not None: tasks = [] for cb in self.callbacks: # If message contains a websocket, then the websocket handle will be passed to the callbacks. # This is useful for duplex websockets if message.websocket is not None: tasks.append( async_create_task( cb(message.message, message.websocket))) else: tasks.append(async_create_task(cb(message.message))) await asyncio.gather(*tasks)
async def start_websockets(self, websocket_start_time_interval_ms: int = 0) -> None: if len(self.subscription_sets) < 1: raise CryptoXLibException("ERROR: There are no subscriptions to be started.") tasks = [] startup_delay_ms = 0 for id, subscription_set in self.subscription_sets.items(): subscription_set.websocket_mgr = self._get_websocket_mgr(subscription_set.subscriptions, startup_delay_ms, self.ssl_context) tasks.append(async_create_task( subscription_set.websocket_mgr.run()) ) startup_delay_ms += websocket_start_time_interval_ms done, pending = await asyncio.wait(tasks, return_when = asyncio.FIRST_EXCEPTION) for task in done: try: task.result() except Exception as e: LOG.error(f"Unrecoverable exception occurred while processing messages: {e}") LOG.info(f"Remaining websocket managers scheduled for shutdown.") await self.shutdown_websockets() if len(pending) > 0: await asyncio.wait(pending, return_when = asyncio.ALL_COMPLETED) LOG.info("All websocket managers shut down.") raise
async def run(self) -> None: await self.validate_subscriptions() await self.initialize_subscriptions() try: # main loop ensuring proper reconnection if required while True: LOG.debug(f"Initiating websocket connection.") websocket = None try: # sleep for the requested period before initiating the connection. This is useful when client # opens many connections at the same time and server cannot handle the load await asyncio.sleep(self.startup_delay_ms / 1000.0) LOG.debug( f"Websocket initiation delayed by {self.startup_delay_ms}ms." ) websocket = self.get_websocket() await websocket.connect() done, pending = await asyncio.wait( [ async_create_task(self.main_loop(websocket)), async_create_task(self.periodic_loop(websocket)) ], return_when=asyncio.FIRST_EXCEPTION) for task in done: try: task.result() except Exception: LOG.debug( "Websocket processing has led to an exception, all pending tasks will be cancelled." ) for task in pending: if not task.cancelled(): task.cancel() if len(pending) > 0: try: await asyncio.wait( pending, return_when=asyncio.ALL_COMPLETED) except asyncio.CancelledError: await asyncio.wait( pending, return_when=asyncio.ALL_COMPLETED) raise finally: LOG.debug( "All pending tasks cancelled successfully." ) raise except (websockets.ConnectionClosedError, websockets.ConnectionClosedOK, websockets.InvalidStatusCode, ConnectionResetError, WebsocketClosed, WebsocketError, WebsocketReconnectionException) as e: if self.auto_reconnect: LOG.info( "A recoverable exception has occurred, the websocket will be restarted automatically." ) self._print_subscriptions() LOG.info(f"Exception: {e}") else: raise finally: if websocket is not None: LOG.debug("Closing websocket connection.") await websocket.close() except asyncio.CancelledError: LOG.warning(f"The websocket was requested to be shutdown.") except Exception: LOG.error( f"An exception occurred. The websocket manager will be closed." ) self._print_subscriptions() raise