Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Emit from different places in code disconnects and reconnects socket #78

Open
64jcl opened this issue Dec 30, 2023 · 7 comments
Open

Emit from different places in code disconnects and reconnects socket #78

64jcl opened this issue Dec 30, 2023 · 7 comments

Comments

@64jcl
Copy link

64jcl commented Dec 30, 2023

If I have several Emit and EmitStringAsJSON in my code, even putting them in separate IEnumerator functions and calling them with StartCoroutine, it will suddenly disconnect the socket and reconnect it shortly after. Do I have to make a singleton and manage a list of all comms to the socket and send them one at a time from one place? I have tried putting try/catch around several of these emit calls but none of them even throw an exception so quite impossible to track whats going on. It feels like something inside is silencing exceptions or something.

EDIT: A look through SocketIO and classes there seem to have a lot of try/catch silencing.

@itisnajim
Copy link
Owner

maybe DontDestroyOnLoad can help, search how to do,
in Awake() method call:
DontDestroyOnLoad(transform.root.gameObject);

@64jcl
Copy link
Author

64jcl commented Dec 31, 2023

@itisnajim , not sure what you mean here. I have several scripts spread around on my objects but keep one object with a script that does the socket connection, using a singleton so that I can reach the socket everywhere. It seems that if I use this socket from different scripts it sometimes results in a disconnect (and no exception is thrown on emit either). I personally think it is because two IEnumerators call emit at the same time but from different threads. I thought IEnumerator calls were called max one at a time from the Unity thread but the only explanation I can find is that they are running concurrently and both calling socket.Emit at the same time then naturally does not work.

How do people solve this issue, do you manage all socket communication from one script only and just send some kind of action objects for it? Or does this socket implementation have queuing like this built in somehow?

Also shouldnt Emit throw some kind of exception when it fails instead of just killing off the socket connection?

EDIT: I read up a bit on Coroutines in Unity and it seems they have nothing to do with threads. From what I understand they are called from the main Unity thread so no two IEnumerator functions should run at the same time. So from this it really should be safe to call socket.Emit from different places without them ever crashing unless the socket library does its own threading for sending the packets, and it returns before the socket is ready for the next call?

@64jcl
Copy link
Author

64jcl commented Dec 31, 2023

Ok I narrowed this down to actually it crashing when two Emit's are sent shortly after each other like this:

        try {
            socket.Emit("mic", byteBuffer);
            Debug.Log("Sent!");
            socket.Emit("updateTest", "test");
            Debug.Log("Sent 2!");
        }
        catch (Exception e) {
            Dbg.Log("Exception: " + e.Message);
        }

This will cause the socket to disconnect, and no exception is thrown either, both log printouts are shown, so something is happening inside the socket library when two socket calls are too close to each other in time. I have tried using EmitAsync on both calls but same thing happens. Looking at your code I see that Emit just calls EmitAsync anyway, same with EmitStringAsJSON with an equivalent async option.

If I rearrange the order of these two Emit's it works just fine. Seeking that async is used for both it would seem to be a race condition then. How would you go about solving this? Is there a way to check if it is free for the next socket call?

@64jcl
Copy link
Author

64jcl commented Dec 31, 2023

Yes, clearly there is race conditions happening within the socket library. If I wait for 100ms after this first byte buffer is sent (it is around 40000 bytes lenght) before sending the other test emit it works fine.

        socket.Emit("mic", byteBuffer);
        Dbg.Log("Sent!");
        // wait for 100 ms
        yield return new WaitForSeconds(0.1f);
        socket.Emit("updateTest", "test");
        Dbg.Log("Sent updateTest!");

I have looked into SocketIO.cs and it would seem to me that the await are not actually waiting for the send to be completed for example this line here: await _transport.SendAsync(msg, cancellationToken).ConfigureAwait(false);

However if I instead just call EmitAsync directly in SocketIO and wait on the Task that is returned, it works fine:

        Task t = socket.EmitAsync("mic", byteBuffer);
        Dbg.Log("Sent!");
        yield return new WaitUntil(() => t.IsCompleted);
        socket.Emit("updateTest", "test");
        Dbg.Log("Sent updateTest!");

So this basically means I need to make some kind of socket dispatch queue myself then and send them one at a time myself.

@64jcl
Copy link
Author

64jcl commented Dec 31, 2023

Ok so here was my simple solution to this and it works just great:

    //...
    private Queue<SocketMessage> messageQueue = new Queue<SocketMessage>();

    IEnumerator sendMessages() {
        while (true) {
            if (isConnected && messageQueue.Count > 0) {
                SocketMessage message = messageQueue.Dequeue();
                Dbg.Log("Sending message: " + message.cmd);
                Task t = socket.EmitAsync(message.cmd, message.data);
                yield return new WaitUntil(() => t.IsCompleted);
            } else {
                yield return null;
            }
        }
    }

    public void emit(string cmd, object data) {
        messageQueue.Enqueue(new SocketMessage() { cmd = cmd, data = data });
    }
    
    void Start()
    {      
           // do your socket connect stuff and start the coroutine for dispatching messages
           StartCoroutine(sendMessages());
    }
    //...
}
    
[Serializable]
public class SocketMessage {
    public string cmd;
    public object data;
}   

@64jcl
Copy link
Author

64jcl commented Dec 31, 2023

While I am at it, take a look at the actual EmitAsync in SocketIO.cs, it has an oddball thingy where it tries to json serialize the data object no matter what it is and set OutgoingBytes as well as Json for a BinaryMessage. Not sure why this is, sounds to me like an extra unecessary performance degrading step if the sender already is sending just a byte array? Although seeing it actually passes the result.Json of the serializing as well it would seem this is needed after all. Would have to dig further to figure this one out though.

From what I understand the ConfigureAwait(false) causes it to actually not await for the socket to actually send all data in the emit.

public async Task EmitAsync(string eventName, CancellationToken cancellationToken, params object[] data)
        {
            if (data != null && data.Length > 0)
            {
                var result = JsonSerializer.Serialize(data);
                if (result.Bytes.Count > 0)
                {
                    var msg = new BinaryMessage
                    {
                        Namespace = Namespace,
                        OutgoingBytes = new List<byte[]>(result.Bytes),
                        Event = eventName,
                        Json = result.Json
                    };
                    await _transport.SendAsync(msg, cancellationToken).ConfigureAwait(false);
                }
                else
                {
                    var msg = new EventMessage
                    {
                        Namespace = Namespace,
                        Event = eventName,
                        Json = result.Json
                    };
                    await _transport.SendAsync(msg, cancellationToken).ConfigureAwait(false);
                }
            }
            else
            {
                var msg = new EventMessage
                {
                    Namespace = Namespace,
                    Event = eventName
                };
                await _transport.SendAsync(msg, cancellationToken).ConfigureAwait(false);
            }
        }

@itisnajim
Copy link
Owner

itisnajim commented Feb 15, 2024

This question is about using the socket.io-client-csharp library, as i mentioned in readme the SocketIOUnity serves as a wrapper for this library.

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

No branches or pull requests

2 participants