Skip to content

Experimental revert of P/Invoke layer #55519

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

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,6 @@
using System.Text;
using BenchmarkDotNet.Attributes;
using Microsoft.AspNetCore.HttpSys.Internal;
using Windows.Win32.Foundation;
using Windows.Win32.Networking.HttpServer;
using RequestHeaders = Microsoft.AspNetCore.HttpSys.Internal.RequestHeaders;

[SimpleJob, MemoryDiagnoser]
Expand Down Expand Up @@ -56,7 +54,7 @@ private unsafe RequestHeaders CreateRequestHeader(int unknowHeaderCount)
var nativeContext = new NativeRequestContext(MemoryPool<byte>.Shared, null, 0, false);
var nativeMemory = new Span<byte>(nativeContext.NativeRequest, (int)nativeContext.Size + 8);

var requestStructure = new HTTP_REQUEST_V1();
var requestStructure = new HttpApiTypes.HTTP_REQUEST();
var remainingMemory = SetUnknownHeaders(nativeMemory, ref requestStructure, GenerateUnknownHeaders(unknowHeaderCount));
SetHostHeader(remainingMemory, ref requestStructure);
MemoryMarshal.Write(nativeMemory, in requestStructure);
Expand All @@ -66,64 +64,64 @@ private unsafe RequestHeaders CreateRequestHeader(int unknowHeaderCount)
return requestHeaders;
}

private unsafe Span<byte> SetHostHeader(Span<byte> nativeMemory, ref HTTP_REQUEST_V1 requestStructure)
private unsafe Span<byte> SetHostHeader(Span<byte> nativeMemory, ref HttpApiTypes.HTTP_REQUEST requestStructure)
{
// Writing localhost to Host header
var dataDestination = nativeMemory[Marshal.SizeOf<HTTP_REQUEST_V1>()..];
var length = Encoding.ASCII.GetBytes("localhost:5001", dataDestination);
var dataDestination = nativeMemory.Slice(Marshal.SizeOf<HttpApiTypes.HTTP_REQUEST>());
int length = Encoding.ASCII.GetBytes("localhost:5001", dataDestination);
fixed (byte* address = &MemoryMarshal.GetReference(dataDestination))
{
requestStructure.Headers.KnownHeaders._28.pRawValue = (PCSTR)address;
requestStructure.Headers.KnownHeaders._28.RawValueLength = (ushort)length;
requestStructure.Headers.KnownHeaders_29.pRawValue = address;
requestStructure.Headers.KnownHeaders_29.RawValueLength = (ushort)length;
}
return dataDestination;
}

/// <summary>
/// Writes an array HTTP_UNKNOWN_HEADER and an array of header key-value pairs to nativeMemory. Pointers in the HTTP_UNKNOWN_HEADER structure points to the corresponding key-value pair.
/// </summary>
private unsafe Span<byte> SetUnknownHeaders(Span<byte> nativeMemory, ref HTTP_REQUEST_V1 requestStructure, IReadOnlyCollection<(string Key, string Value)> headerNames)
private unsafe Span<byte> SetUnknownHeaders(Span<byte> nativeMemory, ref HttpApiTypes.HTTP_REQUEST requestStructure, IReadOnlyCollection<(string Key, string Value)> headerNames)
{
var unknownHeaderStructureDestination = nativeMemory[Marshal.SizeOf<HTTP_REQUEST_V1>()..];
var unknownHeaderStructureDestination = nativeMemory.Slice(Marshal.SizeOf<HttpApiTypes.HTTP_REQUEST>());
fixed (byte* address = &MemoryMarshal.GetReference(unknownHeaderStructureDestination))
{
requestStructure.Headers.pUnknownHeaders = (HTTP_UNKNOWN_HEADER*)address;
requestStructure.Headers.pUnknownHeaders = (HttpApiTypes.HTTP_UNKNOWN_HEADER*)address;
}
requestStructure.Headers.UnknownHeaderCount += (ushort)headerNames.Count;

var unknownHeadersSize = Marshal.SizeOf<HTTP_UNKNOWN_HEADER>();
var dataDestination = unknownHeaderStructureDestination[(unknownHeadersSize * headerNames.Count)..];
foreach (var (headerKey, headerValue) in headerNames)
var unknownHeadersSize = Marshal.SizeOf<HttpApiTypes.HTTP_UNKNOWN_HEADER>();
var dataDestination = unknownHeaderStructureDestination.Slice(unknownHeadersSize * headerNames.Count);
foreach (var headerName in headerNames)
{
var unknownHeaderStructure = new HTTP_UNKNOWN_HEADER();
var nameLength = Encoding.ASCII.GetBytes(headerKey, dataDestination);
var unknownHeaderStructure = new HttpApiTypes.HTTP_UNKNOWN_HEADER();
int nameLength = Encoding.ASCII.GetBytes(headerName.Key, dataDestination);
fixed (byte* address = &MemoryMarshal.GetReference(dataDestination))
{
unknownHeaderStructure.pName = (PCSTR)address;
unknownHeaderStructure.pName = address;
unknownHeaderStructure.NameLength = (ushort)nameLength;
}
dataDestination = dataDestination[nameLength..];
dataDestination = dataDestination.Slice(nameLength);

if (!string.IsNullOrEmpty(headerValue))
if (!string.IsNullOrEmpty(headerName.Value))
{
var valueLength = Encoding.ASCII.GetBytes(headerValue, dataDestination);
int valueLength = Encoding.ASCII.GetBytes(headerName.Value, dataDestination);
fixed (byte* address = &MemoryMarshal.GetReference(dataDestination))
{
unknownHeaderStructure.pRawValue = (PCSTR)address;
unknownHeaderStructure.pRawValue = address;
unknownHeaderStructure.RawValueLength = (ushort)valueLength;
}
dataDestination = dataDestination[nameLength..];
dataDestination = dataDestination.Slice(nameLength);
}
MemoryMarshal.Write(unknownHeaderStructureDestination, in unknownHeaderStructure);
unknownHeaderStructureDestination = unknownHeaderStructureDestination[unknownHeadersSize..];
unknownHeaderStructureDestination = unknownHeaderStructureDestination.Slice(unknownHeadersSize);
}
return dataDestination;
}

private static List<(string, string)> GenerateUnknownHeaders(int count)
private IReadOnlyCollection<(string, string)> GenerateUnknownHeaders(int count)
{
var result = new List<(string, string)>();
for (var i = 0; i < count; i++)
for (int i = 0; i < count; i++)
{
result.Add(($"X-Custom-{i}", $"Value-{i}"));
}
Expand Down
129 changes: 67 additions & 62 deletions src/Servers/HttpSys/samples/TestClient/Program.cs
Original file line number Diff line number Diff line change
@@ -1,89 +1,94 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System;
using System.Net;
using System.Net.Http;
using System.Net.WebSockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace TestClient;

public class Program
namespace TestClient
{
private const string Address =
"http://localhost:5000/public/1kb.txt";
// "https://localhost:9090/public/1kb.txt";

public static void Main(string[] args)
public class Program
{
Console.WriteLine("Ready");
Console.ReadKey();
private const string Address =
"http://localhost:5000/public/1kb.txt";
// "https://localhost:9090/public/1kb.txt";

var handler = new HttpClientHandler();
handler.MaxConnectionsPerServer = 500;
handler.ServerCertificateCustomValidationCallback = HttpClientHandler.DangerousAcceptAnyServerCertificateValidator;
// handler.UseDefaultCredentials = true;
HttpClient client = new HttpClient(handler);
public static void Main(string[] args)
{
Console.WriteLine("Ready");
Console.ReadKey();

RunParallelRequests(client);
var handler = new HttpClientHandler();
handler.MaxConnectionsPerServer = 500;
handler.ServerCertificateCustomValidationCallback = HttpClientHandler.DangerousAcceptAnyServerCertificateValidator;
// handler.UseDefaultCredentials = true;
HttpClient client = new HttpClient(handler);

// RunManualRequests(client);
RunParallelRequests(client);

// RunWebSocketClient().Wait();
// RunManualRequests(client);

Console.WriteLine("Done");
// Console.ReadKey();
}
// RunWebSocketClient().Wait();

private static void RunManualRequests(HttpClient client)
{
while (true)
{
Console.WriteLine("Press any key to send request");
Console.ReadKey();
var result = client.GetAsync(Address).Result;
Console.WriteLine(result);
Console.WriteLine("Done");
// Console.ReadKey();
}
}

private static void RunParallelRequests(HttpClient client)
{
int completionCount = 0;
int iterations = 100000;
for (int i = 0; i < iterations; i++)
private static void RunManualRequests(HttpClient client)
{
client.GetAsync(Address)
.ContinueWith(t => Interlocked.Increment(ref completionCount));
while (true)
{
Console.WriteLine("Press any key to send request");
Console.ReadKey();
var result = client.GetAsync(Address).Result;
Console.WriteLine(result);
}
}

while (completionCount < iterations)
private static void RunParallelRequests(HttpClient client)
{
Thread.Sleep(10);
int completionCount = 0;
int iterations = 100000;
for (int i = 0; i < iterations; i++)
{
client.GetAsync(Address)
.ContinueWith(t => Interlocked.Increment(ref completionCount));
}

while (completionCount < iterations)
{
Thread.Sleep(10);
}
}
}

public static async Task RunWebSocketClient()
{
ClientWebSocket websocket = new ClientWebSocket();

string url = "ws://localhost:5000/";
Console.WriteLine("Connecting to: " + url);
await websocket.ConnectAsync(new Uri(url), CancellationToken.None);

string message = "Hello World";
Console.WriteLine("Sending message: " + message);
byte[] messageBytes = Encoding.UTF8.GetBytes(message);
await websocket.SendAsync(new ArraySegment<byte>(messageBytes), WebSocketMessageType.Text, true, CancellationToken.None);

byte[] incomingData = new byte[1024];
WebSocketReceiveResult result = await websocket.ReceiveAsync(new ArraySegment<byte>(incomingData), CancellationToken.None);

if (result.CloseStatus.HasValue)
{
Console.WriteLine("Closed; Status: " + result.CloseStatus + ", " + result.CloseStatusDescription);
}
else
public static async Task RunWebSocketClient()
{
Console.WriteLine("Received message: " + Encoding.UTF8.GetString(incomingData, 0, result.Count));
ClientWebSocket websocket = new ClientWebSocket();

string url = "ws://localhost:5000/";
Console.WriteLine("Connecting to: " + url);
await websocket.ConnectAsync(new Uri(url), CancellationToken.None);

string message = "Hello World";
Console.WriteLine("Sending message: " + message);
byte[] messageBytes = Encoding.UTF8.GetBytes(message);
await websocket.SendAsync(new ArraySegment<byte>(messageBytes), WebSocketMessageType.Text, true, CancellationToken.None);

byte[] incomingData = new byte[1024];
WebSocketReceiveResult result = await websocket.ReceiveAsync(new ArraySegment<byte>(incomingData), CancellationToken.None);

if (result.CloseStatus.HasValue)
{
Console.WriteLine("Closed; Status: " + result.CloseStatus + ", " + result.CloseStatusDescription);
}
else
{
Console.WriteLine("Received message: " + Encoding.UTF8.GetString(incomingData, 0, result.Count));
}
}
}
}
29 changes: 15 additions & 14 deletions src/Servers/HttpSys/src/AsyncAcceptContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using System.Diagnostics;
using System.Threading.Tasks.Sources;
using Microsoft.Extensions.Logging;
using Microsoft.AspNetCore.HttpSys.Internal;

namespace Microsoft.AspNetCore.Server.HttpSys;

Expand Down Expand Up @@ -45,9 +46,9 @@ internal ValueTask<RequestContext> AcceptAsync()

AllocateNativeRequest();

var statusCode = QueueBeginGetContext();
if (statusCode != ErrorCodes.ERROR_SUCCESS &&
statusCode != ErrorCodes.ERROR_IO_PENDING)
uint statusCode = QueueBeginGetContext();
if (statusCode != UnsafeNclNativeMethods.ErrorCodes.ERROR_SUCCESS &&
statusCode != UnsafeNclNativeMethods.ErrorCodes.ERROR_IO_PENDING)
{
// some other bad error, possible(?) return values are:
// ERROR_INVALID_HANDLE, ERROR_INSUFFICIENT_BUFFER, ERROR_OPERATION_ABORTED
Expand All @@ -62,16 +63,16 @@ private void IOCompleted(uint errorCode, uint numBytes, bool managed)
try
{
ObserveCompletion(managed); // expectation tracking
if (errorCode != ErrorCodes.ERROR_SUCCESS &&
errorCode != ErrorCodes.ERROR_MORE_DATA)
if (errorCode != UnsafeNclNativeMethods.ErrorCodes.ERROR_SUCCESS &&
errorCode != UnsafeNclNativeMethods.ErrorCodes.ERROR_MORE_DATA)
{
// (keep all the error handling in one place)
throw new HttpSysException((int)errorCode);
}

Debug.Assert(_requestContext != null);

if (errorCode == ErrorCodes.ERROR_SUCCESS)
if (errorCode == UnsafeNclNativeMethods.ErrorCodes.ERROR_SUCCESS)
{
var requestContext = _requestContext;
// It's important that we clear the request context before we set the result
Expand All @@ -93,10 +94,10 @@ private void IOCompleted(uint errorCode, uint numBytes, bool managed)
AllocateNativeRequest(numBytes, _requestContext.RequestId);

// We need to issue a new request, either because auth failed, or because our buffer was too small the first time.
var statusCode = QueueBeginGetContext();
uint statusCode = QueueBeginGetContext();

if (statusCode != ErrorCodes.ERROR_SUCCESS &&
statusCode != ErrorCodes.ERROR_IO_PENDING)
if (statusCode != UnsafeNclNativeMethods.ErrorCodes.ERROR_SUCCESS &&
statusCode != UnsafeNclNativeMethods.ErrorCodes.ERROR_IO_PENDING)
{
// some other bad error, possible(?) return values are:
// ERROR_INVALID_HANDLE, ERROR_INSUFFICIENT_BUFFER, ERROR_OPERATION_ABORTED
Expand Down Expand Up @@ -179,15 +180,15 @@ private uint QueueBeginGetContext()
_requestContext.RequestId,
// Small perf impact by not using HTTP_RECEIVE_REQUEST_FLAG_COPY_BODY
// if the request sends header+body in a single TCP packet
0u,
(uint)HttpApiTypes.HTTP_FLAGS.NONE,
_requestContext.NativeRequest,
_requestContext.Size,
&bytesTransferred,
_overlapped);

switch (statusCode)
{
case (ErrorCodes.ERROR_CONNECTION_INVALID or ErrorCodes.ERROR_INVALID_PARAMETER) when _requestContext.RequestId != 0:
case (UnsafeNclNativeMethods.ErrorCodes.ERROR_CONNECTION_INVALID or UnsafeNclNativeMethods.ErrorCodes.ERROR_INVALID_PARAMETER) when _requestContext.RequestId != 0:
// ERROR_CONNECTION_INVALID:
// The client reset the connection between the time we got the MORE_DATA error and when we called HttpReceiveHttpRequest
// with the new buffer. We can clear the request id and move on to the next request.
Expand All @@ -201,23 +202,23 @@ private uint QueueBeginGetContext()
_requestContext.RequestId = 0;
retry = true;
break;
case ErrorCodes.ERROR_MORE_DATA:
case UnsafeNclNativeMethods.ErrorCodes.ERROR_MORE_DATA:
// the buffer was not big enough to fit the headers, we need
// to read the RequestId returned, allocate a new buffer of the required size
// (uint)backingBuffer.Length - AlignmentPadding
CancelExpectCompletion(); // we'll "expect" again when we retry
AllocateNativeRequest(bytesTransferred);
retry = true;
break;
case ErrorCodes.ERROR_SUCCESS:
case UnsafeNclNativeMethods.ErrorCodes.ERROR_SUCCESS:
if (HttpSysListener.SkipIOCPCallbackOnSuccess)
{
// IO operation completed synchronously - callback won't be called to signal completion.
IOCompleted(statusCode, bytesTransferred, true); // marks completion
}
// else: callback fired by IOCP (at some point), which marks completion
break;
case ErrorCodes.ERROR_IO_PENDING:
case UnsafeNclNativeMethods.ErrorCodes.ERROR_IO_PENDING:
break; // no change to state - callback will occur at some point
default:
// fault code, not expecting an IOCP callback
Expand Down
Loading
Loading