Improve HTTP/2 scaling performance (#35694)
authorStephen Toub <stoub@microsoft.com>
Thu, 7 May 2020 10:08:20 +0000 (06:08 -0400)
committerGitHub <noreply@github.com>
Thu, 7 May 2020 10:08:20 +0000 (06:08 -0400)
commitcd8355bd2aafff1776df9e009940d91697752152
tree0a4ab4a44cc0ea5b7d1b22df198fde2a142dbdf2
parentf6aad636c9bdacb6c98b144449c5d72ef9c50e70
Improve HTTP/2 scaling performance (#35694)

* Rearrange Http2Connection.SendHeadersAsync to increase scale

Many concurrent streams are currently bottlenecking in SendHeadersAsync.  They:
- take the headers serialization lock
- request a stream credit
- serialize all their headers to a buffer stored on the connection
- take the write lock
- write out the headers
- release the write lock
- release the headers serialization lock

Instead of using a header buffer pooled on the connection, we can instead temporarily grab ArrayPool buffers into which we serialize the headers, which allows us to eliminate the headers serialization lock.  With that, we get:
- request a stream credit
- serialize the headers to a pooled buffer
- take the write lock
- write out the headers
- release the write lock

This has a significant impact on the ability for many concurrent streams to scale, as all of the header serialization work happens outside of the lock.

* Use ContinueWith instead of async/await in Ignore/LogExceptions

The current implementation will always invoke the continuation, even in the majority case where there's no failure (and if there is a failure, it's cheaper not to throw it again).  We can avoid that and decrease both overhead and allocation in the common case.

* Remove CopyToAsync wrapping task from SendRequestBodyAsync

* Reduce contention on semaphore locks

We're seeing a lot of contention trying to acquire the monitors inside of semaphore slims, even when the contention is to release the semaphore, which should entail minimal contention (and delay of a release will just cause more contention).  We can use a small interlocked gate to add a fast path.

* Stop clearing ArrayBuffer's pooled byte arrays

We already weren't clearing as the array grew, nor are we clearing in most other places in the library.  It's not clear why we were clearing in this one spot, but the zero'ing is showing up meaningfully in profiles.

* Add several gRPC known headers

* Avoid dictionary lookup in HttpMethod.Normalize

* Avoid allocating strings for known Content-Types

* Combine AcquireWriteLockAsync into StartWriteAsync

There's not a good reason to keep them separate, and StartWriteAsync is AcquireWriteLockAsync's only caller.

* Remove GetCancelableWaiterTask async method

We can achieve the same thing without the extra async method by putting the cleanup logic into the awaiter's GetResult.

* Address PR feedback
12 files changed:
src/libraries/Common/src/System/Net/ArrayBuffer.cs
src/libraries/System.Net.Http/src/System.Net.Http.csproj
src/libraries/System.Net.Http/src/System/Net/Http/Headers/HeaderDescriptor.cs
src/libraries/System.Net.Http/src/System/Net/Http/Headers/HttpHeaders.cs
src/libraries/System.Net.Http/src/System/Net/Http/Headers/KnownHeaders.cs
src/libraries/System.Net.Http/src/System/Net/Http/HttpContent.cs
src/libraries/System.Net.Http/src/System/Net/Http/HttpMethod.cs
src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2Connection.cs
src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2Stream.cs
src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/HttpConnectionBase.cs
src/libraries/System.Net.Http/src/System/Threading/AsyncMutex.cs [new file with mode: 0644]
src/libraries/System.Net.Http/tests/UnitTests/Headers/KnownHeadersTest.cs