-
Notifications
You must be signed in to change notification settings - Fork 10.3k
[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
Conversation
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.
- Where are my karma points for this @SteveSandersonMS?
9648c6d
to
f1f7b49
Compare
src/Components/test/E2ETest/ServerExecutionTests/ServerAuthTest.cs
Outdated
Show resolved
Hide resolved
|
||
Browser.Manage().Cookies.DeleteCookieNamed("WebSockets.Identifier"); | ||
Browser.Manage().Cookies.AddCookie(new Cookie("WebSockets.Identifier", SessionIdentifier)); | ||
Browser.Navigate().Refresh(); |
There was a problem hiding this comment.
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)
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
src/Components/test/E2ETest/ServerExecutionTests/ServerReconnectionTest.cs
Show resolved
Hide resolved
src/Components/test/E2ETest/ServerExecutionTests/ServerSideAppTest.cs
Outdated
Show resolved
Hide resolved
src/Components/test/testassets/BasicTestApp/ReconnectTicker.razor
Outdated
Show resolved
Hide resolved
src/Components/test/testassets/TestServer/Infrastructure/InterruptibleWebSocket.cs
Show resolved
Hide resolved
return await await Task.WhenAny( | ||
_disabledReceiveTask.Task, | ||
Socket.ReceiveAsync(buffer, cancellationToken)); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
...ents/test/testassets/TestServer/Infrastructure/InterruptibleWebSocketAppBuilderExtensions.cs
Outdated
Show resolved
Hide resolved
src/Components/test/testassets/TestServer/Infrastructure/InterruptibleWebSocket.cs
Show resolved
Hide resolved
|
||
namespace Components.TestServer.Infrastructure | ||
{ | ||
public class InterruptibleWebSocket : WebSocket |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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).
There was a problem hiding this comment.
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.
src/Components/test/testassets/TestServer/Infrastructure/InterruptibleWebSocketFeature.cs
Show resolved
Hide resolved
💯 🥇 |
src/Components/test/E2ETest/ServerExecutionTests/CircuitGracefulTerminationTests.cs
Outdated
Show resolved
Hide resolved
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? |
f1f7b49
to
84148c4
Compare
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. |
84148c4
to
b38dabe
Compare
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(); | ||
|
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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 ")) |
There was a problem hiding this comment.
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}"; | ||
} |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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)
There was a problem hiding this 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?
|
This was true before too. Can you go into more detail about the other things? |
No it wasn't
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. |
Yeah, you're right! Sorry for mixing that up. |
I'm guessing that whether or not we proceed with this PR depends on whether we're proceeding with #12853. |
@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. |
@SteveSandersonMS It also represents the scenario more accurately than the client closing the connection. |
If it's a tradeoff between:
... 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. |
Closing as we'll leave the test code as-is around and we have an alternative approach |