connection-resiliency

Connection Resiliency with AppWarp

Background

On mobile devices, data connectivity has always been an issue. While users are on the go, the data source will often switch towers and or downgrade/upgrade between 2G and 3G. This has an impact on applications that rely on persistent data connectivity.

AppWarp’s binary protocol runs on top of TCP. This provides developers with fast, efficient and reliable realtime connectivity. However on mobile devices, it will often be the case that the device IP address will change when its on the go. This results in the underlying connection to break and the application code must recover from this by reconnecting, rejoining and resubscribing to the room the user was in before the error occurred. Worse, other users in the room will receive an event indicating that the user has left the room. This could even impact the game logic as other client don’t know whether the concerned user experienced intermittent connectivity issues or willfully disconnected (left the room).

AppWarp Connection Resiliency

We have introduced our Connection Resiliency feature to offer developers an elegant solution to this scenario. When any connectivity error occurs, AppWarp SDK will raise an error indicating whether the error is recoverable or non-recoverable. If its a recoverable error, our new RecoverConnection API can be used and if connectivity has been restored, will automatically restore the client to its last state in terms of the room it was in and all its subscriptions. So no re-joining or re-subscribing and state keeping is required in the application. Additionally when such intermittent errors occur, AppWarp server will raise a specific event to other users in the room indicating that the user has been paused. After a specified timeout, if the concerned user is unable to restore its connection, a user left room event will be raised for other users in the room. On the other hand if the connection is restored successfully, a user resumed event is raised.

APIs usage and examples

Some of the relevant APIs are described below

public static void setRecoveryAllowance ( int maxRecoveryTime ) 

Sets the connection recovery time (seconds) allowed that will be negotiated with the server. By default it is 0 so there is no connection recovery and the SDK behavior will be as before. It is important you invoke this right after you initialize the WarpClient and before you make your first call to connect. Recommended values are between 60 to 120 i.e. 1 minute to 2 minutes. See example below

WarpClient . initialize ( Constants . apiKey , Constants . secretKey ); 
WarpClient . setRecoveryAllowance ( 120 ); 
public void connectWithUserName ( String userName ) 

This attempts to establish a new connection being initiated with the given username.

public void RecoverConnection () 

This attempts to restore the previous connection if it was broken and a recoverable connection error event was raised. If connectivity is not there at the time of calling, it will again raise a recoverable connection error event ( guaranteed within 6 seconds ). and the application can choose to recover again immediately or after a while. See the example towards the end below on how to use this.

public void disconnect () 

This will disconnect the current session with the server. Once disconnected, the session can’t be recovered and a new connection will need to be established.

public interface ConnectionRequestListener { 
    /** 
     * Invoked in response to a ConnectWithUsername or RecoverConnection request. 
     * This is also raised if an established connection breaks. 
     * @param event 
     */ 
    public void onConnectDone ( ConnectEvent event ); 

    /** 
     * Invoked in response to a disconnect request. 
     * @param event 
     */ 
    public void onDisconnectDone ( ConnectEvent event ); 
} 

The result code in the ConnectEvent is important as it indicates whether the connection was successfully established/restored or a recoverable/non-recoverable error has occurred. Below are the important codes that need to be handled

  public static final byte SUCCESS = 0 ;    // successfully established a new connection 
  public static final byte AUTH_ERROR = 1 ;    // indicates incorrect api and secret key pair 
  public static final byte CONNECTION_ERROR = 5 ;    // non-recoverable connection error 
  public static final byte SUCCESS_RECOVERED = 8 ;    // successfully recovered the previous connection 
  public static final byte CONNECTION_ERROR_RECOVERABLE = 9 ;    // recoverable connection error 

An example of how to handle this event is given below.

@Override
public void onConnectDone (final ConnectEvent event ) {
    handler .post (new Runnable () {
        @Override
        public void run () {
            progressDialog .dismiss ();
            if (event .getResult () == WarpResponseResultCode .SUCCESS ){
                Toast .makeText (MainActivity .this , "Connection success" , Toast .LENGTH_SHORT ).show (); 
                }
            else if (event .getResult () == WarpResponseResultCode .SUCCESS_RECOVERED ){
                Toast .makeText (MainActivity .this , "Connection recovered" , Toast .LENGTH_SHORT ).show (); 
                }
            else if (event .getResult () == WarpResponseResultCode .CONNECTION_ERROR_RECOVERABLE ){
                Toast .makeText (MainActivity .this , "Recoverable connection error. Recovering session in 5 seconds" , Toast .LENGTH_SHORT ).show ();
                handler .postDelayed (new Runnable () {
                    @Override
                    public void run () {                                          
                        progressDialog = ProgressDialog .show (MainActivity .this , "" , "Recovering..." );
                        theClient .RecoverConnection (); 
                        } 
                        }, 5000 ); 
                        }
                        else {
                            Toast .makeText (MainActivity .this , "non-recoverable connection error. Reconnecting in 5 seconds" , Toast .LENGTH_SHORT ).show ();
                            handler .postDelayed (new Runnable () {
                                @Override
                                public void run () {
                                    progressDialog = ProgressDialog .show (MainActivity .this , "" , "Reconnecting..." );
                                    theClient .connectWithUserName (Utils .USER_NAME ); 
                                } 
                          }, 5000 ); 
                   } 
              }
      });		
}