Reduce allocations (#6132)

- Remove per request allocations by implementing IThreadPoolWorkItem on the IISHttpContext.
- Removed per operation allocations by using UnsafeQueueUserWorkItem in AsyncIOOperation.
- This should also reduce overhead by removing non-essential ExecutionContext propagation logic
This commit is contained in:
David Fowler 2018-12-28 21:18:58 -04:00 committed by GitHub
parent a7b783724e
commit dd912850eb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 55 additions and 46 deletions

View File

@ -25,7 +25,7 @@ using Microsoft.Extensions.Logging;
namespace Microsoft.AspNetCore.Server.IIS.Core
{
internal abstract partial class IISHttpContext : NativeRequestContext, IDisposable
internal abstract partial class IISHttpContext : NativeRequestContext, IThreadPoolWorkItem, IDisposable
{
private const int MinAllocBufferSize = 2048;
private const int PauseWriterThreshold = 65536;
@ -531,5 +531,40 @@ namespace Microsoft.AspNetCore.Server.IIS.Core
}
return null;
}
// Invoked by the thread pool
public void Execute()
{
_ = HandleRequest();
}
private async Task HandleRequest()
{
bool successfulRequest = false;
try
{
successfulRequest = await ProcessRequestAsync();
}
catch (Exception ex)
{
_logger.LogError(0, ex, $"Unexpected exception in {nameof(IISHttpContext)}.{nameof(HandleRequest)}.");
}
finally
{
// Post completion after completing the request to resume the state machine
PostCompletion(ConvertRequestCompletionResults(successfulRequest));
Server.DecrementRequests();
// Dispose the context
Dispose();
}
}
private static NativeMethods.REQUEST_NOTIFICATION_STATUS ConvertRequestCompletionResults(bool success)
{
return success ? NativeMethods.REQUEST_NOTIFICATION_STATUS.RQ_NOTIFICATION_CONTINUE
: NativeMethods.REQUEST_NOTIFICATION_STATUS.RQ_NOTIFICATION_FINISH_REQUEST;
}
}
}

View File

@ -152,6 +152,21 @@ namespace Microsoft.AspNetCore.Server.IIS.Core
_nativeApplication.Dispose();
}
private void IncrementRequests()
{
Interlocked.Increment(ref _outstandingRequests);
}
internal void DecrementRequests()
{
if (Interlocked.Decrement(ref _outstandingRequests) == 0 && Stopping)
{
// All requests have been drained.
_nativeApplication.StopCallsIntoManaged();
_shutdownSignal.TrySetResult(null);
}
}
private static NativeMethods.REQUEST_NOTIFICATION_STATUS HandleRequest(IntPtr pInProcessHandler, IntPtr pvRequestContext)
{
IISHttpServer server = null;
@ -159,11 +174,12 @@ namespace Microsoft.AspNetCore.Server.IIS.Core
{
// Unwrap the server so we can create an http context and process the request
server = (IISHttpServer)GCHandle.FromIntPtr(pvRequestContext).Target;
Interlocked.Increment(ref server._outstandingRequests);
server.IncrementRequests();
var context = server._iisContextFactory.CreateHttpContext(pInProcessHandler);
ThreadPool.QueueUserWorkItem(state => _ = HandleRequest((IISHttpContext)state), context);
ThreadPool.UnsafeQueueUserWorkItem(context, preferLocal: false);
return NativeMethods.REQUEST_NOTIFICATION_STATUS.RQ_NOTIFICATION_PENDING;
}
catch (Exception ex)
@ -174,23 +190,6 @@ namespace Microsoft.AspNetCore.Server.IIS.Core
}
}
private static async Task HandleRequest(IISHttpContext context)
{
bool successfulRequest = false;
try
{
successfulRequest = await context.ProcessRequestAsync();
}
catch (Exception ex)
{
context.Server._logger.LogError(0, ex, $"Unexpected exception in {nameof(IISHttpServer)}.{nameof(HandleRequest)}.");
}
finally
{
CompleteRequest(context, successfulRequest);
}
}
private static bool HandleShutdown(IntPtr pvRequestContext)
{
IISHttpServer server = null;
@ -238,28 +237,6 @@ namespace Microsoft.AspNetCore.Server.IIS.Core
}
}
private static void CompleteRequest(IISHttpContext context, bool result)
{
// Post completion after completing the request to resume the state machine
context.PostCompletion(ConvertRequestCompletionResults(result));
if (Interlocked.Decrement(ref context.Server._outstandingRequests) == 0 && context.Server.Stopping)
{
// All requests have been drained.
context.Server._nativeApplication.StopCallsIntoManaged();
context.Server._shutdownSignal.TrySetResult(null);
}
// Dispose the context
context.Dispose();
}
private static NativeMethods.REQUEST_NOTIFICATION_STATUS ConvertRequestCompletionResults(bool success)
{
return success ? NativeMethods.REQUEST_NOTIFICATION_STATUS.RQ_NOTIFICATION_CONTINUE
: NativeMethods.REQUEST_NOTIFICATION_STATUS.RQ_NOTIFICATION_FINISH_REQUEST;
}
private class IISContextFactory<T> : IISContextFactory
{
private readonly IHttpApplication<T> _application;

View File

@ -151,10 +151,7 @@ namespace Microsoft.AspNetCore.Server.IIS.Core.IO
{
if (Continuation != null)
{
// TODO: use generic overload when code moved to be netcoreapp only
var continuation = Continuation;
var state = State;
ThreadPool.QueueUserWorkItem(_ => continuation(state));
ThreadPool.UnsafeQueueUserWorkItem(Continuation, State, preferLocal: false);
}
}
}