Skip to content

System.ObjectDisposedException when the request is already disposed #223

@cswpy

Description

@cswpy

Describe the bug

Below is the logs when I have verbose logging turned on:

[18:56:04 WARN JunimoServer] Error handling request: System.ObjectDisposedException: Cannot access a disposed object.
Object name: 'System.Net.HttpListenerResponse'.
at System.Net.HttpListenerResponse.CheckDisposed()
at System.Net.HttpListenerResponse.set_StatusCode(Int32 value)
at JunimoServer.Services.Api.ApiService.HandleRequestAsync(HttpListenerContext context) in /src/mod/JunimoServer/Services/Api/ApiService.cs:line 654
[18:56:04 WARN JunimoServer] Failed to write error response: System.ObjectDisposedException: Cannot access a disposed object.
Object name: 'System.Net.HttpListenerResponse'.
at System.Net.HttpListenerResponse.CheckDisposed()
at System.Net.HttpListenerResponse.set_StatusCode(Int32 value)
at JunimoServer.Services.Api.ApiService.HandleRequestAsync(HttpListenerContext context) in /src/mod/JunimoServer/Services/Api/ApiService.cs:line 684
[18:57:10 WARN JunimoServer] Error handling request: System.ObjectDisposedException: Cannot access a disposed object.
Object name: 'System.Net.HttpListenerResponse'.
at System.Net.HttpListenerResponse.CheckDisposed()
at System.Net.HttpListenerResponse.set_StatusCode(Int32 value)
at JunimoServer.Services.Api.ApiService.HandleRequestAsync(HttpListenerContext context) in /src/mod/JunimoServer/Services/Api/ApiService.cs:line 654
[18:57:10 WARN JunimoServer] Failed to write error response: System.ObjectDisposedException: Cannot access a disposed object.
Object name: 'System.Net.HttpListenerResponse'.
at System.Net.HttpListenerResponse.CheckDisposed()
at System.Net.HttpListenerResponse.set_StatusCode(Int32 value)
at JunimoServer.Services.Api.ApiService.HandleRequestAsync(HttpListenerContext context) in /src/mod/JunimoServer/Services/Api/ApiService.cs:line 684

Steps to reproduce

I have not tried to reproduce myself, I would assume something along the lines of:

  • keep the server running
  • send a WebSocket request to the API endpoint and then immediately terminate it

Because the WebSocket request fails and we dispose it, any attempt later in catch and finally block to assign status code to the response will result in a ObjectDisposedException.

Expected behavior

Expected a log to be printed, but no an uncaught exception.

Additional context

The explanation below is generated by AI, but I have verified its correctness.

The issue is in the HandleRequestAsync method's error handling flow when a WebSocket upgrade request comes in on the /ws path.

Root Cause
Look at this sequence in HandleRequestAsync:

stardew-valley-dedicated-server / server / mod / JunimoServer / Services / Api / ApiService.cs
v1
// Handle WebSocket upgrade
if (path == "/ws" && request.IsWebSocketRequest)
{
await HandleWebSocketAsync(context);
return;
}
When a WebSocket connection is established via HandleWebSocketAsync, the HTTP response is consumed and disposed as part of the WebSocket upgrade handshake (context.AcceptWebSocketAsync). The method then returns.

However, the finally block always runs:

finally
{
    try { response.Close(); }
    catch (Exception closeEx)
    {
        Monitor.Log($"Failed to close response: {closeEx}", LogLevel.Debug);

The response.Close() call on an already-disposed response object throws ObjectDisposedException. That exception is caught and logged at Debug level — but that's not what's causing the WARN logs.

The real problem is: if HandleWebSocketAsync itself throws an exception (e.g., the client disconnects abruptly during the upgrade, or AcceptWebSocketAsync fails), execution falls into the outer catch block:

catch (Exception ex)
{
    Monitor.Log($"Error handling request: {ex}", LogLevel.Warn);  // ← line ~654 in stack
    try
    {
        response.StatusCode = 500;  // ← ObjectDisposedException!

At this point the response has already been disposed by the WebSocket upgrade, so setting response.StatusCode = 500 throws ObjectDisposedException, which triggers the inner catch — producing both WARN log lines you see.

Final checks

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions