Skip to content

[dotnet-watch] Fix WebSocket transport crash on Ctrl+R restart#53648

Merged
jonathanpeppers merged 2 commits intorelease/10.0.3xxfrom
dev/peppers/dotnet-watch-ctrl-r
Apr 1, 2026
Merged

[dotnet-watch] Fix WebSocket transport crash on Ctrl+R restart#53648
jonathanpeppers merged 2 commits intorelease/10.0.3xxfrom
dev/peppers/dotnet-watch-ctrl-r

Conversation

@jonathanpeppers
Copy link
Copy Markdown
Member

@jonathanpeppers jonathanpeppers commented Mar 31, 2026

Fixes #53634

When a mobile/WebSocket app is shut down (Ctrl+C) or restarted (Ctrl+R), the child process is killed which closes the TCP connection. Kestrel tears down the HTTP context, and then RunningProject.DisposeAsync tries to dispose the WebSocket — hitting an ObjectDisposedException on the already-disposed IFeatureCollection. The ListenForResponsesAsync loop also logs a spurious WebSocketException error.

Fixes:

  • RequestHandler.Dispose: catch ObjectDisposedException when the underlying Kestrel HTTP context is already torn down
  • IsExpectedConnectionTermination: treat WebSocketException as expected when the socket state is Aborted (remote disconnected)

Tests:

  • CtrlC_ShutsDownCleanly: verifies clean shutdown with WebSocket transport
  • CtrlR_RestartsCleanly: verifies restart without WebSocketException or ObjectDisposedException errors

Both tests fail without the fix.

Fixes #53634

When restarting a mobile/WebSocket app via Ctrl+R, the child process is
killed which closes the TCP connection. Kestrel tears down the HTTP
context, and then RunningProject.DisposeAsync tries to dispose the
WebSocket — hitting an ObjectDisposedException on the already-disposed
IFeatureCollection. The ListenForResponsesAsync loop also logs a
spurious WebSocketException error.

Fixes:
- RequestHandler.Dispose: catch ObjectDisposedException when the
  underlying Kestrel HTTP context is already torn down
- IsExpectedConnectionTermination: treat WebSocketException as expected
  when the socket state is Aborted (remote disconnected)

Tests:
- CtrlC_ShutsDownCleanly: verifies clean shutdown with WebSocket
  transport (already worked, added for coverage)
- CtrlR_RestartsCleanly: verifies restart without WebSocketException
  or ObjectDisposedException errors (was broken, now passes)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@jonathanpeppers jonathanpeppers requested review from a team and tmat as code owners March 31, 2026 22:31
Copilot AI review requested due to automatic review settings March 31, 2026 22:31
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Fixes a dotnet-watch shutdown/restart crash path specific to WebSocket-based hot reload transports (mobile scenarios), where killing the child process can tear down Kestrel’s HTTP context before the WebSocket transport is disposed.

Changes:

  • Treat ObjectDisposedException during WebSocket transport disposal/reads as expected in relevant scenarios.
  • Treat WebSocketException as expected when the client socket is in Aborted state to avoid spurious error logs.
  • Add dotnet-watch tests covering Ctrl+C shutdown and Ctrl+R restart with WebSocket transport.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.

File Description
test/dotnet-watch.Tests/HotReload/MobileHotReloadTests.cs Adds Ctrl+C/Ctrl+R regression tests for WebSocket hot reload transport.
src/Dotnet.Watch/HotReloadClient/WebSocketClientTransport.cs Makes WebSocket transport shutdown/restart more resilient by handling expected disposal/abort exceptions.

Comment thread test/dotnet-watch.Tests/HotReload/MobileHotReloadTests.cs
Comment thread test/dotnet-watch.Tests/HotReload/MobileHotReloadTests.cs
Comment thread test/dotnet-watch.Tests/HotReload/MobileHotReloadTests.cs
Comment thread test/dotnet-watch.Tests/HotReload/MobileHotReloadTests.cs
- ClearOutput before SendControlC/SendControlR to avoid matching
  stale output
- Assert exit code 0 specifically in Ctrl+C test
- Use WaitForOutputLineContaining for post-restart Started assertion

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@jonathanpeppers jonathanpeppers merged commit 2086764 into release/10.0.3xx Apr 1, 2026
28 checks passed
@jonathanpeppers jonathanpeppers deleted the dev/peppers/dotnet-watch-ctrl-r branch April 1, 2026 17:54
@tmat tmat modified the milestones: 10.0.3, 10.0.3xx Apr 15, 2026
jonathanpeppers added a commit that referenced this pull request Apr 27, 2026
Fixes: #53634

When a mobile/WebSocket app is shut down (Ctrl+C) or restarted (Ctrl+R), the child process is killed which closes the TCP connection. Kestrel tears down the HTTP context, and then `RunningProject.DisposeAsync` tries to dispose the WebSocket — hitting an `ObjectDisposedException` on the already-disposed `IFeatureCollection`. The `ListenForResponsesAsync` loop also logs a spurious `WebSocketException` error.

**Fixes:**
- `RequestHandler.Dispose`: catch `ObjectDisposedException` when the underlying Kestrel HTTP context is already torn down
- `IsExpectedConnectionTermination`: treat `WebSocketException` as expected when the socket state is `Aborted` (remote disconnected)

**Tests:**
- `CtrlC_ShutsDownCleanly`: verifies clean shutdown with WebSocket transport
- `CtrlR_RestartsCleanly`: verifies restart without `WebSocketException` or `ObjectDisposedException` errors

Both tests fail without the fix.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
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.

3 participants