Skip to content
This repository was archived by the owner on Jan 23, 2023. It is now read-only.

[WIP] SslStream Throttle - #25187

Closed
Drawaes wants to merge 3 commits into
dotnet:masterfrom
Drawaes:SslStream-Throttle
Closed

[WIP] SslStream Throttle#25187
Drawaes wants to merge 3 commits into
dotnet:masterfrom
Drawaes:SslStream-Throttle

Conversation

@Drawaes

@Drawaes Drawaes commented Nov 11, 2017

Copy link
Copy Markdown

This would satisfy the throttling.

I will test the perf on windows and linux and see if it makes any difference on windows. If it does then I will need to push the semaphore down into the PAL.

I am submitting the PR to check the outerloop.

@Drawaes

Drawaes commented Nov 11, 2017

Copy link
Copy Markdown
Author

#23485

@Drawaes

Drawaes commented Nov 11, 2017

Copy link
Copy Markdown
Author

@dotnet-bot test Outerloop Linux x64 Debug Build please
@dotnet-bot test Outerloop Linux x64 Release Build please


async Task CompleteAsync(TWriteAdapter wAdapter, Task sTask)
{
await semaphoreTask.ConfigureAwait(false);

@stephentoub stephentoub Nov 11, 2017

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This incurs a closure allocation even on the fast path. I think you meant sTask.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Yes I did... rookie mistake

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Nah, it's way too easy for anyone to make that mistake. This is an example of why I want the ability to mark lambdas and local functions as non-capturing.
cc: @MadsTorgersen

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

I normally do the move it outside the outer function and look for errors but yeah it catches me a bit


SecurityStatusPal status = _sslState.EncryptData(buffer, ref outBuffer, out int encryptedBytes);
s_throttle.Release();
await WriteEncryptedDataAsync(wAdapter, outBuffer, rentedBuffer, encryptedBytes, status);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

ConfigureAwait(false)


SecurityStatusPal status = _sslState.EncryptData(buffer, ref outBuffer, out int encryptedBytes);
s_throttle.Release();
return WriteEncryptedDataAsync(writeAdapter, outBuffer, rentedBuffer, encryptedBytes, status);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Can these five lines be moved to a Task-returning method? Then it's essentially:

return semaphoreTask.IsCompletedSuccessfully ?
    ThatMethodWhateverItsCalled() :
    CompleteAsync(writeAdapter, semaphoreTask);

async Task CompleteAsync(TWriteAdapter wAdapter, Task sTask)
{
    await sTask.ConfigureAwait(false);
    await ThatMethodWhateverItsCalled().ConfigureAwait(false);
}

@Drawaes

Drawaes commented Nov 11, 2017

Copy link
Copy Markdown
Author

What's interesting is it works (with a small perf hit) on windows. But aspnet isn't working at all with https on linux clean install (without my change but latest) so I need to confirm that somethings not broken.

@stephentoub

Copy link
Copy Markdown
Member

But aspnet isn't working at all with https on linux clean install (without my change but latest) so I need to confirm that somethings not broken.

I'm suspicious of the test I previously noted as repeatedly failing:
SslStream_StreamToStream_WriteAsync_ReadAsync_Pending_Success
That test was added in the past because it's a pattern ASP.NET uses.

@Drawaes

Drawaes commented Nov 11, 2017

Copy link
Copy Markdown
Author

Yeah I am looking at it now to figure out if the issue is my system or code first .. then what .net 2.1 works

@Drawaes

Drawaes commented Nov 11, 2017

Copy link
Copy Markdown
Author

@Priya91 @stephentoub I can confirm if I rollback master to before the cancellation PR that asp.net 2.0 works fine, if I take current master (none of my changes) it doesn't work.

The test is a simple "hello world" with wrk to connect 256 times. No connection works. I think that PR needs to be looked at.

@Drawaes

Drawaes commented Nov 11, 2017

Copy link
Copy Markdown
Author

#24857

@Drawaes

Drawaes commented Nov 12, 2017

Copy link
Copy Markdown
Author

#25199 Found fix here, (Your suspicion was bang on)

FYI with that fix, and this change I am seeing approx 4x speed up on Linux still (16 core azure ubuntu 16.04) but need SslStream stable to move forward

@Drawaes

Drawaes commented Nov 12, 2017

Copy link
Copy Markdown
Author

Figures without the extra allocation (both master and this PR with the cancellation commits removed for testing).

I didn't bother with loads of connection combos on Linux, because the effect is pretty consistent and clear, and I didn't want to burn all my azure credits when the results are pretty clear.

(All results are 16 pipelined requests, on Azure 16 core DS series, AspNetCore helloworld-Techempower)

(I also forgot to do both before and after on the 256 for win so can do later)

Branch OS Connections RPS Avg Latency
Master Linux 256 224351.67 15.69ms
Throttle Linux 256 858556.50 5.51ms
Master Windows 512 670998.64 34.36ms
Throttle Windows 512 659504.27 28.84ms
Master Windows 1024 659186.81 58.20ms
Throttle Windows 1024 653020.98 53.23ms

My takeaway from this, Linux is a no brainer. Windows, well it actually seems to make very little difference except maybe a slight drop in RPS but better average latency.

@Drawaes

Drawaes commented Nov 12, 2017

Copy link
Copy Markdown
Author

I don't know if its worth PALing out and pushing down the semaphore out of Windows. To be honest I would like to see the read side fixed first (I suspect that read PR will work fine with the roll back) and then if we could try this with both sides in the mix.

@benaadams

Copy link
Copy Markdown
Member

@dotnet-bot test Outerloop Linux x64 Debug Build please
@dotnet-bot test Outerloop Linux x64 Release Build please

@benaadams

Copy link
Copy Markdown
Member

Other one has issues #24497; might want to remove WIP on this one; raise issue for moving semaphore to Linux PAL; then rebasing and investigating failures on other one?

@Drawaes Drawaes changed the title [WIP] SslStream Throttle SslStream Throttle Nov 13, 2017
@benaadams

Copy link
Copy Markdown
Member

Heh, also need to do the feedback 😉

@Drawaes

Drawaes commented Nov 13, 2017

Copy link
Copy Markdown
Author

Yeah I had done it in a branch with the revert. Tonight I will address the feedback.

@Drawaes

Drawaes commented Nov 13, 2017

Copy link
Copy Markdown
Author

@dotnet-bot test Outerloop Linux x64 Debug Build please
@dotnet-bot test Outerloop Linux x64 Release Build please

private static readonly AsyncProtocolCallback s_resumeAsyncReadCallback = new AsyncProtocolCallback(ResumeAsyncReadCallback);
private static readonly AsyncProtocolCallback s_readHeaderCallback = new AsyncProtocolCallback(ReadHeaderCallback);
private static readonly AsyncProtocolCallback s_readFrameCallback = new AsyncProtocolCallback(ReadFrameCallback);
private static readonly SemaphoreSlim s_throttle = new SemaphoreSlim(Environment.ProcessorCount);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Have you experimented with this value? I'm wondering if some small multiple (2 or 3) would test better or worse.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Also, can you add a comment explaining why this exists? Presumably we'll undo this once we upgrade to OpenSSL 1.1. if there's an issue tracking that, we can include its issue number here.

@Drawaes Drawaes Nov 13, 2017

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Will add a comment and issue number.

As to the multiplier, I can retry these numbers I believe that actually a smaller number (/2) was better on boxes with hyperthreading as the aes algo uses pretty much uses the registers/pipeline anyway. As azure doesn't do hyperthreading (well some newer instances do) 1x was the happy medium.

But that is from memory so I will try to retest on physical vs azure, I am limited to 16 cores on azure and my physical has 24 cores so it's also a bit Apple's and oranges.

There isn't a way that I know of to get the "real" core count rather than virtual without me going to native apis...?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I can retry these numbers I believe that actually a smaller number (/2)

I could believe that as well, but that's also dangerous, especially when using the same logic on Windows.

There isn't a way that I know of to get the "real" core count rather than virtual without me going to native apis...?

Correct; that's not currently exposed anywhere. There have been discussions in the past about exposing more about the machine's topology, but that's never materialized in built-in APIs. I'm fairly certain there are 3rd party implementations of that, though, including if I remember correctly in Joe Duffy's concurrency book.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Its also more complicated with Jobs/Docker/cg_groups etc

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Yeah I have my own code for that for some stuff I do at work (heavy handed avx montecarlo simulations are a good place to ignore hyperthreads to avoid context thrashing ;)) but I didn't want to include that here. Maybe I can put in an API review. Anyway I will run a basic test at 2x and see what direction it heads

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

I will revert my statement then "I have code for bare metal on linux and windows" :)

Anyway @stephentoub I tested 2x and on Ubuntu on Azure (no hyper threading) it goes from ~720k to ~200k-300k, it wasn't consistent on the 2x so I think the lock convoy is in full effect.

Also the same locks are used on encrypt and decrypt so actually we are nearer to 2x anyway as we are only throttling one side with the current change. A different multiplier might be worth a visit if I get the other side in as async and hook it into the semaphore.

I will make the comment change after getting tests to pass I don't want to nuke the long run.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Me 3 and linux, but I figure that might be pushing the boundary for this PR ;)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

it goes from ~720k to ~200k-300k

Ok. Thanks for checking.

that might be pushing the boundary for this PR

Yes ;)

@benaadams

Copy link
Copy Markdown
Member

redhat 🔥 System.Reflection.Metadata.Tests

2017-11-13 20:19:41,275: INFO: proc(54): run_and_log_output: Output: Discovering: System.Reflection.Metadata.Tests
2017-11-13 20:19:41,484: INFO: proc(54): run_and_log_output: Output: Discovered:  System.Reflection.Metadata.Tests
2017-11-13 20:19:41,622: INFO: proc(54): run_and_log_output: Output: Starting:    System.Reflection.Metadata.Tests

fedora

Unhandled Exception of Type System.Net.Http.HttpRequestException
Message :
System.Net.Http.HttpRequestException : An error occurred while sending the request.
---- System.Net.Http.CurlException : SSL connect error
Stack Trace :
   at System.Net.Http.HttpClient.<FinishSendAsyncBuffered>d__58.MoveNext() in /mnt/j/workspace/dotnet_corefx/master/linux-TGroup_netcoreapp+CGroup_Debug+AGroup_x64+TestOuter_true_prtest/src/System.Net.Http/src/System/Net/Http/HttpClient.cs:line 464
--- End of stack trace from previous location where exception was thrown ---
   at System.Net.Http.Functional.Tests.PostScenarioTest.<PostHelper>d__24.MoveNext() in /mnt/j/workspace/dotnet_corefx/master/linux-TGroup_netcoreapp+CGroup_Debug+AGroup_x64+TestOuter_true_prtest/src/System.Net.Http/tests/FunctionalTests/PostScenarioTest.cs:line 204
--- End of stack trace from previous location where exception was thrown ---
   at System.Net.Http.Functional.Tests.PostScenarioTest.<PostUsingContentLengthSemantics_Success>d__13.MoveNext() in /mnt/j/workspace/dotnet_corefx/master/linux-TGroup_netcoreapp+CGroup_Debug+AGroup_x64+TestOuter_true_prtest/src/System.Net.Http/tests/FunctionalTests/PostScenarioTest.cs:line 79
--- End of stack trace from previous location where exception was thrown ---
--- End of stack trace from previous location where exception was thrown ---
--- End of stack trace from previous location where exception was thrown ---
----- Inner Stack Trace -----
   at System.Net.Http.CurlHandler.ThrowIfCURLEError(CURLcode error) in /mnt/j/workspace/dotnet_corefx/master/linux-TGroup_netcoreapp+CGroup_Debug+AGroup_x64+TestOuter_true_prtest/src/System.Net.Http/src/System/Net/Http/Unix/CurlHandler.cs:line 655
   at System.Net.Http.CurlHandler.MultiAgent.FinishRequest(StrongToWeakReference`1 easyWrapper, CURLcode messageResult) in /mnt/j/workspace/dotnet_corefx/master/linux-TGroup_netcoreapp+CGroup_Debug+AGroup_x64+TestOuter_true_prtest/src/System.Net.Http/src/System/Net/Http/Unix/CurlHandler.MultiAgent.cs:line 877

@benaadams

Copy link
Copy Markdown
Member

@dotnet-bot test Outerloop Linux x64 Debug Build please

@benaadams

Copy link
Copy Markdown
Member

OSX x64 Debug Build has been running 8 hrs; think it has hung (though not timed out)

@benaadams

Copy link
Copy Markdown
Member

@dotnet-bot test OSX x64 Debug Build

@Drawaes

Drawaes commented Nov 13, 2017

Copy link
Copy Markdown
Author

I suspect the OSX issues is, there is 1 Mac Pro available and its getting hammered.

#25118 (comment)

@Drawaes

Drawaes commented Nov 13, 2017

Copy link
Copy Markdown
Author

OSX is still
Still waiting to schedule task
Waiting for next available executor on osx-10.12||OSX.1012.Amd64.Open

@Drawaes

Drawaes commented Nov 14, 2017

Copy link
Copy Markdown
Author

@dotnet-bot test OSX x64 Debug Build please

@benaadams

Copy link
Copy Markdown
Member

Took 19 hr think OSX just gave up

@Drawaes

Drawaes commented Nov 15, 2017

Copy link
Copy Markdown
Author

They all did... (all OSX runs)
image

@davidsh davidsh removed their assignment Nov 16, 2017
@karelz karelz added this to the 2.1.0 milestone Nov 18, 2017
@Drawaes Drawaes changed the title SslStream Throttle [WIP] SslStream Throttle Nov 28, 2017
@Drawaes

Drawaes commented Nov 28, 2017

Copy link
Copy Markdown
Author

WIP because I am yet to do any perf/stress tests or tracing. I would probably think that a refactor could be done as well of the adapaters, at least a common Interface, but not until perf has been confirmed.

@Drawaes

Drawaes commented Nov 28, 2017

Copy link
Copy Markdown
Author

I have the following error locally as well?

15:50:36 CSC : error CS2001: Source file '/mnt/j/workspace/dotnet_corefx/master/linux-TGroup_netcoreapp+CGroup_Release+AGroup_x64+TestOuter_false_prtest/src/Microsoft.CSharp/src/Microsoft/CSharp/RuntimeBinder/Semantics/Types/TypeParameterType.cs' could not be found. [/mnt/j/workspace/dotnet_corefx/master/linux-TGroup_netcoreapp+CGroup_Release+AGroup_x64+TestOuter_false_prtest/src/Microsoft.CSharp/src/Microsoft.CSharp.csproj]

Bad merge or is something else going on here?

@stephentoub

stephentoub commented Nov 28, 2017

Copy link
Copy Markdown
Member

Most likely a bad merge. There was an issue a while back with the wrong casing for that file, but it was fixed weeks ago.

@Drawaes

Drawaes commented Nov 29, 2017

Copy link
Copy Markdown
Author

Okay I will look for the issue...

@Drawaes

Drawaes commented Nov 29, 2017

Copy link
Copy Markdown
Author

So my initial findings are (I can write up later),, that it hits windows ~5-10% rps on benchmarks. This seems too much to me, so I will (hopefully tonight if possible) push the Throttle down into the PAL and make it a no-op on windows and only active on Linux.

@Drawaes

Drawaes commented Nov 30, 2017

Copy link
Copy Markdown
Author

Results on windows (with the PAL) are as expected... the difference bouncing around (Azure benching seems to be less than perfect) but its basically

Branch RPS
Master 542571
Throttle 538472

So basically no change (some runs were faster than master some slower so above is the mean)

Coming up the Linux benchmarks

@benaadams

Copy link
Copy Markdown
Member

@dotnet-bot test Outerloop Linux x64 Debug Build please
@dotnet-bot test Outerloop Linux x64 Release Build please
@dotnet-bot Test outerloop Windows x64 Debug Build please
@dotnet-bot Test outerloop OSX x64 Debug Build please

@Drawaes

Drawaes commented Dec 1, 2017

Copy link
Copy Markdown
Author

@stephentoub I have done the Linux thing on Azure... to sum it up
200k on Master
400k on Throttle....

But I have made a PR to my PR

https://github.com/Drawaes/corefx/pull/6/files

It hits ~500k

This seems to me a better solution, giving it more than 1 for the semaphore improves it but I don't really have a solid heuristic for what it should be.

Thoughts?

@stephentoub

Copy link
Copy Markdown
Member

@Drawaes, can you explain the fix to me? Why does calling ClearError address the issue, and what kind of impact does that have functionally? And would we still need to do asynchronous WaitAsyncs, or at this point could it just use lock/Monitor, or if we wanted a higher count, Wait/Release on the semaphore rather than WaitAsync/Release.
cc: @bartonjs

@NinoFloris

Copy link
Copy Markdown

About that PR (https://github.com/Drawaes/corefx/pull/6/files) @stephentoub.

ClearError was previously always called in the c++ SslRead and SslWrite methods (ERR_clear_error) before calling into openssl. But as the throttle methods were specifically introduced to be run before doing any r/w/encrypt/decrypt calls it seems like a valid option to remove the ERR_clear_error calls in the shim and let throttle handle it.
This allows more control over when and how many times ClearError is actually called (resulting in better perf).

Downside is correctness not being guaranteed (false positives, errs from another thread) when calling into pal, now need to remember/know to first call throttle to be correct (a comment might be useful here). As long as all code using sslstate/securechannel/pal etc does throttle before r/w/enc/dec it's correct.

@Drawaes good summary?

@bartonjs

bartonjs commented Dec 1, 2017

Copy link
Copy Markdown
Member

But as the throttle methods were specifically introduced to be run before doing any r/w/encrypt/decrypt calls it seems like a valid option to remove the ERR_clear_error calls in the shim and let throttle handle it.

Does the throttle guarantee that the subsequent operations are on the same pthread?

Downside is correctness not being guaranteed

Then it's not worth the perf gain.

@Drawaes

Drawaes commented Dec 1, 2017

Copy link
Copy Markdown
Author

Why would correctness not be guaranteed? The move have just reduced the lock time. There is no async or any other calls between the clear error and the encrypt/decrypt. I didn't remove the clear error from any call other than the encrypt/decrypt (all other methods still do them in native code).

Basically I have changed the code from this

//Potentially Async Throttle

//PInvoke
    Native ClearError
    Native Encrypt

//Managed Release

To this

//Potentially Async Throttle

//PInvoke
   Native ClearError

//Managed Release

//PInvoke
   Native Encrypt/Decrypt

No where there is there an opportunity for anyone to swap out the thread and let it do something else...

@NinoFloris

NinoFloris commented Dec 1, 2017 via email

Copy link
Copy Markdown

@Drawaes

Drawaes commented Dec 1, 2017

Copy link
Copy Markdown
Author

Ahh sure but its not a public API and its only called in one place in internal classes. I think what @bartonjs took from your wording was the previous situation.....

A thread doesn't clean itself up, is reused from the pool and you get an error from another op potentially polluting the state (the Ssl part of OpenSsl uses errors to indicate things like "I need more data etc").

That is still not a possibility with this code.

@NinoFloris

Copy link
Copy Markdown

Yeah that was all my comment was about and I suppose what @stephentoub was asking as well. What's the impact of removing from shim. So yep as it's internal api all callers need to do the correct dance to not get false positives (I've the feeling I'm repeating exactly what I wrote though)

@Drawaes

Drawaes commented Dec 1, 2017

Copy link
Copy Markdown
Author

@bartonjs

Does the throttle guarantee that the subsequent operations are on the same pthread?

Yes 100%

@Drawaes

Drawaes commented Dec 1, 2017

Copy link
Copy Markdown
Author

You know what, forget the side PR. It's just confusing everyone (I know its better :P but hey). Let's stick with what is in here....

The figures I have are basically these

Master ~200k rps
Throttle ~400k rps
Throttle with ProcCount/2 ~580k rps

It starts to go down from there again, 1/2 seems to be the peak on a 16 core azure box running ubuntu.

I have also tried it on my local machine (4 core desktop) and the same pattern holds. I can't test it on 32 cores because I need a 32 core load gen and on my puny azure account I can only have 64 cores in a single region.

So I would say go with the throttle with ProcCount/2 it won't effect windows or OSX now because I PAL'd them out.

@benaadams

Copy link
Copy Markdown
Member
fedora.25.amd64.Open-Debug-x64
Get Repro environment
Unhandled Exception of Type System.Net.Http.HttpRequestException
Message :
System.Net.Http.HttpRequestException : An error occurred while sending the request.
---- System.Net.Http.CurlException : SSL connect error
Stack Trace :
   at System.Net.Http.HttpClient.<FinishSendAsyncBuffered>d__58.MoveNext() in /mnt/j/workspace/dotnet_corefx/master/linux-TGroup_netcoreapp+CGroup_Debug+AGroup_x64+TestOuter_true_prtest/src/System.Net.Http/src/System/Net/Http/HttpClient.cs:line 464
--- End of stack trace from previous location where exception was thrown ---
   at System.Net.Http.Functional.Tests.HttpClientHandlerTest.<SendAsync_GetWithValidHostHeader_Success>d__35.MoveNext() in /mnt/j/workspace/dotnet_corefx/master/linux-TGroup_netcoreapp+CGroup_Debug+AGroup_x64+TestOuter_true_prtest/src/System.Net.Http/tests/FunctionalTests/HttpClientHandlerTest.cs:line 299
--- End of stack trace from previous location where exception was thrown ---
--- End of stack trace from previous location where exception was thrown ---
--- End of stack trace from previous location where exception was thrown ---
----- Inner Stack Trace -----
   at System.Net.Http.CurlHandler.ThrowIfCURLEError(CURLcode error) in /mnt/j/workspace/dotnet_corefx/master/linux-TGroup_netcoreapp+CGroup_Debug+AGroup_x64+TestOuter_true_prtest/src/System.Net.Http/src/System/Net/Http/Unix/CurlHandler.cs:line 655
   at System.Net.Http.CurlHandler.MultiAgent.FinishRequest(StrongToWeakReference`1 easyWrapper, CURLcode messageResult) in /mnt/j/workspace/dotnet_corefx/master/linux-TGroup_netcoreapp+CGroup_Debug+AGroup_x64+TestOuter_true_prtest/src/System.Net.Http/src/System/Net/Http/Unix/CurlHandler.MultiAgent.cs:line 877

@dotnet-bot test Outerloop Linux x64 Debug Build please

@stephentoub

Copy link
Copy Markdown
Member

Replaced by #25646

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

9 participants