Skip to content
New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

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

Open
KamilTheDev opened this issue Sep 25, 2024 · 3 comments

Comments

@KamilTheDev
Copy link

KamilTheDev commented Sep 25, 2024

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.

@itisnajim
Copy link
Owner

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

@KamilTheDev
Copy link
Author

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?

@itisnajim
Copy link
Owner

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

# for free to join this conversation on GitHub. Already have an account? # to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants