Skip to content

[Blazor][Fixes #12788] Fixes blazor reconnection tests #12813

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

Closed
wants to merge 5 commits into from

Conversation

javiercn
Copy link
Member

@javiercn javiercn commented Aug 1, 2019

  • Enables a customer websocket feature in tests that can be used to trigger errors on the server at will.
  • Enables conditionally turning errors through cookies so that normal tests aren't affected and run against the product bits.
  • Cleans up dead code in blazor.server.js that is not longer required.
  • Moves the reconnection tests out of ComponentsApp.App into basic test app.

@javiercn javiercn requested a review from rynowak August 1, 2019 18:51
@javiercn javiercn force-pushed the javiercn/fix-reconnection-tests branch from 9648c6d to f1f7b49 Compare August 1, 2019 18:56

Browser.Manage().Cookies.DeleteCookieNamed("WebSockets.Identifier");
Browser.Manage().Cookies.AddCookie(new Cookie("WebSockets.Identifier", SessionIdentifier));
Browser.Navigate().Refresh();
Copy link
Member

Choose a reason for hiding this comment

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

Do we want to just replace this with navigation + reload? (line 34)

Copy link
Member Author

Choose a reason for hiding this comment

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

Did you mean line 32? I don't see why

Copy link
Member

Choose a reason for hiding this comment

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

I mean line 30. Do we need to navigate to the root and then trigger a refresh?

Copy link
Member Author

Choose a reason for hiding this comment

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

In the server-side case yes because the connection already got established by the time we set the cookies.

These tests don't run client-side.

return await await Task.WhenAny(
_disabledReceiveTask.Task,
Socket.ReceiveAsync(buffer, cancellationToken));
}
Copy link
Member

Choose a reason for hiding this comment

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


namespace Components.TestServer.Infrastructure
{
public class InterruptibleWebSocket : WebSocket
Copy link
Member

Choose a reason for hiding this comment

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

I think this deserves some comments and how it works and why it's needed.

Copy link
Member Author

Choose a reason for hiding this comment

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

I'll add a comment explaining how this works

Copy link
Member

Choose a reason for hiding this comment

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

Definitely. Without studying this in detail, I have absolutely no idea why this is the solution to our problems. I don't doubt that it works, but I can't guess whether this is exploiting some random implementation detail of SignalR that might change at any time, or whether it's fundamental to the idea of WebSockets that they can be "interrupted" in some officially understood way.

Copy link
Member Author

Choose a reason for hiding this comment

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

Added some comments.

it's fundamental to the idea of WebSockets that they can be "interrupted" in some officially understood way.

Is exactly that, if we throw an exception from the transport that signalr is not aware of, it will propagate it. I chose to throw an IOException but I could have chosen to throws something else. The one that signalr catches and handles gracefully is WebsocketException when reason = ClosedWithoutSendingCloseFrame

Copy link
Contributor

Choose a reason for hiding this comment

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

The one that signalr catches and handles gracefully is WebsocketException when reason = ClosedWithoutSendingCloseFrame

This is a behavior we've gone back and forth about. Customers have been adamant that this kind of error is too noisy, but it still feels wrong that we suppress it. The reality is that any failure to complete the closing process is considered an error by the spec.

Also, the underlying WebSocket implementation won't ever actually produce IOExceptions. It explicitly wraps them in WebSocketExceptions with the error code ConectionClosedPrematurely.

Perhaps having a way for a Hub to indicate that it's willing to see even these more "noisy" failures is appropriate. For a system like Blazor it makes sense to want to tell the difference here. I think we should've left this error in the OnDisconnectedAsync handler instead of suppressing it. Maybe 3.0 can justify changing that behavior but we'd need quite a bit of backup to get that through ask mode.

Copy link
Member Author

Choose a reason for hiding this comment

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

I do have an alternative for graceful reconection that uses the send beacon API
#12853

@anurse This might be something you folks want to consider doing in signalr directly as it turns out it works pretty well, and would give you the ability to signal the end of the connection in most/all transports. (Given that you have a ping for all of them).

Copy link
Contributor

Choose a reason for hiding this comment

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

This might be something you folks want to consider doing in signalr directly as it turns out it works pretty well

Yep, we've talked about using sendBeacon. In the current landscape I think it's entirely appropriate for Blazor to use it because of the limitations I described above (SignalR suppresses ungraceful disconnect errors in WebSockets).

However, I do think that the WebSocket close handshake (or lack thereof) should be enough for SignalR to distinguish graceful vs ungraceful disconnects. In fact, it is, but SignalR doesn't manifest it properly to a consumer (such as Blazor). In those cases, sendBeacon shouldn't even be necessary. A little more investigation may be needed (perhaps you've already done it and I didn't find the results) since I don't know off-hand if the close handshake works when the browser closes the tab, but we (SignalR) should be doing that work.

Anyway, glad to hear you have an alternative in the works. The rest is on us I think.

@Pilchie Pilchie added the area-blazor Includes: Blazor, Razor Components label Aug 1, 2019
@SteveSandersonMS
Copy link
Member

Where are my karma points for this @SteveSandersonMS?

💯 🥇

@SteveSandersonMS
Copy link
Member

This may well be good (though to be clear, I'll await more explanation before thinking I understand it).

However, do you think you'd be able to check the current real-world behavior of the product code before we consider this to be a solution?

@javiercn javiercn force-pushed the javiercn/fix-reconnection-tests branch from f1f7b49 to 84148c4 Compare August 2, 2019 12:28
@analogrelay
Copy link
Contributor

However, do you think you'd be able to check the current real-world behavior of the product code before we consider this to be a solution?

I think this is the right approach. We need to dive in to the real-world behavior and SignalR likely also needs to consider changes here.

@javiercn javiercn force-pushed the javiercn/fix-reconnection-tests branch from 84148c4 to b38dabe Compare August 3, 2019 17:25
@javiercn
Copy link
Member Author

javiercn commented Aug 3, 2019

I rebased this PR and removed all the copied websocket code. This should be good to go. Independently of what we end up doing with graceful disconnection, we want to take this PR as it offers advantages for testing reconnection. (Mainly no test stuff in product code) and cleans up our tests.

Browser.Manage().Cookies.DeleteCookieNamed("WebSockets.Identifier");
Browser.Manage().Cookies.AddCookie(new Cookie("WebSockets.Identifier", SessionIdentifier));
Browser.Navigate().Refresh();

Copy link
Member

Choose a reason for hiding this comment

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

Is there a drawback to this running for the client-side tests?

Copy link
Member Author

Choose a reason for hiding this comment

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

These test don't run on the client, when they do, we can simply key this off to whether or not we are running server-side.

string script,
Func<object, T> converter = null)
{
if(!script.StartsWith("return "))
Copy link
Member

Choose a reason for hiding this comment

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

format

if(!script.StartsWith("return "))
{
script = $"return {script}";
}
Copy link
Member

Choose a reason for hiding this comment

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

Do we need this level of cleverness?

Copy link
Member Author

Choose a reason for hiding this comment

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

Yes, because it saves you from a common pitfall. (I spent like 1h on this the other day, couldn't figure out why I was only seeing null)

Copy link
Member

@rynowak rynowak left a comment

Choose a reason for hiding this comment

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

This seems like a much more reasonable set of pieces to maintain. Thanks for digging into this further.

Is the idea that using fault injection in the websocket is a much better approach than forcing the client-side code to disconnect? What are the advantages?

@javiercn
Copy link
Member Author

javiercn commented Aug 3, 2019

Is the idea that using fault injection in the websocket is a much better approach than forcing the client-side code to disconnect? What are the advantages?

  • No test code in product code
  • We can decide for how long we fail
  • The failure pattern, etc.

@rynowak
Copy link
Member

rynowak commented Aug 3, 2019

No test code in product code

This was true before too.

Can you go into more detail about the other things?

@javiercn
Copy link
Member Author

javiercn commented Aug 3, 2019

This was true before too.

No it wasn't

https://github.com/aspnet/AspNetCore/pull/12813/files#diff-fdbaeed1d036d944c000f3ef1aa7d066L92

Can you go into more detail about the other things?

We simply disable the socket completely right now, but with this approach we can prevent the client from reconnecting as many times as we want, or force intermittent re-connections.

By controlling it on the server we get more a more realistic scenario than triggering the connection close from the client side. We could for example choose to trigger the connection failure at different points.

@rynowak
Copy link
Member

rynowak commented Aug 3, 2019

No it wasn't

Yeah, you're right! Sorry for mixing that up.

@SteveSandersonMS
Copy link
Member

I'm guessing that whether or not we proceed with this PR depends on whether we're proceeding with #12853.

@javiercn
Copy link
Member Author

javiercn commented Aug 5, 2019

@SteveSandersonMS I still want to get this one in, as I think it cleans-up test code from product code and that's worth it.

@javiercn
Copy link
Member Author

javiercn commented Aug 5, 2019

@SteveSandersonMS It also represents the scenario more accurately than the client closing the connection.

@SteveSandersonMS
Copy link
Member

If it's a tradeoff between:

  1. Having the forceDisconnect method on _internal in the blazor.server.js
  2. Or having all this infrastructure for interrupting a websocket connection

... then given how massively more complex #2 is I'm not totally convinced it's a pragmatic choice. The internals of how websockets work are meant to be transparent to us. If this capability existed in SignalR then sure we'd use it, but since it's not I'm wary about us owning it. Let's discuss in the sync later.

@javiercn
Copy link
Member Author

javiercn commented Aug 5, 2019

Closing as we'll leave the test code as-is around and we have an alternative approach

@javiercn javiercn closed this Aug 5, 2019
@dougbu dougbu deleted the javiercn/fix-reconnection-tests branch May 18, 2020 19:43
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area-blazor Includes: Blazor, Razor Components
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants