Description
Calling StartClient() or StartHost() from within an OnServerStopped()/OnClientStopped() callback leads to inconsistent state.
Reproduce Steps
NetworkManager.Singleton.OnServerStopped += MyOnServerStoppedHandler
NetworkManager.Singleton.StartHost()
- Wait for a few seconds
NetworkManager.Singleton.Shutdown(true)
- Inside
MyOnServerStoppedHandler, call NetworkManager.Singleton.StartClient()
Actual Outcome
A few tidbits of client state are cleared out at the tail end of NetworkManager::InternalShutdown() that were just set up inside of StartClient()/StartHost(). For example, StartClient() calls ConnectionManager.LocalClient.SetRole(false, true, this); as part of that callback, but then a moment later InternalShutdown() calls ConnectionManager.LocalClient.SetRole(false, false);.
Expected Outcome
Any of these three options would be fine:
- (preferred) All shutdown work is complete and settled before
OnServerStopped()/OnClientStopped() are called, permitting StartClient()/StartHost() to be called from within those callbacks.
- A new
OnNetworkManagerShutdownComplete callback happens when shutdown is well and truly complete.
StartClient()/StartHost() detect that shutdown isn't quite finished yet, and bail by returning false (preferably with some sort of error log explaining why).
Environment
- OS: N/A
- Unity Version: N/A
- Netcode Version: 1.6.0
Additional Context
I bumped into this as a problem while trying to accommodate a flow of "I used to be hosting, but then I got an invite from a friend and want to switch to being a client that's connected to their host". To work around the problem, I had MyOnServerStopped() call Invoke(nameof(OnServerFullyStopped), 0.01f), and then called StartClient() from within OnServerFullyStopped().
This workaround caused an issue for me in a different way, though, because SteamNetworkingSocketsTransport does a similar workaround during their shutdown. Critically, they wait for 0.1f seconds before finishing the transport's cleanup. This resulted in a race condition where my game would be halfway through connecting to its new host when the transport would wrap up shutting itself down.
For the time being, I can work around that problem by simply waiting longer for everything to settle before calling StartClient(), but clearly that's suboptimal all around.
All of that said, if I'm simply missing something in the docs on how my code can know that NetworkManager is really, truly safe to call StartClient()/StartHost(), or if I'm going about it wrong and should instead destroy/recreate my NetworkManager for this particular flow, please let me know!
Description
Calling
StartClient()orStartHost()from within anOnServerStopped()/OnClientStopped()callback leads to inconsistent state.Reproduce Steps
NetworkManager.Singleton.OnServerStopped += MyOnServerStoppedHandlerNetworkManager.Singleton.StartHost()NetworkManager.Singleton.Shutdown(true)MyOnServerStoppedHandler, callNetworkManager.Singleton.StartClient()Actual Outcome
A few tidbits of client state are cleared out at the tail end of
NetworkManager::InternalShutdown()that were just set up inside ofStartClient()/StartHost(). For example,StartClient()callsConnectionManager.LocalClient.SetRole(false, true, this);as part of that callback, but then a moment laterInternalShutdown()callsConnectionManager.LocalClient.SetRole(false, false);.Expected Outcome
Any of these three options would be fine:
OnServerStopped()/OnClientStopped()are called, permittingStartClient()/StartHost()to be called from within those callbacks.OnNetworkManagerShutdownCompletecallback happens when shutdown is well and truly complete.StartClient()/StartHost()detect that shutdown isn't quite finished yet, and bail by returningfalse(preferably with some sort of error log explaining why).Environment
Additional Context
I bumped into this as a problem while trying to accommodate a flow of "I used to be hosting, but then I got an invite from a friend and want to switch to being a client that's connected to their host". To work around the problem, I had
MyOnServerStopped()callInvoke(nameof(OnServerFullyStopped), 0.01f), and then calledStartClient()from withinOnServerFullyStopped().This workaround caused an issue for me in a different way, though, because
SteamNetworkingSocketsTransportdoes a similar workaround during their shutdown. Critically, they wait for 0.1f seconds before finishing the transport's cleanup. This resulted in a race condition where my game would be halfway through connecting to its new host when the transport would wrap up shutting itself down.For the time being, I can work around that problem by simply waiting longer for everything to settle before calling
StartClient(), but clearly that's suboptimal all around.All of that said, if I'm simply missing something in the docs on how my code can know that
NetworkManageris really, truly safe to callStartClient()/StartHost(), or if I'm going about it wrong and should instead destroy/recreate myNetworkManagerfor this particular flow, please let me know!