Skip to content

Commit dd91285

Browse files
authored
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
1 parent a7b7837 commit dd91285

File tree

3 files changed

+55
-46
lines changed

3 files changed

+55
-46
lines changed

src/Servers/IIS/src/Microsoft.AspNetCore.Server.IIS/Core/IISHttpContext.cs

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525

2626
namespace Microsoft.AspNetCore.Server.IIS.Core
2727
{
28-
internal abstract partial class IISHttpContext : NativeRequestContext, IDisposable
28+
internal abstract partial class IISHttpContext : NativeRequestContext, IThreadPoolWorkItem, IDisposable
2929
{
3030
private const int MinAllocBufferSize = 2048;
3131
private const int PauseWriterThreshold = 65536;
@@ -531,5 +531,40 @@ private WindowsPrincipal GetWindowsPrincipal()
531531
}
532532
return null;
533533
}
534+
535+
// Invoked by the thread pool
536+
public void Execute()
537+
{
538+
_ = HandleRequest();
539+
}
540+
541+
private async Task HandleRequest()
542+
{
543+
bool successfulRequest = false;
544+
try
545+
{
546+
successfulRequest = await ProcessRequestAsync();
547+
}
548+
catch (Exception ex)
549+
{
550+
_logger.LogError(0, ex, $"Unexpected exception in {nameof(IISHttpContext)}.{nameof(HandleRequest)}.");
551+
}
552+
finally
553+
{
554+
// Post completion after completing the request to resume the state machine
555+
PostCompletion(ConvertRequestCompletionResults(successfulRequest));
556+
557+
Server.DecrementRequests();
558+
559+
// Dispose the context
560+
Dispose();
561+
}
562+
}
563+
564+
private static NativeMethods.REQUEST_NOTIFICATION_STATUS ConvertRequestCompletionResults(bool success)
565+
{
566+
return success ? NativeMethods.REQUEST_NOTIFICATION_STATUS.RQ_NOTIFICATION_CONTINUE
567+
: NativeMethods.REQUEST_NOTIFICATION_STATUS.RQ_NOTIFICATION_FINISH_REQUEST;
568+
}
534569
}
535570
}

src/Servers/IIS/src/Microsoft.AspNetCore.Server.IIS/Core/IISHttpServer.cs

Lines changed: 18 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -152,18 +152,34 @@ public void Dispose()
152152
_nativeApplication.Dispose();
153153
}
154154

155+
private void IncrementRequests()
156+
{
157+
Interlocked.Increment(ref _outstandingRequests);
158+
}
159+
160+
internal void DecrementRequests()
161+
{
162+
if (Interlocked.Decrement(ref _outstandingRequests) == 0 && Stopping)
163+
{
164+
// All requests have been drained.
165+
_nativeApplication.StopCallsIntoManaged();
166+
_shutdownSignal.TrySetResult(null);
167+
}
168+
}
169+
155170
private static NativeMethods.REQUEST_NOTIFICATION_STATUS HandleRequest(IntPtr pInProcessHandler, IntPtr pvRequestContext)
156171
{
157172
IISHttpServer server = null;
158173
try
159174
{
160175
// Unwrap the server so we can create an http context and process the request
161176
server = (IISHttpServer)GCHandle.FromIntPtr(pvRequestContext).Target;
162-
Interlocked.Increment(ref server._outstandingRequests);
177+
server.IncrementRequests();
163178

164179
var context = server._iisContextFactory.CreateHttpContext(pInProcessHandler);
165180

166-
ThreadPool.QueueUserWorkItem(state => _ = HandleRequest((IISHttpContext)state), context);
181+
ThreadPool.UnsafeQueueUserWorkItem(context, preferLocal: false);
182+
167183
return NativeMethods.REQUEST_NOTIFICATION_STATUS.RQ_NOTIFICATION_PENDING;
168184
}
169185
catch (Exception ex)
@@ -174,23 +190,6 @@ private static NativeMethods.REQUEST_NOTIFICATION_STATUS HandleRequest(IntPtr pI
174190
}
175191
}
176192

177-
private static async Task HandleRequest(IISHttpContext context)
178-
{
179-
bool successfulRequest = false;
180-
try
181-
{
182-
successfulRequest = await context.ProcessRequestAsync();
183-
}
184-
catch (Exception ex)
185-
{
186-
context.Server._logger.LogError(0, ex, $"Unexpected exception in {nameof(IISHttpServer)}.{nameof(HandleRequest)}.");
187-
}
188-
finally
189-
{
190-
CompleteRequest(context, successfulRequest);
191-
}
192-
}
193-
194193
private static bool HandleShutdown(IntPtr pvRequestContext)
195194
{
196195
IISHttpServer server = null;
@@ -238,28 +237,6 @@ private static NativeMethods.REQUEST_NOTIFICATION_STATUS OnAsyncCompletion(IntPt
238237
}
239238
}
240239

241-
private static void CompleteRequest(IISHttpContext context, bool result)
242-
{
243-
// Post completion after completing the request to resume the state machine
244-
context.PostCompletion(ConvertRequestCompletionResults(result));
245-
246-
if (Interlocked.Decrement(ref context.Server._outstandingRequests) == 0 && context.Server.Stopping)
247-
{
248-
// All requests have been drained.
249-
context.Server._nativeApplication.StopCallsIntoManaged();
250-
context.Server._shutdownSignal.TrySetResult(null);
251-
}
252-
253-
// Dispose the context
254-
context.Dispose();
255-
}
256-
257-
private static NativeMethods.REQUEST_NOTIFICATION_STATUS ConvertRequestCompletionResults(bool success)
258-
{
259-
return success ? NativeMethods.REQUEST_NOTIFICATION_STATUS.RQ_NOTIFICATION_CONTINUE
260-
: NativeMethods.REQUEST_NOTIFICATION_STATUS.RQ_NOTIFICATION_FINISH_REQUEST;
261-
}
262-
263240
private class IISContextFactory<T> : IISContextFactory
264241
{
265242
private readonly IHttpApplication<T> _application;

src/Servers/IIS/src/Microsoft.AspNetCore.Server.IIS/Core/IO/AsyncIOOperation.cs

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -151,10 +151,7 @@ public void Invoke()
151151
{
152152
if (Continuation != null)
153153
{
154-
// TODO: use generic overload when code moved to be netcoreapp only
155-
var continuation = Continuation;
156-
var state = State;
157-
ThreadPool.QueueUserWorkItem(_ => continuation(state));
154+
ThreadPool.UnsafeQueueUserWorkItem(Continuation, State, preferLocal: false);
158155
}
159156
}
160157
}

0 commit comments

Comments
 (0)