Skip to content
This repository has been archived by the owner on Dec 18, 2018. It is now read-only.

Handle multiple tokens in Connection header (#1170) #1178

Merged
merged 1 commit into from
Oct 27, 2016

Conversation

cesarblum
Copy link
Contributor

@@ -234,12 +286,13 @@ protected virtual void OnConsumedBytes(int count)
var connection = headers.HeaderConnection.ToString();
Copy link
Member

Choose a reason for hiding this comment

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

Wouldn't it be more efficient to foreach the HeaderConnection? Instead of string.Joining like we do in ToString()?

Copy link
Member

Choose a reason for hiding this comment

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

Talked to @CesarBS and he points out that we don't split headers by commas so this will only matter when there are multiple Connection headers.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Updated - not calling ToString() anymore.


while (ch < end)
{
if ((*ch | 0x20) == 'k' && (end - ch) >= 9)
Copy link
Member

Choose a reason for hiding this comment

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

Maybe add a comment that the first characters of connection cannot be whitespace.

{
if ((*ch | 0x20) == 'k' && (end - ch) >= 9)
{
if ((*++ch | 0x20) == 'e' &&
Copy link
Member

Choose a reason for hiding this comment

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

Could we cast to a ulongs and uints to compare 4 or 2 characters at a time? Can we use Benchmark.NET to determine comparing larger types is faster than the the character-by-character version, and that pointers in general are faster than foreaching the string.

When I wrote similar code for ValidateHeaderCharacters I found foreach was faster than fixing the string and comparing using char pointers. You can see the numbers I got here. The foreach version was TestValidateHeaderValues1 and the char* version was TestValidateHeaderValues2

Copy link
Contributor

Choose a reason for hiding this comment

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

Also for (var i = 0... string[i] ==

Copy link
Contributor

Choose a reason for hiding this comment

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

For ulong compare you want to use the magic number ;-) Gowan....

davidfowl/Channels#120

Copy link
Member

Choose a reason for hiding this comment

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

for (var i = 0... string[i] == is TestValidateHeaderValues4

Copy link
Contributor

Choose a reason for hiding this comment

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

The magic number for Vector and long in the linked Channel repo are a bit down on what they could be as they Slice on each step.

@cesarblum cesarblum force-pushed the cesarbs/connection-upgrade-keepalive branch from 45173dd to 763e925 Compare October 24, 2016 22:55
{
return new ForRemainingData(context);
return new ForRemainingData(upgrade, context);
Copy link
Member

Choose a reason for hiding this comment

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

Did you decide what to do yet about making sure the Request.Body is not readable on Upgrade requests?

Copy link
Member

Choose a reason for hiding this comment

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

Why should we go out of our way to prevent that?

Copy link
Member

Choose a reason for hiding this comment

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

Poorly coded request buffering middleware?

Copy link
Member

Choose a reason for hiding this comment

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

The request has no body by any given metric, so anything that tried to read from the body would hang when it should just return 0.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Let's track this as a separate issue.

Copy link
Contributor

@analogrelay analogrelay left a comment

Choose a reason for hiding this comment

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

I approve, but am not well-versed in all the performance implications. The change looks correct though, which is my primary concern.

ch++;
isKeepAlive = ch == end || *ch == ',' || *ch == ' ';
Copy link
Member

Choose a reason for hiding this comment

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

Is finding a space sufficient? Wouldn't you also need to ensure the space(s) are followed by a ',' or the end of string?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The space or comma char delimits the token, that's all I care about here. The following spaces/commas will be skipped later.

Copy link
Member

Choose a reason for hiding this comment

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

What about this test case? "keep-alive jk!"

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Damn 😆

@halter73
Copy link
Member

I think we should do the same thing when we check whether keep-alive is set on the response Connection header in Frame.CreateResponseHeader.

I just created this issue to better handle the Transfer-Encoding request header.

@cesarblum
Copy link
Contributor Author

Updated to parse close too. Instead of out params I'm now using a Flags enum for connection options. Will now make the change to use this in Frame.CreateResponseHeader.

Some benchmarks (ParseConnectionPtr - code prior to latest commit; ParseConnectionFlags - new code):

Host Process Environment Information:
BenchmarkDotNet.Core=v0.9.9.0
OS=Windows
Processor=?, ProcessorCount=12
Frequency=3410073 ticks, Resolution=293.2489 ns, Timer=TSC
CLR=CORE, Arch=64-bit ? [RyuJIT]
GC=Concurrent Workstation
dotnet cli version: 1.0.0-preview2-003131

Type=Benchmarks  Mode=Throughput

               Method |          Connection |     Median |    StdDev |
--------------------- |-------------------- |----------- |---------- |
   ParseConnectionPtr |               close |  7.7008 ns | 1.0165 ns |
 ParseConnectionFlags |               close |  7.1349 ns | 0.1602 ns |
   ParseConnectionPtr |      close, upgrade | 17.6415 ns | 0.2234 ns |
 ParseConnectionFlags |      close, upgrade | 16.6620 ns | 0.2859 ns |
   ParseConnectionPtr |          keep-alive | 10.6774 ns | 0.2426 ns |
 ParseConnectionFlags |          keep-alive | 10.1829 ns | 0.6486 ns |
   ParseConnectionPtr | keep-alive, upgrade | 20.5912 ns | 0.3835 ns |
 ParseConnectionFlags | keep-alive, upgrade | 19.0261 ns | 0.6819 ns |
   ParseConnectionPtr |             upgrade |  9.5199 ns | 0.4007 ns |
 ParseConnectionFlags |             upgrade |  8.3817 ns | 0.1555 ns |

@cesarblum
Copy link
Contributor Author

cesarblum commented Oct 26, 2016

Another benchmark, now comparing against using string.IndexOf() if we were to be really permissive on what counts as close, keep-alive and upgrade:

                            Method |          Connection |      Median |    StdDev |
---------------------------------- |-------------------- |------------ |---------- |
      ParseConnectionStringIndexOf |               close | 298.3227 ns | 4.8557 ns |
 ParseConnectionStringIndexOfFlags |               close | 301.2077 ns | 7.5143 ns |
                ParseConnectionPtr |               close |   7.2877 ns | 0.1098 ns |
              ParseConnectionFlags |               close |   7.9504 ns | 0.1004 ns |
      ParseConnectionStringIndexOf |      close, upgrade | 369.8158 ns | 3.6107 ns |
 ParseConnectionStringIndexOfFlags |      close, upgrade | 366.7112 ns | 6.5670 ns |
                ParseConnectionPtr |      close, upgrade |  16.9927 ns | 0.2179 ns |
              ParseConnectionFlags |      close, upgrade |  17.0791 ns | 0.2203 ns |
      ParseConnectionStringIndexOf |          keep-alive | 339.7815 ns | 2.8325 ns |
 ParseConnectionStringIndexOfFlags |          keep-alive | 344.3799 ns | 7.4574 ns |
                ParseConnectionPtr |          keep-alive |   9.5432 ns | 0.1690 ns |
              ParseConnectionFlags |          keep-alive |  10.7998 ns | 0.2648 ns |
      ParseConnectionStringIndexOf | keep-alive, upgrade | 417.9434 ns | 8.8287 ns |
 ParseConnectionStringIndexOfFlags | keep-alive, upgrade | 414.3960 ns | 8.5653 ns |
                ParseConnectionPtr | keep-alive, upgrade |  18.6661 ns | 0.1675 ns |
              ParseConnectionFlags | keep-alive, upgrade |  19.0828 ns | 0.2613 ns |
      ParseConnectionStringIndexOf |             upgrade | 314.2134 ns | 5.3169 ns |
 ParseConnectionStringIndexOfFlags |             upgrade | 314.4707 ns | 3.1906 ns |
                ParseConnectionPtr |             upgrade |   9.0997 ns | 0.1481 ns |
              ParseConnectionFlags |             upgrade |   8.8362 ns | 0.1246 ns |

Here's the code for those:

[Benchmark]
public void ParseConnectionStringIndexOf()
{
    var value = Connection;

    var isKeepAlive = value.IndexOf("keep-alive", StringComparison.OrdinalIgnoreCase) != -1;
    var isUpgrade = value.IndexOf("upgrade", StringComparison.OrdinalIgnoreCase) != -1;
    var isClose = value.IndexOf("close", StringComparison.OrdinalIgnoreCase) != -1;
}

[Benchmark]
public void ParseConnectionStringIndexOfFlags()
{
    var value = Connection;
    var connectionOptions = ConnectionOptions.None;

    if (value.IndexOf("keep-alive", StringComparison.OrdinalIgnoreCase) != -1)
    {
        connectionOptions |= ConnectionOptions.KeepAlive;
    }

    if (value.IndexOf("upgrade", StringComparison.OrdinalIgnoreCase) != -1)
    {
        connectionOptions |= ConnectionOptions.Upgrade;
    }

    if (value.IndexOf("close", StringComparison.OrdinalIgnoreCase) != -1)
    {
        connectionOptions |= ConnectionOptions.Close;
    }
}

[Flags]
public enum ConnectionOptions
{
None = 0,
Copy link
Contributor

Choose a reason for hiding this comment

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

Why have this instead of returning a nullable ConnectionOptions?

Copy link
Member

Choose a reason for hiding this comment

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

Flags enums should always have a None

Copy link
Contributor

Choose a reason for hiding this comment

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

@halter73
Copy link
Member

@CesarBS Out of curiosity, did you include the call to HasFlag in your flag benchmarks?

@davidfowl
Copy link
Member

I thought we banned the use of HasFlag, it's pretty awful (it boxes the enum and it does a type check wtf 😄 )

/cc @rynowak

@cesarblum
Copy link
Contributor Author

Thanks guys, I wasn't aware of that. Benchmarks:

Host Process Environment Information:
BenchmarkDotNet.Core=v0.9.9.0
OS=Windows
Processor=?, ProcessorCount=12
Frequency=3410073 ticks, Resolution=293.2489 ns, Timer=TSC
CLR=CORE, Arch=64-bit ? [RyuJIT]
GC=Concurrent Workstation
dotnet cli version: 1.0.0-preview2-003131

Type=Benchmarks  Mode=Throughput

                  Method |          Connection |     Median |    StdDev |
------------------------ |-------------------- |----------- |---------- |
    ParseConnectionFlags |               close |  7.5603 ns | 0.1160 ns |
 ParseConnectionHasFlags |               close | 52.3294 ns | 1.5909 ns |
    ParseConnectionFlags |      close, upgrade | 18.0481 ns | 0.3127 ns |
 ParseConnectionHasFlags |      close, upgrade | 60.7138 ns | 1.1203 ns |
    ParseConnectionFlags |          keep-alive | 11.0001 ns | 0.1665 ns |
 ParseConnectionHasFlags |          keep-alive | 56.5062 ns | 1.5794 ns |
    ParseConnectionFlags | keep-alive, upgrade | 20.3282 ns | 0.1825 ns |
 ParseConnectionHasFlags | keep-alive, upgrade | 64.2798 ns | 0.9651 ns |
    ParseConnectionFlags |             upgrade |  9.8415 ns | 0.1697 ns |
 ParseConnectionHasFlags |             upgrade | 52.3777 ns | 1.0821 ns |

HasFlag is indeed awful. I'll remove the calls from Kestrel.

@cesarblum
Copy link
Contributor Author

Updated.

@halter73
Copy link
Member

:shipit:

@cesarblum cesarblum force-pushed the cesarbs/connection-upgrade-keepalive branch from f50a20a to 1ffad5c Compare October 27, 2016 19:20
@cesarblum cesarblum merged commit 1ffad5c into dev Oct 27, 2016
@cesarblum cesarblum deleted the cesarbs/connection-upgrade-keepalive branch October 27, 2016 19:25
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

8 participants