doghappy / socket.io-client-csharp

socket.io-client implemention for .NET
MIT License
715 stars 124 forks source link

`System.ObjectDisposedException` while sending payload. #331

Closed larryr1 closed 11 months ago

larryr1 commented 11 months ago

This is a really weird one... I'm trying to send thumbnails of my desktop to a server over the socket connection. The process goes like so:

When calling response.CallbackAsync(reply.StringData) I get a System.ObjectDisposedExcetion. Stack trace:

String data has length of 518344
Sending reply.
Exception thrown: 'System.ObjectDisposedException' in mscorlib.dll
Handler threw System.ObjectDisposedException: The semaphore has been disposed.
   at System.Threading.SemaphoreSlim.CheckDispose()
   at System.Threading.SemaphoreSlim.Release(Int32 releaseCount)
   at SocketIOClient.Transport.WebSockets.WebSocketTransport.<SendAsync>d__13.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at SocketIOClient.Transport.BaseTransport.<SendAsync>d__29.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at SocketIOClient.SocketIO.<ClientAckAsync>d__107.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at SocketIOClient.SocketIOResponse.<CallbackAsync>d__17.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter.GetResult()
   at Rustera.Classes.EventManager.EventManager.<socketEvent>d__10.MoveNext() in C:\Users\larry\source\repos\Rustera\Rustera\Classes\EventManager\EventManager.cs:line 143

Here is the code that is responsible for routing the event and responding to the callback. I have omitted several try/catch and if statements for brevity.

#pragma warning disable IDE1006 // Naming Styles
private static async void socketEvent(string eventName, SocketIOResponse response)
#pragma warning restore IDE1006 // Naming Styles

{
    Debug.WriteLine("Got event. " + eventName);

    // The SocketIOResponse class is really confusing and undocumented. I had to look at it's source to figure out
    // what it's method and properties were.
    // I'm only doing it this way because I can't synchronously access arguments in the same event if I use the documented way
    // in the repository's README. Below is the reference for the class.
    // https://github.com/doghappy/socket.io-client-csharp/blob/5a7b8e331908edcd722eca955d249d1f0e8656aa/src/SocketIOClient/SocketIOResponse.cs#L9

    // The response has each argument sent from the server in a list.
    // You can access each element of this list by calling response.getValue() with it's index.
    // I ignore all other elements because the server should be sending arguments in a JSON object as the first event arugment.
    string potentialJson = response.GetValue(0).ToString();

    // Create the object
    JObject json;
    json = JObject.Parse(potentialJson);

    // Args for handler
    SocketEventArgs eventArgs = new SocketEventArgs
    {
        EventName = eventName,
        Argument = json
    };

    // Get handler for this event
    ISocketEventHandler handler = eventHandlers.Find(h => h.EventName == eventName);

    // Execute handler
    try
    {
        SocketEventReply reply = handler.HandleEvent(eventArgs);
        Debug.WriteLine("String data has length of " + reply.StringData.Length);
        if (reply.StringData != null)
        {
            Debug.WriteLine("Sending reply.");
            await response.CallbackAsync(reply.StringData);
        }

    } catch (Exception ex)
    {
        Debug.WriteLine("Handler threw " + ex.ToString());
    }
}

This is the code that sets up the event handler.

public static void Initialize()
{
    socketManager = new SocketManager();
    OnAnyHandler handler = new OnAnyHandler(socketEventThreadRunner);
    socketManager.GetClient().OnAny(handler);
    socketManager.GetClient().ConnectAsync().Wait();
}

private static void Io_OnConnected(object sender, EventArgs e)
{
    Console.WriteLine("IO Connected to server");
}

private static void socketEventThreadRunner(string eventName, SocketIOResponse response)
{
    Thread thread = new Thread(() => { socketEvent(eventName, response); });
    thread.Start();
}

private static async void socketEvent(string eventName, SocketIOResponse response) {...}

I am using undocumented classes for some of the methods, but it appears that I am allowed to use them because they are public and they appear in the overloads for things like client.onAny(...).

I'm really lost on what to do next here. Any help is greatly appreciated.

larryr1 commented 11 months ago

I've done some more research. It turns out, Socket.IO has a serverside maximum buffer size to prevent clients trying to overload the server. The amount of data I was sending (~0.5MB) was violating this limit. Adjusting my server's configuration to allow larger packet sizes fixed the problem.