Skip to content

Commit

Permalink
Remove GCHandles from SendHeaders() code path (#44561)
Browse files Browse the repository at this point in the history
* Remove GCHandles from SendHeaders() code path

This introduces a new type, UnmanagedBufferAllocator, that manages unmanaged block allocations. The intent here is to remove the allocation of handles and temporary managed arrays and replace them with native memory.
  • Loading branch information
AaronRobinsonMSFT committed Oct 24, 2022
1 parent 9c8520a commit a5ae53a
Show file tree
Hide file tree
Showing 7 changed files with 491 additions and 351 deletions.
4 changes: 2 additions & 2 deletions src/Servers/HttpSys/src/Helpers.cs
Expand Up @@ -8,8 +8,8 @@ namespace Microsoft.AspNetCore.Server.HttpSys;

internal static class Helpers
{
internal static readonly byte[] ChunkTerminator = new byte[] { (byte)'0', (byte)'\r', (byte)'\n', (byte)'\r', (byte)'\n' };
internal static readonly byte[] CRLF = new byte[] { (byte)'\r', (byte)'\n' };
public static ReadOnlySpan<byte> ChunkTerminator => "0\r\n\r\n"u8;
public static ReadOnlySpan<byte> CRLF => "\r\n"u8;

internal static ArraySegment<byte> GetChunkHeader(long size)
{
Expand Down
147 changes: 61 additions & 86 deletions src/Servers/HttpSys/src/HttpSysListener.cs
Expand Up @@ -3,7 +3,6 @@

using System.Buffers;
using System.Diagnostics;
using System.Runtime.InteropServices;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.HttpSys.Internal;
using Microsoft.Extensions.Logging;
Expand Down Expand Up @@ -310,103 +309,79 @@ internal unsafe void SendError(ulong requestId, int httpStatusCode, IList<string
httpResponse.Response_V1.Version.MajorVersion = (ushort)1;
httpResponse.Response_V1.Version.MinorVersion = (ushort)1;

List<GCHandle>? pinnedHeaders = null;
GCHandle gcHandle;
try
{
// Copied from the multi-value headers section of SerializeHeaders
if (authChallenges != null && authChallenges.Count > 0)
{
pinnedHeaders = new List<GCHandle>(authChallenges.Count + 3);
using UnmanagedBufferAllocator allocator = new();

HttpApiTypes.HTTP_RESPONSE_INFO[] knownHeaderInfo = new HttpApiTypes.HTTP_RESPONSE_INFO[1];
gcHandle = GCHandle.Alloc(knownHeaderInfo, GCHandleType.Pinned);
pinnedHeaders.Add(gcHandle);
httpResponse.pResponseInfo = (HttpApiTypes.HTTP_RESPONSE_INFO*)gcHandle.AddrOfPinnedObject();
byte* bytes;
int bytesLength;

knownHeaderInfo[httpResponse.ResponseInfoCount].Type = HttpApiTypes.HTTP_RESPONSE_INFO_TYPE.HttpResponseInfoTypeMultipleKnownHeaders;
knownHeaderInfo[httpResponse.ResponseInfoCount].Length =
(uint)Marshal.SizeOf<HttpApiTypes.HTTP_MULTIPLE_KNOWN_HEADERS>();
// Copied from the multi-value headers section of SerializeHeaders
if (authChallenges != null && authChallenges.Count > 0)
{
HttpApiTypes.HTTP_RESPONSE_INFO* knownHeaderInfo = allocator.AllocAsPointer<HttpApiTypes.HTTP_RESPONSE_INFO>(1);
httpResponse.pResponseInfo = knownHeaderInfo;

HttpApiTypes.HTTP_MULTIPLE_KNOWN_HEADERS header = new HttpApiTypes.HTTP_MULTIPLE_KNOWN_HEADERS();
knownHeaderInfo[httpResponse.ResponseInfoCount].Type = HttpApiTypes.HTTP_RESPONSE_INFO_TYPE.HttpResponseInfoTypeMultipleKnownHeaders;
knownHeaderInfo[httpResponse.ResponseInfoCount].Length =
(uint)sizeof(HttpApiTypes.HTTP_MULTIPLE_KNOWN_HEADERS);

header.HeaderId = HttpApiTypes.HTTP_RESPONSE_HEADER_ID.Enum.HttpHeaderWwwAuthenticate;
header.Flags = HttpApiTypes.HTTP_RESPONSE_INFO_FLAGS.PreserveOrder; // The docs say this is for www-auth only.
HttpApiTypes.HTTP_MULTIPLE_KNOWN_HEADERS* header = allocator.AllocAsPointer<HttpApiTypes.HTTP_MULTIPLE_KNOWN_HEADERS>(1);

HttpApiTypes.HTTP_KNOWN_HEADER[] nativeHeaderValues = new HttpApiTypes.HTTP_KNOWN_HEADER[authChallenges.Count];
gcHandle = GCHandle.Alloc(nativeHeaderValues, GCHandleType.Pinned);
pinnedHeaders.Add(gcHandle);
header.KnownHeaders = (HttpApiTypes.HTTP_KNOWN_HEADER*)gcHandle.AddrOfPinnedObject();
header->HeaderId = HttpApiTypes.HTTP_RESPONSE_HEADER_ID.Enum.HttpHeaderWwwAuthenticate;
header->Flags = HttpApiTypes.HTTP_RESPONSE_INFO_FLAGS.PreserveOrder; // The docs say this is for www-auth only.
header->KnownHeaderCount = 0;

for (int headerValueIndex = 0; headerValueIndex < authChallenges.Count; headerValueIndex++)
{
// Add Value
string headerValue = authChallenges[headerValueIndex];
byte[] bytes = HeaderEncoding.GetBytes(headerValue);
nativeHeaderValues[header.KnownHeaderCount].RawValueLength = (ushort)bytes.Length;
gcHandle = GCHandle.Alloc(bytes, GCHandleType.Pinned);
pinnedHeaders.Add(gcHandle);
nativeHeaderValues[header.KnownHeaderCount].pRawValue = (byte*)gcHandle.AddrOfPinnedObject();
header.KnownHeaderCount++;
}
HttpApiTypes.HTTP_KNOWN_HEADER* nativeHeaderValues = allocator.AllocAsPointer<HttpApiTypes.HTTP_KNOWN_HEADER>(authChallenges.Count);
header->KnownHeaders = nativeHeaderValues;

// This type is a struct, not an object, so pinning it causes a boxed copy to be created. We can't do that until after all the fields are set.
gcHandle = GCHandle.Alloc(header, GCHandleType.Pinned);
pinnedHeaders.Add(gcHandle);
knownHeaderInfo[0].pInfo = (HttpApiTypes.HTTP_MULTIPLE_KNOWN_HEADERS*)gcHandle.AddrOfPinnedObject();

httpResponse.ResponseInfoCount = 1;
for (int headerValueIndex = 0; headerValueIndex < authChallenges.Count; headerValueIndex++)
{
// Add Value
string headerValue = authChallenges[headerValueIndex];
bytes = allocator.GetHeaderEncodedBytes(headerValue, out bytesLength);
nativeHeaderValues[header->KnownHeaderCount].RawValueLength = checked((ushort)bytesLength);
nativeHeaderValues[header->KnownHeaderCount].pRawValue = bytes;
header->KnownHeaderCount++;
}

httpResponse.Response_V1.StatusCode = (ushort)httpStatusCode;
string? statusDescription = HttpReasonPhrase.Get(httpStatusCode);
uint dataWritten = 0;
uint statusCode;
byte[] byteReason = statusDescription != null ? HeaderEncoding.GetBytes(statusDescription) : Array.Empty<byte>();
fixed (byte* pReason = byteReason)
{
httpResponse.Response_V1.pReason = (byte*)pReason;
httpResponse.Response_V1.ReasonLength = (ushort)byteReason.Length;
knownHeaderInfo[0].pInfo = header;

byte[] byteContentLength = new byte[] { (byte)'0' };
fixed (byte* pContentLength = byteContentLength)
{
(&httpResponse.Response_V1.Headers.KnownHeaders)[(int)HttpSysResponseHeader.ContentLength].pRawValue = (byte*)pContentLength;
(&httpResponse.Response_V1.Headers.KnownHeaders)[(int)HttpSysResponseHeader.ContentLength].RawValueLength = (ushort)byteContentLength.Length;
httpResponse.Response_V1.Headers.UnknownHeaderCount = 0;

statusCode =
HttpApi.HttpSendHttpResponse(
_requestQueue.Handle,
requestId,
0,
&httpResponse,
null,
&dataWritten,
IntPtr.Zero,
0,
SafeNativeOverlapped.Zero,
IntPtr.Zero);
}
}
if (statusCode != UnsafeNclNativeMethods.ErrorCodes.ERROR_SUCCESS)
{
// if we fail to send a 401 something's seriously wrong, abort the request
HttpApi.HttpCancelHttpRequest(_requestQueue.Handle, requestId, IntPtr.Zero);
}
httpResponse.ResponseInfoCount = 1;
}
finally

httpResponse.Response_V1.StatusCode = checked((ushort)httpStatusCode);
string statusDescription = HttpReasonPhrase.Get(httpStatusCode) ?? string.Empty;
uint dataWritten = 0;
uint statusCode;

bytes = allocator.GetHeaderEncodedBytes(statusDescription, out bytesLength);
httpResponse.Response_V1.pReason = bytes;
httpResponse.Response_V1.ReasonLength = checked((ushort)bytesLength);

const int contentLengthLength = 1;
byte* pContentLength = allocator.AllocAsPointer<byte>(contentLengthLength + 1);
pContentLength[0] = (byte)'0';
pContentLength[1] = 0; // null terminator

(&httpResponse.Response_V1.Headers.KnownHeaders)[(int)HttpSysResponseHeader.ContentLength].pRawValue = pContentLength;
(&httpResponse.Response_V1.Headers.KnownHeaders)[(int)HttpSysResponseHeader.ContentLength].RawValueLength = contentLengthLength;
httpResponse.Response_V1.Headers.UnknownHeaderCount = 0;

statusCode =
HttpApi.HttpSendHttpResponse(
_requestQueue.Handle,
requestId,
0,
&httpResponse,
null,
&dataWritten,
IntPtr.Zero,
0,
SafeNativeOverlapped.Zero,
IntPtr.Zero);
if (statusCode != UnsafeNclNativeMethods.ErrorCodes.ERROR_SUCCESS)
{
if (pinnedHeaders != null)
{
foreach (GCHandle handle in pinnedHeaders)
{
if (handle.IsAllocated)
{
handle.Free();
}
}
}
// if we fail to send a 401 something's seriously wrong, abort the request
HttpApi.HttpCancelHttpRequest(_requestQueue.Handle, requestId, IntPtr.Zero);
}
}

Expand Down

0 comments on commit a5ae53a

Please sign in to comment.