SocketIOUnity icon indicating copy to clipboard operation
SocketIOUnity copied to clipboard

Is it possible emitting 2 different events with ack callback at same time causes race condition issue?

Open KamilTheDev opened this issue 1 year ago • 3 comments
trafficstars

I've been having a very strange problem, in socket.OnConnected event, I'm emitting 2 events: get-player and get-clan that have a callback that processes the result from the server.

Sometimes rarely, the get-clan event is actually receiving the player data (that would be for get-player), and the get-player callback is never called. Other times, get-player will get the player data, but the callback for get-clan is never called. I have server logs that indicate the server is always getting the right data for each event, and in the cases where client never gets the get-player or get-clan callback called, the server did receive the request.

Is there any potential ways to accidently create issues when emitting different events with ack callbacks at the same time? Perhaps I'm doing something that I shouldn't.

KamilTheDev avatar Sep 25 '24 05:09 KamilTheDev

i have no code snippet of what u are trying to achieve,

but this is an example to how u can do it


    public async Task<(string playerData, string clanData)> GetPlayerAndClanAsync(object someObject)
    {
        var playerTaskCompletion = new TaskCompletionSource<string>();
        var clanTaskCompletion = new TaskCompletionSource<string>();

        // Emit get-player event and set the response to playerTaskCompletion
        socket.Emit("get-player", (response) =>
        {
            string playerData = response.GetValue<string>();
            playerTaskCompletion.TrySetResult(playerData);
        }, someObject);

        // Emit get-clan event and set the response to clanTaskCompletion
        socket.Emit("get-clan", (response) =>
        {
            string clanData = response.GetValue<string>();
            clanTaskCompletion.TrySetResult(clanData);
        }, someObject);

        // Await both tasks and return the tuple with both results
        string playerResult = await playerTaskCompletion.Task;
        string clanResult = await clanTaskCompletion.Task;

        return (playerResult, clanResult);
    }

usage:

var (playerData, clanData) = await socketManager.GetPlayerAndClanAsync(someObject);
Console.WriteLine($"Player Data: {playerData}");
Console.WriteLine($"Clan Data: {clanData}");

adjust the code to meet ur needs

itisnajim avatar Sep 25 '24 10:09 itisnajim

I imagine it might be from having one socket.emit being executed in Unity thread, and the other socket.emit is not. I was playing around with different ways.

void Start()
{
     // Connect to the server
    socket.Connect();
    Debug.Log("socket.Connect is called.");

    // Handle connection event
    socket.OnConnected += (sender, e) =>
    {
        UnityThread.executeInUpdate(() => OnConnected?.Invoke());

        socket.Emit("player-data", (response) =>
        {
            UnityThread.executeInUpdate(() =>
            {
                Debug.Log("Player data response: " + response.GetValue<string>();
            });
        });
    };
}

public async void ExampleOnConnectedSubscriber()
{
    SocketIOResponse response = await EmitAsync("clan-data");
    Debug.Log("Clan data response: " + response.GetValue<string>());
}

public static async ValueTask<SocketIOResponse> EmitAsync(string eventName, object data = null)
{
    var tcs = new TaskCompletionSource<SocketIOResponse>();

    await Instance.socket.EmitAsync(eventName, (response) =>
    {
        tcs.SetResult(response);
    }, data);

    return await tcs.Task;
}

I think ensuring they are both executed in either socket or Unity thread fixes the issue. I was also testing the idea of being able to simply await the socket callback.

For example, I don't really like the syntax, especially if you need to make more emits inside:

socket.Emit("player-data", (response) =>
{
    UnityThread.executeInUpdate(() =>
    {
        playerText.text = response.GetValue<string>();

          socket.Emit("other-data", (response) =>
          {
              UnityThread.executeInUpdate(() =>
              {
                  otherText.text = response.GetValue<string>();
              });
          });
    });
});

I much rather:

SocketIOResponse playerResponse = await EmitAsync("player-data");
playerText.text = playerResponse.GetValue<string>();

SocketIOResponse otherResponse= await EmitAsync("other-data");
otherText.text = otherResponse.GetValue<string>();

Is the way I implemented EmitAsync the correct way to be able to directly await the callback?

KamilTheDev avatar Sep 25 '24 11:09 KamilTheDev

try to not use unity thread when fetching ur needed data (get-player and get-clan), then after retrieving ur data run use unity thread

var (playerData, clanData) = await socketManager.GetPlayerAndClanAsync(someObject);
UnityThread.executeInUpdate(() =>
{
        // TODO
});

i have no code snippet of what u are trying to achieve,

but this is an example to how u can do it

    public async Task<(string playerData, string clanData)> GetPlayerAndClanAsync(object someObject)
    {
        var playerTaskCompletion = new TaskCompletionSource<string>();
        var clanTaskCompletion = new TaskCompletionSource<string>();

        // Emit get-player event and set the response to playerTaskCompletion
        socket.Emit("get-player", (response) =>
        {
            string playerData = response.GetValue<string>();
            playerTaskCompletion.TrySetResult(playerData);
        }, someObject);

        // Emit get-clan event and set the response to clanTaskCompletion
        socket.Emit("get-clan", (response) =>
        {
            string clanData = response.GetValue<string>();
            clanTaskCompletion.TrySetResult(clanData);
        }, someObject);

        // Await both tasks and return the tuple with both results
        string playerResult = await playerTaskCompletion.Task;
        string clanResult = await clanTaskCompletion.Task;

        return (playerResult, clanResult);
    }

usage:

var (playerData, clanData) = await socketManager.GetPlayerAndClanAsync(someObject);
Console.WriteLine($"Player Data: {playerData}");
Console.WriteLine($"Clan Data: {clanData}");

adjust the code to meet ur needs

itisnajim avatar Sep 26 '24 11:09 itisnajim