Skip to main content

Lobby WebSocket Recovery: Handling Disruptions and Reconnection

Last updated on February 27, 2025

In real-time applications, maintaining a stable WebSocket connection is essential for a smooth user experience. However, network disruptions or server issues when there is service maintenance can cause connection failures. When retry attempts reach their timeout limit and reconnection is unsuccessful, implementing fallback mechanisms becomes crucial to ensure that key features remain functional. This article outlines best practices for Lobby WebSocket recovery, helping developers build more resilient and user-friendly systems. To avoid a bad user experience for the players, developers should:

βœ… Display clear UI notifications when a connection is lost.

βœ… Provide manual reconnection options when automatic methods fail.

By handling WebSocket reconnections properly, developers can ensure a smooth, uninterrupted experience for players. πŸš€

Websocket Reconnect Strategy​

The default websocket reconnection strategy is using Balanced Reconnect Strategy. Here's how the general reconnect strategy works:

  1. The first reconnect starts with an initial backoff delay. The value depends on the reconnect strategy.
  2. Each retry backoff delay is multiplied with the base factor backoff of the reconnect strategy.
  3. The backoff delay is clamped between 1 seconds as minimum value, and the maximum retry interval as maximum value.
  4. A small random variation is added by taking a value between -backoff delay and +backoff delay, dividing it by four, and applying it to the current backoff delay. This prevents retries from happening at perfectly predictable intervals.
  5. If the total time spent on retries exceeds the total timeout of the reconnect strategy, the system stops reconnecting and triggers OnConnectionClosed delegate with error status code is EWebsocketErrorTypes::DisconnectFromExternalReconnect.

The websocket reconnection strategy support was implemented by default and released on:

Reconnection Notification​

When the lobby connection is lost, AccelByte SDK has a retry mechanism to reconnect the lobby. Meanwhile the AccelByte SDK is in retry-state, the Byte Wars will popup a reconnecting notification.

Byte Wars Unreal Engine project's file references:

  • A Header file: /Source/AccelByteWars/TutorialModules/Play/OnlineSessionUtilsAccelByteWarsOnlineSession.h
  • A CPP file: /Source/AccelByteWars/TutorialModules/Play/OnlineSessionUtils/AccelByteWarsOnlineSession.cpp
  1. Lobby reconnection binding.

    After the lobby connection is succesfully connected, bind the lobby reconnecting handling into delegate AccelByteOnLobbyReconnectedDelegates of AccelByte OSS FOnlineIdentityAccelByte.

    void UAccelByteWarsOnlineSession::OnConnectLobbyComplete(int32 LocalUserNum, bool bSucceeded, const FUniqueNetId& UserId, const FString& Error)
    {
    // ...
    FOnlineIdentityAccelBytePtr ABIdentityInt = GetABIdentityInt();
    if (!ensureMsgf(ABIdentityInt, TEXT("AB OnlineIdentity interface is nullptr.")))
    {
    return;
    }
    // ...
    ABIdentityInt->AccelByteOnLobbyReconnectingDelegates->RemoveAll(this);
    // ...
    ABIdentityInt->AccelByteOnLobbyReconnectingDelegates->AddUObject(this, &ThisClass::OnLobbyReconnecting);
    // ...
    }
  2. Pop up a reconnecting message.

    // ...
    #define LOBBY_RECONNECTING_MESSAGE NSLOCTEXT(BYTEWARS_LOCTEXT_NAMESPACE, "Lobby Reconnecting", "Reconnecting to AGS.")
    void UAccelByteWarsOnlineSession::OnLobbyReconnecting(int32 LocalUserNum, const FUniqueNetId& UserId, int32 StatusCode, const FString& Reason, bool bWasClean)
    {
    UAccelByteWarsGameInstance* GameInstance = Cast<UAccelByteWarsGameInstance>(GetGameInstance());
    if (!ensureMsgf(GameInstance, TEXT("Game Instance is null")))
    {
    return;
    }

    GameInstance->bIsReconnecting = true;
    GetPromptSubystem()->ShowLoading(LOBBY_RECONNECTING_MESSAGE);
    }

Lobby Reconnection Timeout​

When the lobby websocket reconnection reaches a timeout, the game must have some handling to ensure good experience for the player. The handling could be a notification popup, game level auto or manual connect, disable online feature, etc. You can configure the websocket reconnection following these steps.

In the Byte Wars Unreal Engine project, there are 2 steps to handle the lobby reconnection timed out.

Game-level Lobby Auto-Connect​

If either lobby fails to connect or the lobby reconecting mechanism has failed, the AccelByte OSS FOnlineIdentityAccelByte will trigger delegate AccelByteOnLobbyConnectionClosedDelegates. Therefore, you have to bind your connection lost handling code to that delegate.

  1. Lobby connection close binding.

    After the lobby connection is succesfully connected, bind the lobby reconnecting handling into delegate AccelByteOnLobbyConnectionClosedDelegates of AccelByte OSS FOnlineIdentityAccelByte.

    void UAccelByteWarsOnlineSession::OnConnectLobbyComplete(int32 LocalUserNum, bool bSucceeded, const FUniqueNetId& UserId, const FString& Error)
    {
    // ...
    FOnlineIdentityAccelBytePtr ABIdentityInt = GetABIdentityInt();
    if (!ensureMsgf(ABIdentityInt, TEXT("AB OnlineIdentity interface is nullptr.")))
    {
    return;
    }
    // ...
    ABIdentityInt->AccelByteOnLobbyConnectionClosedDelegates->RemoveAll(this);
    // ...
    ABIdentityInt->AccelByteOnLobbyConnectionClosedDelegates->AddUObject(this, &ThisClass::OnLobbyConnectionClosed);
    }
  2. Lobby (Manual) Auto Reconnect Timeout Handling in the Game Code

    void UAccelByteWarsOnlineSession::OnLobbyConnectionClosed(int32 LocalUserNum, const FUniqueNetId& UserId, int32 StatusCode, const FString& Reason, bool bWasClean)
    {
    UAccelByteWarsGameInstance* GameInstance = Cast<UAccelByteWarsGameInstance>(GetGameInstance());
    if (!ensureMsgf(GameInstance, TEXT("Game Instance is null")))
    {
    return;
    }

    GameInstance->bIsReconnecting = false;
    GetPromptSubystem()->HideLoading();

    if (StatusCode == static_cast<int32>(AccelByte::EWebsocketErrorTypes::DisconnectFromExternalReconnect))
    {
    // Do some manual handle to reconnect lobby
    LobbyConnect(LocalUserNum);

    GameInstance->bIsReconnecting = true;
    GetPromptSubystem()->ShowLoading(LOBBY_RECONNECTING_MESSAGE);
    }
    }

    There are different types of WebSocket errors when the lobby connection is closed. If the lobby reconnecting takes too long and times out, make sure to run the lobby connect only when the WebSocket error type is DisconnectFromExternalReconnect.

Lobby Manual Connect​

If the auto lobby connect fails to return the lobby connection back, Byte Wars prompt out a dialog box to the player with options to connect lobby manually or log out the game. If the player choose option to log out the game, the game will be redirected to the Main Menu level and shows the login page.

// ...
#define LOBBY_FAILED_CONNECT_MESSAGE NSLOCTEXT(BYTEWARS_LOCTEXT_NAMESPACE, "Lobby Connection Failed", "Failed to connect AGS Lobby. Try to Reconnect")
#define LOBBY_FAILED_RECONNECT_MESSAGE NSLOCTEXT(BYTEWARS_LOCTEXT_NAMESPACE, "Lobby Reconnect Failed", "Failed to reconnect AGS Lobby. Try to Reconnect")
// ...
#define LOBBY_RECONNECTING_MESSAGE NSLOCTEXT(BYTEWARS_LOCTEXT_NAMESPACE, "Lobby Reconnecting", "Reconnecting to AGS.")
void UAccelByteWarsOnlineSession::OnConnectLobbyComplete(int32 LocalUserNum, bool bSucceeded, const FUniqueNetId& UserId, const FString& Error)
{
// ...
FOnlineIdentityAccelBytePtr ABIdentityInt = GetABIdentityInt();
if (!ensureMsgf(ABIdentityInt, TEXT("AB OnlineIdentity interface is nullptr.")))
{
return;
}
// ...
if (!bSucceeded)
{
// Lobby Connect and Reconnect failed
FText message = GameInstance->bIsReconnecting ? LOBBY_FAILED_RECONNECT_MESSAGE : LOBBY_FAILED_CONNECT_MESSAGE;
GetPromptSubystem()->ShowDialoguePopUp(ERROR_PROMPT_TEXT, message, EPopUpType::ConfirmationYesNo,
FPopUpResultDelegate::CreateWeakLambda(this, [this, LocalUserNum](EPopUpResult Result)
{
switch (Result)
{
// Accept manual reconnect attempt
case Confirmed:
{
GetPromptSubystem()->ShowLoading(LOBBY_RECONNECTING_MESSAGE);
LobbyConnect(LocalUserNum);

UAccelByteWarsGameInstance* GameInstance = Cast<UAccelByteWarsGameInstance>(GetGameInstance());
GameInstance->bIsReconnecting = true;
}
break;
// Deny reconnect and logout
case Declined:
{
UGameplayStatics::OpenLevel(GetWorld(), TEXT("MainMenu"));

// Pending Perform Logout after Main Menu level opened
GetWorld()->GetTimerManager().SetTimerForNextTick(FVoidHandler::CreateLambda([this, LocalUserNum]()
{
// Open Login Menu
UTutorialModuleDataAsset* AuthEssentialsDataAsset =
UTutorialModuleUtility::GetTutorialModuleDataAsset(FPrimaryAssetId("TutorialModule:AUTHESSENTIALS"), this);
TSubclassOf<UAccelByteWarsActivatableWidget> LoginWidgetClass = AuthEssentialsDataAsset->GetTutorialModuleUIClass();

UAccelByteWarsGameInstance* GameInstance = Cast<UAccelByteWarsGameInstance>(GetGameInstance());
GameInstance->GetBaseUIWidget()->PushWidgetToStack(EBaseUIStackType::Menu, LoginWidgetClass.Get());

GetPromptSubystem()->ShowLoading();
GetABIdentityInt()->Logout(LocalUserNum);

GameInstance->bIsReconnecting = false;
}));
}
break;
}
}));

GameInstance->bIsReconnecting = false;
return;
}
// ...
}

Auto-retrieve Missing Notifications​

When the game client and server get disconnected from the lobby service, they might miss important notifications. Luckily, the Unreal SDK and Unity SDK have a built-in system to detect and recover these lost notifications once the WebSocket connection is restored. After successfully retrieving the missing notifications, the SDK triggers the relevant event delegates, ensuring the game doesn't lose critical gameplay-related updates. The SDK triggers delegates based on the sequence number of the notifications. This means the oldest missing notification will be processed first, ensuring that events are handled in the correct sequence.

Since AccelByte's session and matchmaking features depend heavily on lobby notifications, this mechanism also checks for any missed updates in these areas. This ensures that matchmaking and session management continue smoothly, even if the WebSocket connection is temporarily disrupted.