BitMEX / api-connectors

Libraries for connecting to the BitMEX API.
https://www.bitmex.com/app/restAPI
909 stars 797 forks source link

How reconnect websocket ["Connection is already closed."] #355

Open btcyeahboy opened 5 years ago

btcyeahboy commented 5 years ago

When "Connection is already closed.", how can I reconnect bitmex websocket?

verata-veritatis commented 5 years ago

Yeah I've been getting this a lot and I've tried various methods of exceptions but they all don't work.

verata-veritatis commented 4 years ago

I believe the issue may be that __on_error is attempting to call the ws logger, but the connection is already closed so it raises an unhandled message. I have changed the __on_error function to always raise an error instead. I'll report back with results if the error comes up again.

Default __on_error functionality:

    def __on_error(self, error):
        '''Called on fatal websocket errors. We exit on these.'''
        if not self.exited:
            self.logger.error("Error : %s" % error)
            raise websocket.WebSocketException(error)

Uncaught error encountered:

error from callback <bound method BitMEXWebsocket.__on_error of <bitmex_websocket.BitMEXWebsocket object at [REDACTED]>>: Connection is already closed.

Proposed __on_error functionality:

    def __on_error(self, ws, error):
        '''Called on fatal websocket errors. We exit on these.'''
        raise websocket.WebSocketConnectionClosedException(error)
R2FREE commented 4 years ago

I believe the issue may be that __on_error is attempting to call the ws logger, but the connection is already closed so it raises an unhandled message. I have changed the __on_error function to always raise an error instead. I'll report back with results if the error comes up again.

Default __on_error functionality:

    def __on_error(self, error):
        '''Called on fatal websocket errors. We exit on these.'''
        if not self.exited:
            self.logger.error("Error : %s" % error)
            raise websocket.WebSocketException(error)

Uncaught error encountered:

error from callback <bound method BitMEXWebsocket.__on_error of <bitmex_websocket.BitMEXWebsocket object at [REDACTED]>>: Connection is already closed.

Proposed __on_error functionality:

    def __on_error(self, ws, error):
        '''Called on fatal websocket errors. We exit on these.'''
        raise websocket.WebSocketConnectionClosedException(error)

I will try it. My man. Thanks anyway!

verata-veritatis commented 4 years ago

I believe the issue may be that __on_error is attempting to call the ws logger, but the connection is already closed so it raises an unhandled message. I have changed the __on_error function to always raise an error instead. I'll report back with results if the error comes up again. Default __on_error functionality:

    def __on_error(self, error):
        '''Called on fatal websocket errors. We exit on these.'''
        if not self.exited:
            self.logger.error("Error : %s" % error)
            raise websocket.WebSocketException(error)

Uncaught error encountered:

error from callback <bound method BitMEXWebsocket.__on_error of <bitmex_websocket.BitMEXWebsocket object at [REDACTED]>>: Connection is already closed.

Proposed __on_error functionality:

    def __on_error(self, ws, error):
        '''Called on fatal websocket errors. We exit on these.'''
        raise websocket.WebSocketConnectionClosedException(error)

I will try it. My man. Thanks anyway!

You can forget what I said; it didn't work haha. I'm debating if I should just reinitialize the whole class on error by calling self.__init__(), forcing the script to essentially restart the websocket connection.

R2FREE commented 4 years ago

I believe the issue may be that __on_error is attempting to call the ws logger, but the connection is already closed so it raises an unhandled message. I have changed the __on_error function to always raise an error instead. I'll report back with results if the error comes up again. Default __on_error functionality:

    def __on_error(self, error):
        '''Called on fatal websocket errors. We exit on these.'''
        if not self.exited:
            self.logger.error("Error : %s" % error)
            raise websocket.WebSocketException(error)

Uncaught error encountered:

error from callback <bound method BitMEXWebsocket.__on_error of <bitmex_websocket.BitMEXWebsocket object at [REDACTED]>>: Connection is already closed.

Proposed __on_error functionality:

    def __on_error(self, ws, error):
        '''Called on fatal websocket errors. We exit on these.'''
        raise websocket.WebSocketConnectionClosedException(error)

I will try it. My man. Thanks anyway!

You can forget what I said; it didn't work haha. I'm debating if I should just reinitialize the whole class on error by calling self.__init__(), forcing the script to essentially restart the websocket connection.

You and I think alike. I'm trying to do this, Man. LOL

verata-veritatis commented 4 years ago

I believe the issue may be that __on_error is attempting to call the ws logger, but the connection is already closed so it raises an unhandled message. I have changed the __on_error function to always raise an error instead. I'll report back with results if the error comes up again. Default __on_error functionality:

    def __on_error(self, error):
        '''Called on fatal websocket errors. We exit on these.'''
        if not self.exited:
            self.logger.error("Error : %s" % error)
            raise websocket.WebSocketException(error)

Uncaught error encountered:

error from callback <bound method BitMEXWebsocket.__on_error of <bitmex_websocket.BitMEXWebsocket object at [REDACTED]>>: Connection is already closed.

Proposed __on_error functionality:

    def __on_error(self, ws, error):
        '''Called on fatal websocket errors. We exit on these.'''
        raise websocket.WebSocketConnectionClosedException(error)

I will try it. My man. Thanks anyway!

You can forget what I said; it didn't work haha. I'm debating if I should just reinitialize the whole class on error by calling self.__init__(), forcing the script to essentially restart the websocket connection.

You and I think alike. I'm trying to do this, Man. LOL

It works! Forcing initialization is really a roundabout band-aid and somewhat of a no-no, but it works for this situation.

def __on_error(self, ws, error):
    '''Called on fatal websocket errors. Attempts to reconnect.'''
    print("\nFATAL WEBSOCKET ERROR. ATTEMPTING TO RECONNECT...\n")
    sleep(1)
    self.__init__(self.endpoint, self.symbol, self.api_key, self.api_secret)

Remember that websocket stream is asynchronous, so if you have code that constantly fetches data, you'll need to handle a disconnect/reconnect. Most likely, you'll end up getting a KeyError if ws data can't be fetched while it's trying to reconnect. So make sure you handle this with something like a while loop, breaking on successful data retrieval and continuing on except KeyError::

def get_last_price():
    while True:
        try:
            return ws.get_instrument()['lastPrice']
        except KeyError:
            print("WARNING: GETLASTPRICE IS AWAITING WS.")
            sleep(0.5)
            continue
R2FREE commented 4 years ago

Dude. Thanks. I think my main issue is my V2Ray, I am wondering if I run the code on AWS,will this issue still happening

verata-veritatis commented 4 years ago

Dude. Thanks. I think my main issue is my V2Ray, I am wondering if I run the code on AWS,will this issue still happening

Probably. It made no difference running it locally, on DigitalOcean, or Azure Cloud; I still get the same occasional error.

R2FREE commented 4 years ago

Probably. It made no difference running it locally, on DigitalOcean, or Azure Cloud; I still get the same occasional error.

Thanks for your sharing! I used plan to rest the whole process when the WS closed error occur , LOL. But your method isn't work for me, I tried disconnect the internet, and then reconnet it, but after Connected to WS, the Websocket Closed , I cann't figure out how this happen.(っ °Д °;)っ

R2FREE commented 4 years ago

like this

 Connecting to wss://www.bitmex.com/realtime?subscribe=trade:XBTUSD
 Not authenticating.
 Couldn't connect to WS! Exiting.
 Websocket Closed
 error from callback <bound method BitMEXWebsocket.__on_error of <bitmex_websocket.BitMEXWebsocket object at 0x0ABC7F10>>: Couldn't connect to WS! Exiting.
 Websocket Closed
R2FREE commented 4 years ago

😂I figured it out. Every thing you shared is right Dude. And one more step need to be done in here. The .py file which imported BitMEXWebsocket, shoulde chang the code like this:

    while ws.ws.sock.connected:
        if ws.api_key:
            logger.info("Funds: %s" % ws.funds())
        price = ws.get_ticker()[1]
        time_now = datetime.datetime.strptime(ws.get_ticker()[0], UTC_FORMAT)

to code below:

    while 1: #   or while True:
        if ws.api_key:
            logger.info("Funds: %s" % ws.funds())
        price = ws.get_ticker()[1]
        time_now = datetime.datetime.strptime(ws.get_ticker()[0], UTC_FORMAT)
verata-veritatis commented 4 years ago

@naozhongzhang

Now that I had more time to do some digging, I realized that the bitmex-ws package on the PyPi repo is outdated. By a long shot. September 2018 was the last update, while September 2019 was the last commit on this GitHub repo. I'd recommend you to modify your bitmex-ws files in site-packages so that they match the most recent commit. Also, be sure to update the websocket-client package, otherwise it won't work.

I realize now that you can also detect when websocket has closed simply by calling ws.ws.sock.connected. You'll receive the same error that says Error : Connected is already closed, but you can reconnect by wrapping your fetch function. If websocket is not connected, sock object (I can't remember if its ws or sock object now) will return as NoneType and will not have the proper method. You will therefore get an AttributeError attempting to call ws.ws.sock.connected, which you can catch and use to reconnect. For example:

def Connect(endpoint_url, api_key, api_secret, restart):
    global ws
    if restart:
        ws.exit() # Exit original ws if attempting to reopen.
    ws = BitMEXWebsocket(endpoint=endpoint_url, symbol="XBTUSD", 
        api_key=api_key, api_secret=api_secret)

Connect(endpoint_url, api_key, api_secret, False)

while True:
    try:
        (ws.ws.sock.connected)
        print(ws.get_instrument()['lastPrice'], end='\r')
    except AttributeError:
        Connect(endpoint_url, api_key, api_secret, True)
        continue
R2FREE commented 4 years ago

@verata-veritatis (ws.ws.sock.connected) can not work for me. And the principle of it seems no essentitialy difference with modify the def __on_error(self, ws, error):. For my case, although the Websocket Closed error will occur, but the WS is sitll working well. I just modified def __on_error(self, ws, error): , the fetch def and change the while sock.connected to while 1