Skip to content

Handle ObjectDisposedException when closing disposed WebSocket#194

Draft
Copilot wants to merge 2 commits intodevfrom
copilot/fix-websocket-dispose-error
Draft

Handle ObjectDisposedException when closing disposed WebSocket#194
Copilot wants to merge 2 commits intodevfrom
copilot/fix-websocket-dispose-error

Conversation

Copy link

Copilot AI commented Mar 3, 2026

WebSocketStream.OnShutdownAsync() calls CloseOutputAsync on an already-disposed ClientWebSocket, which throws ObjectDisposedException. The existing catch filter wraps it in a RelayException and rethrows — surfacing as an unhandled exception to callers.

A disposed WebSocket is already effectively closed, so this should be a no-op.

Changes

  • WebSocketStream.cs: Added catch (ObjectDisposedException) before the general catch in both OnShutdownAsync (primary fix) and OnCloseAsync (defense-in-depth). Logs as warning via HandledExceptionAsWarning, consistent with existing error handling patterns.
  • AssemblyInfo.cs: Added InternalsVisibleTo for the test assembly.
  • WebSocketStreamTests.cs: Tests that ShutdownAsync and CloseAsync do not throw on a disposed ClientWebSocket.
// Before: ObjectDisposedException gets wrapped in RelayException and rethrown
catch (Exception exception) when (!WebSocketExceptionHelper.IsRelayContract(exception))

// After: ObjectDisposedException caught first and handled gracefully
catch (ObjectDisposedException exception)
{
    RelayEventSource.Log.HandledExceptionAsWarning(this, exception);
}
catch (Exception exception) when (!WebSocketExceptionHelper.IsRelayContract(exception))
Original prompt

This section details on the original issue you should resolve

<issue_title>C# Client may try to close a disposed WebSocket</issue_title>
<issue_description>## Actual Behavior

  1. Lets me close a disposed socked which causes an exception
The service threw an unhandled exception, System.ObjectDisposedException: Cannot access a disposed object.
Object name: 'System.Net.WebSockets.ClientWebSocket'.
   at System.Net.WebSockets.ClientWebSocket.ThrowIfNotConnected()
   at System.Net.WebSockets.ClientWebSocket.CloseOutputAsync(WebSocketCloseStatus closeStatus, String statusDescription, CancellationToken cancellationToken)
   at Microsoft.Azure.Relay.WebSocketStream.<OnShutdownAsync>d__32.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 Microsoft.Azure.Relay.HybridConnectionStream.<ShutdownAsync>d__10.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()
  1. The problem is very similar to a singalIR issues C# Client may try to close a disposed WebSocket aspnet/SignalR#1004

await this.webSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, "CloseAsync", linkedCancelSource.Token).ConfigureAwait(false);

await this.webSocket.CloseOutputAsync(WebSocketCloseStatus.NormalClosure, "Shutdown", linkedCancelSource.Token).ConfigureAwait(false);

The real issue is that the websockets client throws :(
https://github.com/dotnet/runtime/blob/d6bf13a39c8ccb8fa7815a2f7e4387120a837138/src/libraries/System.Net.WebSockets.Client/src/System/Net/WebSockets/ClientWebSocket.cs#L226

Expected Behavior

  1. Do not throw on closing a disposed socket
  2. or throw? Doesn't seem correct since even singalIR addressed the issue.

Versions

  • OS platform and version:
  • .NET Version:
  • NuGet package version or commit ID:</issue_description>

<agent_instructions>Create a new branch with a test that reproduces this issue and this exception if possible. If there is an exception, create an appropriate fix. Finally, create a PR.</agent_instructions>

Comments on the Issue (you are @copilot in this section)


💬 We'd love your input! Share your thoughts on Copilot coding agent in our 2 minute survey.

In WebSocketStream.OnShutdownAsync(), calling CloseOutputAsync on an
already-disposed WebSocket throws ObjectDisposedException. This was
not properly handled - it got wrapped in a RelayException and rethrown.

Add explicit catch for ObjectDisposedException in both OnShutdownAsync
and OnCloseAsync to log the exception as a warning instead of propagating
it, since the WebSocket is already effectively closed/shut down.

Also add InternalsVisibleTo for the test assembly and a unit test that
verifies the fix.

Co-authored-by: jbjordan <108886350+jbjordan@users.noreply.github.com>
Copilot AI changed the title [WIP] Fix C# client closing disposed WebSocket issue Handle ObjectDisposedException when closing disposed WebSocket Mar 3, 2026
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

Successfully merging this pull request may close these issues.

C# Client may try to close a disposed WebSocket

2 participants