Skip to content

#930 Try to close WebSocket destination when state is Open or CloseReceived #2091

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

Merged
merged 20 commits into from
Jun 12, 2025

Conversation

hogwartsdeveloper
Copy link
Contributor

@hogwartsdeveloper hogwartsdeveloper commented Jun 8, 2024

Fixes #930

Proposed Changes

  • error occurs when trying to close connections but destination status == abort
Screenshot 2024-06-09 at 02 29 46

@raman-m raman-m added bug Identified as a potential bug Websockets Ocelot feature: Websockets 2023 Annual 2023 release labels Jun 10, 2024
@raman-m raman-m added this to the Annual 2023 milestone Jun 10, 2024
@raman-m
Copy link
Member

raman-m commented Jun 10, 2024

Zhannur, thank you for the pull request!

Firstly, have you managed to reproduce the issue through tests?
I understand you've debugged the problem during runtime, but we need to verify the bug with tests.
Therefore, unit and acceptance tests are necessary before the team can proceed with a review.

P.S.

Please, update your develop branch in forked repo, and pay attention I've rebased the feature branch, so you need to re-checkout the branch locally.

@PaulARoy
Copy link
Contributor

PaulARoy commented Aug 26, 2024

Hello, is this branch only missing tests to fix the bug? Can we contribute and if so, what would be the best way to do it? (Fork the branch on our side?)

@raman-m
Copy link
Member

raman-m commented Aug 26, 2024

@PaulARoy Hi Paul!

Hello, is this branch only missing tests to fix the bug?

Yes, it is. The lack of tests breaches our Dev Process (refer to point 5). I cannot proceed with a code review without tests, as we cannot be certain that the bug fix is effective.

Can we contribute and if so, what would be the best way to do it? (Fork the branch on our side?)

The optimal approach is to pair-program with the author @hogwartsdeveloper to gain access to his forked repository. He will need to add you as a collaborator to his repository. If this is not feasible, please inform me again. We should wait a few weeks for him to add you as a collaborator.

@raman-m raman-m added Oct'24 October 2024 release and removed 2023 Annual 2023 release labels Aug 26, 2024
@raman-m raman-m modified the milestones: Annual 2023, September'24 Aug 26, 2024
@raman-m
Copy link
Member

raman-m commented Aug 26, 2024

@hogwartsdeveloper Hello Zhannur!
Are you available? I requested that you write tests for this PR nearly three months ago.

Do you plan to complete your PR with tests?
If not, please add @PaulARoy as a collaborator to your forked repository here to assist with tests.

P.S. Your PRs have been moved to the September'24 milestone.

@raman-m raman-m added Spring'25 Spring 2025 release and removed Oct'24 October 2024 release labels Oct 26, 2024
@raman-m raman-m modified the milestones: October'24, Autumn'24 Oct 26, 2024
@raman-m raman-m force-pushed the develop branch 7 times, most recently from b0cdbd6 to fa54613 Compare April 3, 2025 11:23
@raman-m raman-m force-pushed the issues-930 branch 7 times, most recently from 3e7aa1a to 99b8f68 Compare June 3, 2025 15:38
Copy link
Member

@raman-m raman-m left a comment

Choose a reason for hiding this comment

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

Ready for delivery ✅

  • Code reviews ✔️
  • Unit testing ✔️
  • Acceptance testing ✔️
  • Manual testing ☑️ Added draft version of the Bug0930.html webpage which is useful for JavaScript WebSocket connection testing.
  • Docs ☑️ Updates are not required due to the nature of the bug fix.

TODO

Manual testing has revealed more intricate issues in the middleware and Websockets feature when the clients are web browsers.

Comment on lines +150 to +153
protected virtual Task TryCloseOutputAsync(WebSocket webSocket, WebSocketCloseStatus closeStatus, string statusDescription, CancellationToken cancellation)
=> (webSocket.State == WebSocketState.Open || webSocket.State == WebSocketState.CloseReceived)
? webSocket.CloseOutputAsync(closeStatus, statusDescription, cancellation)
: Task.CompletedTask;
Copy link
Member

Choose a reason for hiding this comment

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

Actual bug fix ❕ ⚠️

Comment on lines +68 to +71
// DON'T THROW, NEVER! Just log the warning...
// The logging level has been decreased from level 4 (Error) to level 3 (Warning) due to the high number of disconnecting events for sensitive WebSocket connections in unstable networks.
Logger.LogWarning(() => $"{nameof(WebSocketException)} when {nameof(e.WebSocketErrorCode)} is {e.WebSocketErrorCode}");
return; // swallow the error
Copy link
Member

@raman-m raman-m Jun 12, 2025

Choose a reason for hiding this comment

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

We no longer throw WebSocketException objects because open WebSocket connections are long-running tasks that run alongside regular short-term HTTP tasks. This should improve Ocelot Core stability, which is crucial for containerized environments.

return;
}

await destination.SendAsync(new ArraySegment<byte>(buffer, 0, result.Count), result.MessageType, result.EndOfMessage, cancellationToken);
if (destination.State == WebSocketState.Open)
Copy link
Member

Choose a reason for hiding this comment

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

This if-statement should, in fact, be incorporated as a condition within the while loop.

@@ -118,13 +107,13 @@ private async Task Proxy(HttpContext context, DownstreamRequest request, Downstr
client.Options.AddSubProtocol(protocol);
}

foreach (var headerEntry in context.Request.Headers)
foreach (var header in context.Request.Headers)
Copy link
Member

Choose a reason for hiding this comment

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

Conducting a comprehensive review is necessary...

Comment on lines +140 to +143
using var server = await context.WebSockets.AcceptWebSocketAsync(client.SubProtocol);
await Task.WhenAll(
PumpAsync(client.ToWebSocket(), server, DefaultWebSocketBufferSize, context.RequestAborted),
PumpAsync(server, client.ToWebSocket(), DefaultWebSocketBufferSize, context.RequestAborted));
Copy link
Member

Choose a reason for hiding this comment

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

After accepting, the WebSocket connection should be converted into a "tunnel" object and stored in shared storage for all connections. Currently, both pumping tasks are long-running, blocking the first CONNECT-request in the middleware. In the logs, this appears as a hung thread, waiting for both tasks to complete or an exception to be generated.

Comment on lines +140 to +146
[Theory]
[Trait("Bug", "930")]
[Trait("PR", "2091")] // https://github.com/ThreeMammals/Ocelot/pull/2091
[InlineData("ws", "corefx-net-http11.azurewebsites.net", 80, "/WebSocket/EchoWebSocket.ashx")] // https://learn.microsoft.com/en-us/dotnet/fundamentals/networking/websockets#differences-in-http11-and-http2-websockets
[InlineData("wss", "echo.websocket.org", 443, "/")] // https://websocket.org/tools/websocket-echo-server/
[InlineData("wss", "ws.postman-echo.com", 443, "/raw")] // https://blog.postman.com/introducing-postman-websocket-echo-service/
public async Task Http11Client_ConnectionClosedPrematurely_ShouldCloseSocketsWithoutExceptions(string scheme, string host, int port, string path)
Copy link
Member

Choose a reason for hiding this comment

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

These are the main acceptance tests, but they only verify a single echo cycle. Therefore, a standard test with multiple echo cycles is necessary, though not specifically for bug fix of #930.

Comment on lines +322 to +325
[Fact]
[Trait("Bug", "930")]
[Trait("PR", "2091")] // https://github.com/ThreeMammals/Ocelot/pull/2091
public async Task PumpAsync_OperationCanceledException_ClosedDestinationSocket()
Copy link
Member

Choose a reason for hiding this comment

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

These, along with the ones below, are the primary unit tests.

@raman-m raman-m merged commit e527f27 into ThreeMammals:develop Jun 12, 2025
2 checks passed
raman-m added a commit that referenced this pull request Jun 12, 2025
… actions: adding DNS records and installing certificates
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Identified as a potential bug high High priority Spring'25 Spring 2025 release Websockets Ocelot feature: Websockets
Projects
None yet
Development

Successfully merging this pull request may close these issues.

The WebSocket is in an invalid state (Aborted) for this operation. Valid states are: Open, CloseReceived
6 participants