Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

why on_close only was called once, so it causes the client to reconnect automatically only once, even I set reconnect parameter. #974

Closed
GooVincent opened this issue Mar 8, 2024 · 8 comments

Comments

@GooVincent
Copy link

Hi, everyone, I am trying to implement reconnecting ws server when close by the ws server. What I am using websocket-client version is 1.7.0. And I found on_close was just triggerd once when the ws server closed the socket.

Following is my demo code.

client.demo

def on_close():
       print('on_close is triggered')
        ws.run_forever(dispatcher=rel, reconnect=5)
        rel.signal(2, rel.abort)  # Keyboard Interrupt
        rel.dispatch()

def main()
      ws = websocket.WebSocketApp(
                    f"ws://{host}/ws",
                    on_open=on_open,
                    on_message=on_message,
                    on_close=on_close,
                    on_error=on_error,
                    ping_timeout=3,
                    ping_interval=5)
      
       ws.run_forever(dispatcher=rel, reconnect=5)
       rel.signal(2, rel.abort)  # Keyboard Interrupt
       rel.dispatch()

server.demo

async def test(ws):
    print(f'running test..')
    while True:
        current_time = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
        await asyncio.sleep(3)
        print(f"{current_time}")
        break


async def main():
    ip = "localhost"
    port = 8765
    async with websockets.serve(test, ip, port):
        print(f'ws server running on {ip}:{port}')
        await asyncio.Future()  

What I wanna is the client will keep reconnecting to the server, as I call run_forever() again in on_close, but it only happen once, why?

Please anyone could help me on this?

@GooVincent GooVincent changed the title why not able to run client more than one times. why on_close only was called once, so it causes the client to reconnect automatically only once, even I set reconnect parameter. Mar 8, 2024
@bubbleboy14
Copy link
Collaborator

Hey @GooVincent, a few notes:

  1. only call rel.dispatch() once (in your example, main()) looks like the correct place)
  2. no need to call rel.signal() - websocket-client takes care of that here:
    dispatcher.signal(2, dispatcher.abort) # keyboard interrupt
  3. ping_timeout and ping_interval kwargs should be passed to run_forever(), not the WebSocketApp constructor

If you provide complete (runnable via "python client.py") client and server examples, I can take a crack at your issue.

@GooVincent
Copy link
Author

Here is my complete client.py

import websocket
import rel
from functools import partial


class WsClient(object):
    def __init__(self) -> None:
        # websocket.enableTrace(True)
        host = "localhost:8765"

        self.ws = websocket.WebSocketApp(
            f"ws://{host}/ws",
            on_open=partial(self.on_open),
            on_message=partial(self.on_message),
            on_error=partial(self.on_error),
            on_close=partial(self.on_close),
            on_ping=partial(self.on_ping),
            on_pong=partial(self.on_pong))

    def on_message(self, ws, message):
        print(f"received message: {message}")

    def on_error(self, ws, error):
        print(f"{ws} error: {error}")

    def on_close(self, ws, close_status_code, close_msg):
        print(f"### {ws} closed with code={close_status_code}, msg={close_msg}###")

        # Always to reconnet to server
        self.set_run_option()

    def on_open(self, ws):
        print(f"{ws} connected to server")

    def on_ping(self, ws, data):
        """
            handle msg when receive ping from the other side
        """
        print(f'<<<<<<<<received a pinging, data:[{data}]')

    def on_pong(self, ws, data):
        """
            handle msg when receive pong from the other side
        """
        print(f'<<<<<<<<received a pongong, data:[{data}')

    def set_run_option(self):
        self.ws.run_forever(dispatcher=rel,
                            reconnect=5)

                            # websocket-client will exit when ping timeout
                            # ping_interval=int(main_conf['Client']['ping_interval']),
                            # ping_timeout=int(main_conf['Client']['ping_timeout']),
                            # ping_payload=self.protocol_handle_util.ping_payload()
    def run(self):
        self.set_run_option()
        rel.dispatch()


def main():
    ws_client = WsClient()
    ws_client.run()


if __name__ == '__main__':
    main()

and the client will print like following. then stucked, watch the link status, there are two CLOSE_WAIT links.

<websocket._app.WebSocketApp object at 0x7f967c2cffd0> connected to server
### <websocket._app.WebSocketApp object at 0x7f967c2cffd0> closed with code=1000, msg=###
<websocket._app.WebSocketApp object at 0x7f967c2cffd0> connected to server

@GooVincent
Copy link
Author

Here is my complete client.py

import websocket
import rel
from functools import partial


class WsClient(object):
    def __init__(self) -> None:
        # websocket.enableTrace(True)
        host = "localhost:8765"

        self.ws = websocket.WebSocketApp(
            f"ws://{host}/ws",
            on_open=partial(self.on_open),
            on_message=partial(self.on_message),
            on_error=partial(self.on_error),
            on_close=partial(self.on_close),
            on_ping=partial(self.on_ping),
            on_pong=partial(self.on_pong))

    def on_message(self, ws, message):
        print(f"received message: {message}")

    def on_error(self, ws, error):
        print(f"{ws} error: {error}")

    def on_close(self, ws, close_status_code, close_msg):
        print(f"### {ws} closed with code={close_status_code}, msg={close_msg}###")

        # Always to reconnet to server
        self.set_run_option()

    def on_open(self, ws):
        print(f"{ws} connected to server")

    def on_ping(self, ws, data):
        """
            handle msg when receive ping from the other side
        """
        print(f'<<<<<<<<received a pinging, data:[{data}]')

    def on_pong(self, ws, data):
        """
            handle msg when receive pong from the other side
        """
        print(f'<<<<<<<<received a pongong, data:[{data}')

    def set_run_option(self):
        self.ws.run_forever(dispatcher=rel,
                            reconnect=5)

                            # websocket-client will exit when ping timeout
                            # ping_interval=int(main_conf['Client']['ping_interval']),
                            # ping_timeout=int(main_conf['Client']['ping_timeout']),
                            # ping_payload=self.protocol_handle_util.ping_payload()
    def run(self):
        self.set_run_option()
        rel.dispatch()


def main():
    ws_client = WsClient()
    ws_client.run()


if __name__ == '__main__':
    main()

and the client will print like following. then stucked, watch the link status, there are two CLOSE_WAIT links.

<websocket._app.WebSocketApp object at 0x7f967c2cffd0> connected to server
### <websocket._app.WebSocketApp object at 0x7f967c2cffd0> closed with code=1000, msg=###
<websocket._app.WebSocketApp object at 0x7f967c2cffd0> connected to server

I found to modify the member has_done_teardown, it will work. like:

def on_close(self, ws, close_status_code, close_msg):
      ...
      self.ws.has_done_teardown = False
     ...

But I am not sure if it is reasonable.

@bubbleboy14
Copy link
Collaborator

I tested your client code against my usual websocket server, @GooVincent, and didn't notice any problem with reconnects - every time I turn the server off and on, the client reconnects without issue.

In any case, it sounds like you found a workaround - glad to hear you have a solution, happy coding!

@GooVincent
Copy link
Author

I tested your client code against my usual websocket server, @GooVincent, and didn't notice any problem with reconnects - every time I turn the server off and on, the client reconnects without issue.

In any case, it sounds like you found a workaround - glad to hear you have a solution, happy coding!

Perhaps to have a try with my server.py if possible.

Yes, setting self.ws.has_done_teardown = False will work. Anyway thanks for all your advice above.

@bubbleboy14
Copy link
Collaborator

@GooVincent I tried your server example, and finally see what you mean - this is about reuse of the WebSocketApp instance after the server closes the connection. Hah, I should have read your question more carefully, sorry.

Great work determining that this is as simple as flipping that bool - I added your change to this pull request (which addresses an ambiguity related to reconnects): #972.

@engn33r I stuck has_done_teardown = False near the top of run_forever(). That pull request also includes a fix related to #971 - LMK what you think.

@engn33r
Copy link
Collaborator

engn33r commented Apr 23, 2024

This should be fixed in v1.8.0 which includes PR #972. If this reoccurs, we can reopen the issue.

@engn33r engn33r closed this as completed Apr 23, 2024
@bubbleboy14
Copy link
Collaborator

bubbleboy14 commented Apr 24, 2024

I think I encountered this as well! This was happening because WrappedDispatcher (via reconnect() and timeout()) wasn't passing reconnecting=True to setSock(). I think this was actually probably my oversight, oops, sorry.

Anyway, #983 addresses this issue, and also some related stuff.

Anyone having reconnect (or async write) issues, please LMK if this branch improves things on your end!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants