How to reconnect and rejoin when mobile app goes to background. Best practice?

The whole answer can be found below.

Please note: The Photon forum is closed permanently. After many dedicated years of service we have made the decision to retire our forum and switch to read-only: we've saved the best to last! And we offer you support through these channels:

Try Our
Documentation

Please check if you can find an answer in our extensive documentation on PUN.

Join Us
on Discord

Meet and talk to our staff and the entire Photon-Community via Discord.

Read More on
Stack Overflow

Find more information on Stack Overflow (for Circle members only).

Write Us
an E-Mail

Feel free to send your question directly to our developers.

How to reconnect and rejoin when mobile app goes to background. Best practice?

Burny123
2019-05-26 20:31:22

Hi guys, we are using PUN for almost a year and are very happy with it. The only problem we have is handling the case where the mobile app is going to the background and to trigger a successful reconnect and rejoin. We can’t seem to find some best practices or code snippets so we cooked up something ourselves. The following implementation seems to work most of the times but it’s not 100% reliable. We still have cases where the app is not successfully reconnecting and rejoining.

We hope you could take the time and spot some problems with this script. We also hope you could provide us with a best practice for reconnecting and rejoining.

https://pastebin.com/X83mubxf

FYI:

  • The room we create has a PlayerTTL of 300000.
  • We are only targeting iOS.
  • PUN v2.7
  • Unity 2018.3.5

Thanks!

Comments

JohnTube
2019-05-27 12:49:57

Hi @Burny123,

Try this UtilityScript.
Do you have the same issue described here?

Burny123
2019-05-27 18:46:45

@JohnTube Thanks for helping out. The link you provided leads to an empty page on hastebin, I guess something went wrong?

The issue from the link is not the same. We can successfully reconnect and rejoin most of the times, but not always.. It seems random and is hard for us to debug.

I love to see the UtilityScript.

Thanks!

JohnTube
2019-05-28 09:47:47

Hey @Burny123,

I have checked again and I think the link works fine now.
Maybe there was an issue yesterday.
Anyways find the code also here:

using System;  
using Photon.Realtime;  
using UnityEngine;

namespace Photon.Pun.UtilityScripts
{
///


/// Unexpected disconnects recovery
///

public class DisconnectsRecovery : MonoBehaviourPunCallbacks
{
[Tooltip("Whether or not attempt a rejoin without doing any checks.")]
[SerializeField]
private bool skipRejoinChecks;
[Tooltip("Whether or not realtime webhooks are configured with persistence enabled")]
[SerializeField]
private bool persistenceEnabled;

    private bool rejoinCalled;

    private int minTimeRequiredToRejoin = 0; // TODO: set dynamically based on PhotonNetwork.NetworkingClient.LoadBalancingPeer.RoundTripTime

    private DisconnectCause lastDisconnectCause;  
    private bool wasInRoom;

    private bool reconnectCalled;

    public override void OnEnable()  
    {  
        base.OnEnable();  
        PhotonNetwork.NetworkingClient.StateChanged += this.OnStateChanged;  
    }

    public override void OnDisable()  
    {  
        base.OnDisable();  
        PhotonNetwork.NetworkingClient.StateChanged -= this.OnStateChanged;  
    }

    private void OnStateChanged(ClientState fromState, ClientState toState)  
    {  
        if (toState == ClientState.Disconnected)  
        {

            Debug.LogFormat("OnStateChanged from {0} to {1}, PeerState={2}", fromState, toState,   
                PhotonNetwork.NetworkingClient.LoadBalancingPeer.PeerState);  
            this.HandleDisconnect();  
        }  
    }

    public override void OnDisconnected(DisconnectCause cause)  
    {  
        Debug.LogFormat("OnDisconnected(cause={0}) ClientState={1} PeerState={2}",   
            cause,   
            PhotonNetwork.NetworkingClient.State,   
            PhotonNetwork.NetworkingClient.LoadBalancingPeer.PeerState);  
        if (rejoinCalled)  
        {  
            Debug.LogError("Rejoin failed, client disconnected");  
            rejoinCalled = false;  
            return;  
        }

        if (reconnectCalled)  
        {  
            Debug.LogError("Reconnect failed, client disconnected");  
            reconnectCalled = false;  
            return;  
        }  
        lastDisconnectCause = cause;  
        wasInRoom = PhotonNetwork.CurrentRoom != null;  
        if (PhotonNetwork.NetworkingClient.State == ClientState.Disconnected)  
        {  
            this.HandleDisconnect();  
        }  
    }

    private void HandleDisconnect()  
    {  
        switch (lastDisconnectCause)  
        {  
            case DisconnectCause.Exception:  
            case DisconnectCause.ServerTimeout:  
            case DisconnectCause.ClientTimeout:  
            case DisconnectCause.DisconnectByServerLogic:  
            case DisconnectCause.AuthenticationTicketExpired:  
            case DisconnectCause.DisconnectByServerReasonUnknown:  
                if (wasInRoom)  
                {  
                    this.CheckAndRejoin();  
                }  
                else  
                {  
                    Debug.Log("PhotonNetwork.Reconnect called");  
                    reconnectCalled = PhotonNetwork.Reconnect();  
                }  
                break;  
            case DisconnectCause.OperationNotAllowedInCurrentState:  
            case DisconnectCause.CustomAuthenticationFailed:  
            case DisconnectCause.DisconnectByClientLogic:  
            case DisconnectCause.InvalidAuthentication:  
            case DisconnectCause.ExceptionOnConnect:  
            case DisconnectCause.MaxCcuReached:  
            case DisconnectCause.InvalidRegion:  
            case DisconnectCause.None:  
                break;  
            default:  
                throw new ArgumentOutOfRangeException("cause", lastDisconnectCause, null);  
        }  
        lastDisconnectCause = DisconnectCause.None;  
        wasInRoom = false;  
    }

    public override void OnJoinRoomFailed(short returnCode, string message)  
    {  
        if (!rejoinCalled)  
        {  
            return;  
        }  
        rejoinCalled = false;  
        Debug.LogErrorFormat("Quick rejoin failed with error code: {0} & error message: {1}", returnCode, message);  
    }

    public override void OnJoinedRoom()  
    {  
        if (rejoinCalled)  
        {  
            Debug.Log("Rejoin successful");  
            rejoinCalled = false;  
        }  
    }

    private void CheckAndRejoin()  
    {  
        if (skipRejoinChecks)  
        {  
            Debug.Log("PhotonNetwork.ReconnectAndRejoin called");  
            rejoinCalled = PhotonNetwork.ReconnectAndRejoin();  
        }  
        else  
        {  
            bool wasLastActivePlayer = true;  
            if (!persistenceEnabled)  
            {  
                for (int i = 0; i < PhotonNetwork.PlayerListOthers.Length; i++)  
                {  
                    if (!PhotonNetwork.PlayerListOthers[i].IsInactive)  
                    {  
                        wasLastActivePlayer = false;  
                        break;  
                    }  
                }  
            }  
            if ((PhotonNetwork.CurrentRoom.PlayerTtl < 0 || PhotonNetwork.CurrentRoom.PlayerTtl > minTimeRequiredToRejoin) // PlayerTTL checks  
              && (!wasLastActivePlayer || PhotonNetwork.CurrentRoom.EmptyRoomTtl > minTimeRequiredToRejoin || persistenceEnabled)) // EmptyRoomTTL checks  
            {  
                Debug.Log("PhotonNetwork.ReconnectAndRejoin called");  
                rejoinCalled = PhotonNetwork.ReconnectAndRejoin();  
            }  
            else  
            {  
                Debug.Log("PhotonNetwork.ReconnectAndRejoin not called, PhotonNetwork.Reconnect is called instead.");  
                reconnectCalled = PhotonNetwork.Reconnect();  
            }  
        }  
    }

    public override void OnConnectedToMaster()  
    {  
        if (reconnectCalled)  
        {  
            Debug.Log("Reconnect successful");  
            reconnectCalled = false;  
        }  
    }  
}</code></pre>  

}

Burny123
2019-05-28 16:21:02

@JohnTube The hastebin still shows an empty page for me. But I'm going to try out the snippet you provided.

I'll report the results, thanks!

Burny123
2019-05-28 20:41:47

@JohnTube I tried the script but it not works on my iPad. The main issue seems to be:

’Reconnect() failed. Can only connect while in state 'Disconnected'. Current state: Connected’

Full log: https://pastebin.com/xZCZJibz

We had this issue too and added a coroutine which waits for the player to be disconnected fully.

What do you suggest to do from here?

Thanks!

JohnTube
2019-05-30 13:31:17

Hi @Burny123,

I can only think of adding a coroutine to detect when a client + peer are fully properly and disconnected before attempting a reconnect or a rejoin. So it's like your solution, however, you said;

implementation seems to work most of the times but it’s not 100% reliable. We still have cases where the app is not successfully reconnecting and rejoining.
can we try to find out how to reproduce those cases? Let's try to narrow it down.

Burny123
2019-05-30 16:38:20

@JohnTube Sure thing, one of these days I'm going to create a full test environment to try and narrow it down.

To be continued, thanks!

JohnTube
2019-05-31 11:24:56

Hi @Burny123,

In order to prioritize this you can send an email to [email protected] with all the delails.
Thanks.

jeanfabre
2019-06-05 12:28:48

ok,

just for the record should other face this issue while we discuss this internally:

when you get a disconnect call back, raise a boolean provate variable, and during an update call, try to reconnect if that bool is true, this is the shortest way to reconnect and avoid the issue explained above.

It's going to be tricky, because it could be a case where it's a race condition situation, if we change the order of property sets and callbacks, it could break other projects.

Bye,

Jean

jandulio
2019-06-05 15:11:46

@jeanfabre

Is the Utility Script you provided still valid, or does it need updating?

jeanfabre
2019-06-06 12:28:13

Hi,

yes, it needs updating. here's the code featuring the update for the reconnecting to occur,

https://pastebin.com/wk39tgzA

Let me know how it goes.

Bye,

Jean

JohnTube
2019-06-07 10:43:34

I want to clarify/rectify something:
the script shared by @jeanfabre has some extra lines we used for testing internally:

remove them from OnEnable:

before:

        public override void OnEnable()  
        {  
            base.OnEnable();  
            PhotonNetwork.NetworkingClient.StateChanged += this.OnStateChanged;  
            PhotonNetwork.KeepAliveInBackground = 0f;  
            PhotonNetwork.PhotonServerSettings.RunInBackground = false;  
            Application.runInBackground = false;  
        }

after:

        public override void OnEnable()  
        {  
            base.OnEnable();  
            PhotonNetwork.NetworkingClient.StateChanged += this.OnStateChanged;  
        }

@jeanfabre
so if I understood correctly, we need to wait 1 frame before trying to reconnect and rejoin?
we could do this using a coroutine and WaitForEndOfFrame?
hmm will test this.

jeanfabre
2019-06-07 11:08:27

Hi,

yes. That's what I am witnessing here. We are currently investigating as to what exactly happens during that frame and if we can remove this and be able to reconnect straight from a disconnect callback.

Bye,

Jean

Nithish
2020-08-22 16:17:22

Hello @JohnTube, i'm not much into coding and sorry for my english grammer if any mistakes, actually even i'm running same problem, but i'm using pun1 classic, im trying to reconnect when one of host or client mobile disconnected from internet and then reconnected, then i couldnt able to run the game. but in the editor even after reconnecting internet also the game works, but not in android mobile. can you please help me out as i'm in very urgent need...

Nithish
2020-08-23 08:15:01

@Nithish wrote: »

Hello @JohnTube, i'm not much into coding and sorry for my english grammer if any mistakes, actually even i'm running same problem, but i'm using pun1 classic, im trying to reconnect when one of host or client mobile disconnected from internet and then reconnected, then i couldnt able to run the game. but in the editor even after reconnecting internet also the game works, but not in android mobile. can you please help me out as i'm in very urgent need...

anyone there to help......

Jackinabox
2020-09-17 18:52:44

Hi @Nitish,

Hope this is not too late for you. I came across the same issue a few days ago and manage to solve it using jeanfabre's code posted above.

In gist, the blackscreen on android arises when your client disconnects from the server, which causes the server to despawn/destroy the game character of that client. Upon reconnect, whether via detection in OnApplicationPause or from the OnDisconnectFromPhoton callback, you may get an issue as stated above "Reconnect() failed. Can only connect while in state 'Disconnected'. Current state: Connected" (or in my case the current state is disconnecting), which cause the reconnect call to fail.

The trick is to call reconnect in the next frame as your callbacks. This is achieved by setting a flag in your server logic, and checking that flag in the update call. Once that flag has been set, Update function will take care of reconnect logic in the next frame, therefore circumnavigate the issue.

I'm not sure whether there is a conclusion to the official investigation on this issue since August of 2019, but as of now, I can confirm I'm still running into it.

Hope it helps

Back to top