DEV Community

Cover image for SignalR core python client (V): Reconnecting Client
Andrés Baamonde Lozano
Andrés Baamonde Lozano

Posted on

SignalR core python client (V): Reconnecting Client

Intro

These days I have been working on a new feature. The client reconnection. To know if a client has been disconnected we must set a maximum time without interaction with the socket, this will be stored on a variable that we update on the following cases:

  • last sent message
  • last received message

So, now we have a variable with the last time we send/receive information through the socket. If this time has passed we must use the socket and check the connection. This action will revalidate our timeout or close our socket.

If our socket is closed we must stop app or try to reconnect. But, we cant reconnect randomly, we need to establish a policy of reconnections avoiding the saturation of the server. On the following lines i will describe implementations of the several options i had think about.

Last message in socket

I implement an connection state checker class, that classes purpose will check and update last_message variable. It that variable reaches that threshhold we send a ping, if that ping fails socket will close.

class ConnectionStateChecker(threading.Thread):
    def __init__(
            self,
            ping_function,
            keep_alive_interval,
            sleep = 1):
        self.sleep = sleep
        self.keep_alive_interval = keep_alive_interval
        self.last_message = time.time()
        self.ping_function = ping_function
        self.runing = True

    def run(self):
        while self.runing:
            time.sleep(self.sleep)
            time_without_messages = time.time() - self.last_message
            if self.keep_alive_interval < time_without_messages:
                self.ping_function()
Enter fullscreen mode Exit fullscreen mode

Strategies

For now i only implement 2 strategies, will be activated, with the parameter "type" on the with_automatic_reconnect method.

class ReconnectionType(Enum):
    raw = 0  # Reconnection with max reconnections and constant sleep time
    interval = 1  # variable sleep time
Enter fullscreen mode Exit fullscreen mode

Intervals

This reconnection works with a array of 'delays' with this, an exponential reconnection will be easily implemented.


class IntervalReconnectionHandler(ReconnectionHandler):
    def __init__(self, intervals):
        self._intervals = intervals

    def next(self):
        self.reconnecting = True
        index = self.attempt_number
        self.attempt_number += 1
        return self._intervals[index]
Enter fullscreen mode Exit fullscreen mode

You can generate a list of exponential delays easily.

delays = [pow(2, x) for x in range(1,10)]
# [2, 4, 8, 16, 32, 64, 128, 256, 512]  
Enter fullscreen mode Exit fullscreen mode

'raw' reconnect

At 'raw reconnect' i decided to establish a limit, but if limit is no established, connections will be infinite.

class RawReconnectionHandler(ReconnectionHandler):
    def __init__(self, sleep_time, max_attempts):
        super(RawReconnectionHandler, self).__init__()
        self.sleep_time = sleep_time
        self.max_reconnection_attempts = max_attempts

    def next(self):
        self.reconnecting = True
        if self.max_reconnection_attempts is not None:
            if self.attempt_number <= self.max_reconnection_attempts:
                self.attempt_number += 1
                return self.sleep_time
            else:
                raise ValueError("Max attemps reached {0}".format(self.max_reconnection_attempts))
        else:  # Infinite reconnect
            return self.sleep_time
Enter fullscreen mode Exit fullscreen mode

Client syntax

Syntax will be similar to net core signalr 3.0

hub_connection = HubConnectionBuilder()\
    .with_url(server_url)\
    .with_automatic_reconnect({
        "type": "raw",
        "keep_alive_interval": 10,  # Ping function
        "reconnect_interval": 5, # 5s between attempts
        "max_attempts": 5
    }).build()

hub_connection = HubConnectionBuilder()\
    .with_url(server_url, options={
        "access_token_factory": lambda: signalr_core_example_login(login_url, username, password)
    }).with_automatic_reconnect({
        "type": "interval",
        "keep_alive_interval": 10, # Ping function
        "intervals": [1, 3, 5, 6, 7, 87, 3]
    })\
    .build()
Enter fullscreen mode Exit fullscreen mode

Links

Github
Pypi

Thank you for reading, and write any thought below :D

Discussion (15)

Collapse
mirstation2001 profile image
Javier Barreiro

How to get the return value of the .send function (async).
If in the Hub (signalr hub in c#), there is a function, for example public string FOO() that returns a value, how to get that value calling to
hub_connection.send("FOO", [])

.send does not seem to be ASYNC function
Thanks

Collapse
mandrewcito profile image
Andrés Baamonde Lozano Author

Hi,

This functionality solves your problem? github.com/mandrewcito/signalrcore...
If does not, let me know

thank you!

Collapse
mirstation2001 profile image
Javier Barreiro

Not sure. Is it the on_invocation parameter? I tried but did not understand how to get the return value.
Do you have an example?
Thanks!

Thread Thread
mandrewcito profile image
Andrés Baamonde Lozano Author

Yes,

Here are 2 examples of using the callback. There is no return value, websocket methods are not synchronous. To listen from a callback , you must wait for a message with your invocation id.

github.com/mandrewcito/signalrcore...
github.com/mandrewcito/signalrcore...

Collapse
samy0392 profile image
Samer Abbas

How to reconnect when socket gets closed by server?

Collapse
mandrewcito profile image
Andrés Baamonde Lozano Author

Hi again, i have just published a version with includes automatic reconnect, i delayed a lot, because a was fixing a problem with py2 compatibility.

pypi.org/project/signalrcore/0.7.3/

Collapse
samy0392 profile image
Samer Abbas

Reconnect is now working even after the socket closes!

When websocket re-opens after closing, I see this error:
ERROR error from callback >: Expecting value: line 1 column 1 (char 0)

Websocket stays open after this error and I am still able to receive messages through signalr. So this is probably some kind of warning.

Thread Thread
mandrewcito profile image
Andrés Baamonde Lozano Author

I think that is an error related with auth, if an errors occuurs on the login function library encapsulates it an try login later:

error image

do you refer to that error?

I think that my next exteps on the library must be improving logging and documentation to prevent this kind of things.

Thread Thread
samy0392 profile image
Samer Abbas • Edited

Yes after that error, it reconnects and websocket opens again. After websocket opens, then I see this error:

"ERROR error from callback <bound method BaseHubConnection.on_message of signalrcore.hub.base_hub_connection.BaseHubConnection object at 0xb5954410: type"

Thread Thread
mandrewcito profile image
Andrés Baamonde Lozano Author

That error was solved in the 0.74. Was related with the connection handshake. Yesterday I uploaded
the fix. The new version is available since then.

Thread Thread
samy0392 profile image
Samer Abbas

Awesome!
Thank you!

Collapse
samy0392 profile image
Samer Abbas

Thank you! I will test it.

Collapse
samy0392 profile image
Samer Abbas

Hi Andrés,

How can I send a signalr message everytime websocket reconnects after a disconnect?
I want to send a message to the server when I reconnect.
How can I do this?

Thank you

Collapse
mandrewcito profile image
Andrés Baamonde Lozano Author

On the next version, Callbacks for on_connect and on_disconnect will be included. This change would solve your problem right?

Collapse
samy0392 profile image
Samer Abbas

Yes perfect! Thank you so much!