Skip to content

Refactor streaming from client to server #4559

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 1 commit into from
Jan 11, 2019
Merged

Conversation

BrennanConroy
Copy link
Member

@BrennanConroy BrennanConroy commented Dec 10, 2018

Fixes https://github.com/aspnet/SignalR/issues/2592
Fixes #4401

TODO:

  • Couple more tests
  • Update spec with new behavior

@BrennanConroy BrennanConroy added the area-signalr Includes: SignalR clients and servers label Dec 10, 2018
if (descriptor.OriginalParameterTypes[parameterPointer] == typeof(CancellationToken))
{
cts = CancellationTokenSource.CreateLinkedTokenSource(connection.ConnectionAborted);
arguments[parameterPointer] = cts.Token;
}
else if (ReflectionHelper.IsStreamingType(descriptor.OriginalParameterTypes[parameterPointer], mustBeDirectType: true))
Copy link
Member

Choose a reason for hiding this comment

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

The reflection here and line 242 is happening for every stream item right? Should cache it on the descriptor to pay the price once.

@davidfowl
Copy link
Member

I think we should also support accepting a channel writer for sever to client streaming. This is something we discussed briefly but didn’t go into details on. It’s worth looking at as it could simplify the programming model.

CloseMessage |
StreamCompleteMessage |
StreamDataMessage;
CloseMessage;
Copy link
Member

Choose a reason for hiding this comment

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

Should InvocationMessage and StreamInvocationMessage also be combined?

Copy link
Member Author

Choose a reason for hiding this comment

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

No, those are how we distinguish between regular invocation and invocation that returns a stream to the client.

Copy link
Member

Choose a reason for hiding this comment

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

Couldn't that be determined based on the signature of the hub method being invoked? Or the presence of the stream/streamIds property in the InvocationMessage?

Copy link
Member Author

Choose a reason for hiding this comment

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

Current design of the streamIds property is that it is only for streams from client to server.

And we can't rely on the signature of the Hub method to determine if the client intended to wait for a normal invocation response (via CompletionMessage) or a streamed response (via StreaItems + CompletionMessage). So we use two different invocation messages, StreamInvocationMessage and InvocationMessage

Copy link
Contributor

@mikaelm12 mikaelm12 left a comment

Choose a reason for hiding this comment

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

Still reviewing. I think some of my comments are already out of date with the commit you just made seconds ago

@@ -155,16 +141,6 @@ export interface CancelInvocationMessage extends HubInvocationMessage {
readonly invocationId: string;
}

/** A hub message sent to indicate the end of stream items for a streaming parameter. */
export interface StreamCompleteMessage extends HubMessageBase {
Copy link
Contributor

Choose a reason for hiding this comment

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

👍

@@ -97,18 +95,6 @@ export interface StreamItemMessage extends HubInvocationMessage {
readonly item?: any;
}

/** A hub message representing a single stream item, transferred through a streaming parameter. */
export interface StreamDataMessage extends HubMessageBase {
Copy link
Contributor

Choose a reason for hiding this comment

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

👍

@@ -112,7 +112,7 @@ public override string ToString()

try
{
streamIds = string.Join(", ", StreamIds?.Select(id => id?.ToString()));
streamIds = string.Join(", ", StreamIds != null ? StreamIds.Select(id => id?.ToString()) : Array.Empty<string>());
Copy link
Member

@JamesNK JamesNK Dec 13, 2018

Choose a reason for hiding this comment

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

Should an error be thrown for a null streamId?

This place might not be the right place but null stream ids off the wire are something that should be guarded against.

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 don't think we should care if stream ids is null, it's similar to how we don't care about extra data in the protocol

Copy link
Member

@JamesNK JamesNK left a comment

Choose a reason for hiding this comment

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

:shipit:

@BrennanConroy
Copy link
Member Author

Ping, anymore code comments while I finish up the spec?

@BrennanConroy BrennanConroy force-pushed the brecon/streamRefactor branch from af6dc8f to 1d350ab Compare January 2, 2019 21:11
Copy link
Contributor

@mikaelm12 mikaelm12 left a comment

Choose a reason for hiding this comment

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

Some comments on the spec. Let's get this in

@BrennanConroy
Copy link
Member Author

Should we put the streaming from client feature behind a check for the MinorVersion from the server? That way we can fail with a nice clean error on the client if a newer client tries to stream to an older server.

@halter73
Copy link
Member

halter73 commented Jan 5, 2019

Should we put the streaming from client feature behind a check for the MinorVersion from the server? That way we can fail with a nice clean error on the client if a newer client tries to stream to an older server.

I think the error should occur when a newer client attempts to connect to an older server instead of waiting for the client to attempt stream.

AFAIK, SignalR clients have never selectively supported features based on server protocol version. It's always been all or nothing. That way you don't have to worry about providing a feature detection API. If you want to start using a new feature, you have to upgrade all your servers. In the past, we considered requiring server upgrades a smaller ask than requiring client upgrades anyway.

@BrennanConroy
Copy link
Member Author

Started doing some microbenchmarking with the changes:

Method Mean Error StdDev Op/s Gen 0 Allocated
InvocationAsync 1.344 us 0.0267 us 0.0625 us 744,175.5 0.0057 696 B
UploadStream_One 44.187 us 0.1145 us 0.0894 us 22,630.9 - 3496 B

This shows that there is greater up-front costs for client->server streaming

Method Mean Error StdDev Op/s Gen 0 Allocated
UploadStream_Thousand 318.673 us 1.1018 us 0.9767 us 3,138.0 - 49074 B
Invoke_Thousand 1,349.203 us 21.8902 us 20.4761 us 741.2 5.8594 728000 B

And lower per-item cost when comparing sending 1000 of the same messages in streaming vs invoking.

@BrennanConroy BrennanConroy force-pushed the brecon/streamRefactor branch 2 times, most recently from 339716d to 9b62ba4 Compare January 11, 2019 00:52
@BrennanConroy BrennanConroy merged commit 3640182 into master Jan 11, 2019
@BrennanConroy BrennanConroy deleted the brecon/streamRefactor branch January 11, 2019 05:52
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area-signalr Includes: SignalR clients and servers
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants