How in BinanceSocketManager to handle the connection break correctly?

Question:

Good time of the day, tell me I can’t beat the next disease in any way. I receive the found ohlcvs over the WS channel in real time. From time to time, my Internet connection disappears, sometimes when WS is connected for too long, Binance itself breaks the connection. I want to make sure that in case of disconnection, my program tries to connect again. To do this, I made a Try except block – if something bad happens in it, I just try to call the connect function again. It seems to sound logical and should work. However, during the test I get an error

Max reconnections 5 reached: CANCEL read_loop

At first, this error looks like I managed to catch it, but then the call stack follows and the program crashes

Traceback (most recent call last):
  File "/home/master/PycharmProjects/PROJECT/venv/lib/python3.10/site-packages/aiohttp/connector.py", line 1155, in _create_direct_connection
    hosts = await asyncio.shield(host_resolved)
  File "/home/master/PycharmProjects/PROJECT/venv/lib/python3.10/site-packages/aiohttp/connector.py", line 874, in _resolve_host
    addrs = await self._resolver.resolve(host, port, family=self._family)
  File "/home/master/PycharmProjects/PROJECT/venv/lib/python3.10/site-packages/aiohttp/resolver.py", line 33, in resolve
    infos = await self._loop.getaddrinfo(
  File "/usr/lib/python3.10/asyncio/base_events.py", line 863, in getaddrinfo
    return await self.run_in_executor(
  File "/usr/lib/python3.10/concurrent/futures/thread.py", line 58, in run
    result = self.fn(*self.args, **self.kwargs)
  File "/usr/lib/python3.10/socket.py", line 955, in getaddrinfo
    for res in _socket.getaddrinfo(host, port, family, type, proto, flags):
socket.gaierror: [Errno -3] Temporary failure in name resolution

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "/home/master/PycharmProjects/PROJECT/main.py", line 43, in <module>
    loop.run_until_complete(main())
  File "/usr/lib/python3.10/asyncio/base_events.py", line 649, in run_until_complete
    return future.result()
  File "/home/master/PycharmProjects/PROJECT/main.py", line 35, in main
    await asyncio.gather(stream.live_kline(), interrupt_sys.listen(), display(memory))
  File "/home/master/PycharmProjects/PROJECT/kernel/market/stream.py", line 45, in live_kline
    await self.connect()
  File "/home/master/PycharmProjects/PROJECT/kernel/market/stream.py", line 21, in connect
    self.__client = await AsyncClient.create(self.config.system_api_public_key,
  File "/home/master/PycharmProjects/PROJECT/venv/lib/python3.10/site-packages/binance/client.py", line 7866, in create
    await self.ping()
  File "/home/master/PycharmProjects/PROJECT/venv/lib/python3.10/site-packages/binance/client.py", line 7988, in ping
    return await self._get('ping', version=self.PRIVATE_API_VERSION)
  File "/home/master/PycharmProjects/PROJECT/venv/lib/python3.10/site-packages/binance/client.py", line 7953, in _get
    return await self._request_api('get', path, signed, version, **kwargs)
  File "/home/master/PycharmProjects/PROJECT/venv/lib/python3.10/site-packages/binance/client.py", line 7916, in _request_api
    return await self._request(method, uri, signed, **kwargs)
  File "/home/master/PycharmProjects/PROJECT/venv/lib/python3.10/site-packages/binance/client.py", line 7897, in _request
    async with getattr(self.session, method)(uri, **kwargs) as response:
  File "/home/master/PycharmProjects/PROJECT/venv/lib/python3.10/site-packages/aiohttp/client.py", line 1141, in __aenter__
    self._resp = await self._coro
  File "/home/master/PycharmProjects/PROJECT/venv/lib/python3.10/site-packages/aiohttp/client.py", line 536, in _request
    conn = await self._connector.connect(
  File "/home/master/PycharmProjects/PROJECT/venv/lib/python3.10/site-packages/aiohttp/connector.py", line 540, in connect
    proto = await self._create_connection(req, traces, timeout)
  File "/home/master/PycharmProjects/PROJECT/venv/lib/python3.10/site-packages/aiohttp/connector.py", line 901, in _create_connection
    _, proto = await self._create_direct_connection(req, traces, timeout)
  File "/home/master/PycharmProjects/PROJECT/venv/lib/python3.10/site-packages/aiohttp/connector.py", line 1169, in _create_direct_connection
    raise ClientConnectorError(req.connection_key, exc) from exc
aiohttp.client_exceptions.ClientConnectorError: Cannot connect to host api.binance.com:443 ssl:default [Temporary failure in name resolution]

Process finished with exit code 1

Tell me what’s the matter – it feels like "try except" just doesn’t work. Or is there something going on that I don’t understand

How do I test? I just launch the application and then turn off the network card for more than 1 minute to simulate a connection break.

import asyncio
from binance import Client, AsyncClient, ThreadedWebsocketManager, ThreadedDepthCacheManager, BinanceSocketManager


class Stream:

    def __init__(self):
        self.__ts = None
        self.__chanel_name = None
        self.__bsm = None
        self.__client = None

    async def connect(self):
        self.__client = await AsyncClient.create('','')
        self.__bsm = BinanceSocketManager(self.__client, 60)
        self.__chanel_name = 'btcusdt@kline_1m'
        self.__ts = self.__bsm.futures_multiplex_socket([self.__chanel_name])

    async def live_kline(self):
        while True:
            try:
                async with self.__ts as tscm:
                    while True:
                        stream = await tscm.recv()
                        # Updates order statuses in real time
                        if stream['stream'] == self.__chanel_name:
                            Stream.process_message(stream)
            except Exception as e:
                print(f"An error occurred: {e}. Reconnecting...")
                await self.__client.close_connection()
                await asyncio.sleep(1)
                await self.connect()
                continue

    @staticmethod
    def process_message(msg: dict):

        pass

async def main():
    stream = Stream()
    await stream.connect()
    await asyncio.gather(stream.live_kline())
    loop.stop()
    
if __name__ == '__main__':
    loop = asyncio.new_event_loop()
    loop.run_until_complete(main())
Asked By: stas stas

||

Answers:

"Max reconnections 5 reached: CANCEL read_loop," the error you’re seeing, is reported by the binance library when it hits the maximum number of reconnection attempts, which is set to 5 by default. Even if you’re using a try-except block to detect problems and attempt reconnection, it looks that the library is still reaching its internal limit. You may avoid this constraint and elegantly manage reconnections by implementing a custom reconnection mechanism and handling the reconnection count yourself. Here’s a revised version of your code that fixes the problem:

import asyncio
from binance import AsyncClient, BinanceSocketManager

class Stream:

    def __init__(self):
        self.__ts = None
        self.__chanel_name = None
        self.__bsm = None
        self.__client = None
        self.__reconnection_attempts = 0

    async def connect(self):
        self.__client = await AsyncClient.create('your_api_key', 'your_api_secret')
        self.__bsm = BinanceSocketManager(self.__client, 60)
        self.__chanel_name = 'btcusdt@kline_1m'
        self.__ts = self.__bsm.futures_multiplex_socket([self.__chanel_name])

    async def live_kline(self):
        while True:
            try:
                async with self.__ts as tscm:
                    while True:
                        stream = await tscm.recv()
                        # Updates order statuses in real time
                        if stream['stream'] == self.__chanel_name:
                            Stream.process_message(stream)
                            self.__reconnection_attempts = 0  # Reset reconnection attempts on successful connection
            except Exception as e:
                print(f"An error occurred: {e}. Reconnecting...")
                await self.__client.close_connection()
                await asyncio.sleep(1)
                await self.connect()
                self.__reconnection_attempts += 1
                if self.__reconnection_attempts >= 5:
                    print("Max reconnection attempts reached. Exiting.")
                    break

    @staticmethod
    def process_message(msg: dict):
        pass

async def main():
    stream = Stream()
    await stream.connect()
    await asyncio.gather(stream.live_kline())

if __name__ == '__main__':
    loop = asyncio.new_event_loop()
    loop.run_until_complete(main())

To keep track of reconnection attempts, I’ve added a variable called self.__reconnection_attempts. If the limit number of reconnecting tries (5 in this example) is reached after each reconnection attempt, the software will display a notification and quit the loop, stopping unending reconnection attempts. This customized reconnection approach should allow your software to smoothly manage disconnections while adhering to the maximum reconnection restriction.

Answered By: iowzfe