How Join Tick Thread on Authentication Error Callback?

Greetings,

I think I’ve hit my limit of my C++ understanding. If anyone here can help, it would be very much appreciated.

I have a function in my program that takes user input and uses it to authenticate. This function first creates the default client and then starts a thread for the tick. Then it calls Nakama’s _client→AuthenticateEmail()

If authentication is successful all is well. But if authentication is unsuccessful I need to Join the ticker thread inside the Error Callback. If I don’t do this, then when the user tries to auth again and we reinitialize the client, we wind up initializing a thread that’s already running, which causes a crash.

I can join the thread ordinarily with theTicker.join(). This works in my class destructor and in my signout function. But when I execute theTicker.join() inside the Error Callback Lambda I get _RESOURCE_DEADLOCK_WOULD_OCCUR error.

Below is a paraphrased snippet of my code. If I’m completely missing the point, please do let me know. Thank you!

class NakamaSessionManager
{

private:
NClientPtr _client;
NSessionPtr _session;
NRtClientPtr _rtClient;
std::thread _theTicker;
bool _clientActive = false;

void initializeClient() {
     NClientParameters parameters;
     _client = createDefaultClient(parameters);
     _clientActive = true;
     _theTicker = std::thread{ &NakamaSessionManager::theTick, this };
     return;
}

void theTick() {
    while (_clientActive) {
            _client->tick();
        if (_rtClient) {
            _rtClient->tick();
        }    
        std::this_thread::sleep_for(std::chrono::milliseconds(50));
    }
}

void authenticateClientEmail(…) {
    auto successCallback = [](NSessionPtr session)
        { ... };

    auto errorCallback = [this](const NError& error)
        {
            _clientActive = false;
            if (_theTicker.joinable()) {
               _theTicker.join();   //THIS LINE GIVES  ERROR: _RESOURCE_DEADLOCK_WOULD_OCCUR.
        };
             
    _client->authenticateEmail(...);

    return;
   }

public:

void signInEmail(…){
     initializeClient();
     authenticateClientEmail(…);
     return;
     }

}

Greetings Mehoo462!
As far as I can see, the code is joining a thread from within the thread itself (which will, indeed, deadlock). That’s because callback functions are executed from the thread that is running the tick function. Basically, a function (error callback lambda) called from _theTicker thread (when it executes tick) waiting for _theTicker to finish. But as it’s _theTicker who is doing the waiting (by calling join), this just deadlocks.

PS: Besides that, Nakama C++ client sdk is not multithread safe, as in, it’s not designed to be created and destroyed from a thread and ticked from another. It might work, but you have no guarantee. If you have performance problems when using the api, we can discuss on particular performance issues. But it’s intended to be used from a game’s main thread, or if you will entirely from within a single spawned thread, that you must then synchronize in a safe maner with main thread. The callbacks themselves are not a means of thread synchronization.

Please, do clarify if I missed anything.
Cheers!

Thanks Ricard,

This makes sense! Given the above, what is the best wait to stop the ticker at the end if a async process, such as s failed auth?

I can stop it in my destructor, or in another synchronous function. But how can i end the thread at the end of an async function?

Just an update here. I decided to decouple the tick from the actual user session. I just start the tick on launch when i instantiate the client and stop the tick when the user closes my application. no need to associate the lifecycle of the tick with the the user’s auth and signout.

1 Like