Skip to content

Commit 0fbf919

Browse files
authored
Use the RequestContext as the threadpool workitem (#21294)
- Avoids a task allocation (reuses the existing RequestContext allocation) - Side benefit is that it's easier to see what things are queued into the threadpool for diagnostics!
1 parent 5c6f97b commit 0fbf919

File tree

2 files changed

+103
-86
lines changed

2 files changed

+103
-86
lines changed

src/Servers/HttpSys/src/MessagePump.cs

Lines changed: 23 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -22,11 +22,8 @@ internal class MessagePump : IServer
2222
private readonly ILogger _logger;
2323
private readonly HttpSysOptions _options;
2424

25-
private IHttpApplication<object> _application;
26-
2725
private int _maxAccepts;
2826
private int _acceptorCounts;
29-
private Action<object> _processRequest;
3027

3128
private volatile int _stopping;
3229
private int _outstandingRequests;
@@ -58,15 +55,16 @@ public MessagePump(IOptions<HttpSysOptions> options, ILoggerFactory loggerFactor
5855
_serverAddresses = new ServerAddressesFeature();
5956
Features.Set<IServerAddressesFeature>(_serverAddresses);
6057

61-
_processRequest = new Action<object>(ProcessRequestAsync);
6258
_maxAccepts = _options.MaxAccepts;
6359
}
6460

6561
internal HttpSysListener Listener { get; }
6662

63+
internal IHttpApplication<object> Application { get; set; }
64+
6765
public IFeatureCollection Features { get; }
6866

69-
private bool Stopping => _stopping == 1;
67+
internal bool Stopping => _stopping == 1;
7068

7169
public Task StartAsync<TContext>(IHttpApplication<TContext> application, CancellationToken cancellationToken)
7270
{
@@ -115,11 +113,11 @@ public Task StartAsync<TContext>(IHttpApplication<TContext> application, Cancell
115113
// else // Attaching to an existing queue, don't add a default.
116114

117115
// Can't call Start twice
118-
Contract.Assert(_application == null);
116+
Contract.Assert(Application == null);
119117

120118
Contract.Assert(application != null);
121119

122-
_application = new ApplicationWrapper<TContext>(application);
120+
Application = new ApplicationWrapper<TContext>(application);
123121

124122
Listener.Start();
125123

@@ -151,6 +149,21 @@ private void UpdateUrlPrefixes(IList<string> serverAddressCopy)
151149
}
152150
}
153151

152+
internal int IncrementOutstandingRequest()
153+
{
154+
return Interlocked.Increment(ref _outstandingRequests);
155+
}
156+
157+
internal int DecrementOutstandingRequest()
158+
{
159+
return Interlocked.Decrement(ref _outstandingRequests);
160+
}
161+
162+
internal void SetShutdownSignal()
163+
{
164+
_shutdownSignal.TrySetResult(null);
165+
}
166+
154167
// The message pump.
155168
// When we start listening for the next request on one thread, we may need to be sure that the
156169
// completion continues on another thread as to not block the current request processing.
@@ -165,6 +178,8 @@ private async void ProcessRequestsWorker()
165178
try
166179
{
167180
requestContext = await Listener.AcceptAsync().SupressContext();
181+
// Assign the message pump to this request context
182+
requestContext.MessagePump = this;
168183
}
169184
catch (Exception exception)
170185
{
@@ -181,7 +196,7 @@ private async void ProcessRequestsWorker()
181196
}
182197
try
183198
{
184-
Task ignored = Task.Factory.StartNew(_processRequest, requestContext);
199+
ThreadPool.UnsafeQueueUserWorkItem(requestContext, preferLocal: false);
185200
}
186201
catch (Exception ex)
187202
{
@@ -193,79 +208,6 @@ private async void ProcessRequestsWorker()
193208
Interlocked.Decrement(ref _acceptorCounts);
194209
}
195210

196-
private async void ProcessRequestAsync(object requestContextObj)
197-
{
198-
var requestContext = requestContextObj as RequestContext;
199-
try
200-
{
201-
if (Stopping)
202-
{
203-
SetFatalResponse(requestContext, 503);
204-
return;
205-
}
206-
207-
object context = null;
208-
Interlocked.Increment(ref _outstandingRequests);
209-
try
210-
{
211-
var featureContext = new FeatureContext(requestContext);
212-
context = _application.CreateContext(featureContext.Features);
213-
try
214-
{
215-
await _application.ProcessRequestAsync(context).SupressContext();
216-
await featureContext.CompleteAsync();
217-
}
218-
finally
219-
{
220-
await featureContext.OnCompleted();
221-
}
222-
_application.DisposeContext(context, null);
223-
requestContext.Dispose();
224-
}
225-
catch (Exception ex)
226-
{
227-
_logger.LogError(LoggerEventIds.RequestProcessError, ex, "ProcessRequestAsync");
228-
_application.DisposeContext(context, ex);
229-
if (requestContext.Response.HasStarted)
230-
{
231-
// HTTP/2 INTERNAL_ERROR = 0x2 https://tools.ietf.org/html/rfc7540#section-7
232-
// Otherwise the default is Cancel = 0x8.
233-
requestContext.SetResetCode(2);
234-
requestContext.Abort();
235-
}
236-
else
237-
{
238-
// We haven't sent a response yet, try to send a 500 Internal Server Error
239-
requestContext.Response.Headers.IsReadOnly = false;
240-
requestContext.Response.Trailers.IsReadOnly = false;
241-
requestContext.Response.Headers.Clear();
242-
requestContext.Response.Trailers.Clear();
243-
SetFatalResponse(requestContext, 500);
244-
}
245-
}
246-
finally
247-
{
248-
if (Interlocked.Decrement(ref _outstandingRequests) == 0 && Stopping)
249-
{
250-
_logger.LogInformation(LoggerEventIds.RequestsDrained, "All requests drained.");
251-
_shutdownSignal.TrySetResult(0);
252-
}
253-
}
254-
}
255-
catch (Exception ex)
256-
{
257-
_logger.LogError(LoggerEventIds.RequestError, ex, "ProcessRequestAsync");
258-
requestContext.Abort();
259-
}
260-
}
261-
262-
private static void SetFatalResponse(RequestContext context, int status)
263-
{
264-
context.Response.StatusCode = status;
265-
context.Response.ContentLength = 0;
266-
context.Dispose();
267-
}
268-
269211
public Task StopAsync(CancellationToken cancellationToken)
270212
{
271213
void RegisterCancelation()

src/Servers/HttpSys/src/RequestProcessing/RequestContext.cs

Lines changed: 80 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,7 @@
44
using System;
55
using System.Diagnostics;
66
using System.IO;
7-
using System.Runtime.CompilerServices;
87
using System.Security.Authentication.ExtendedProtection;
9-
using System.Security.Claims;
108
using System.Security.Principal;
119
using System.Threading;
1210
using System.Threading.Tasks;
@@ -16,7 +14,7 @@
1614

1715
namespace Microsoft.AspNetCore.Server.HttpSys
1816
{
19-
internal sealed class RequestContext : IDisposable
17+
internal sealed class RequestContext : IDisposable, IThreadPoolWorkItem
2018
{
2119
private static readonly Action<object> AbortDelegate = Abort;
2220

@@ -35,6 +33,8 @@ internal RequestContext(HttpSysListener server, NativeRequestContext memoryBlob)
3533
AllowSynchronousIO = server.Options.AllowSynchronousIO;
3634
}
3735

36+
internal MessagePump MessagePump { get; set; }
37+
3838
internal HttpSysListener Server { get; }
3939

4040
internal ILogger Logger => Server.Logger;
@@ -120,14 +120,14 @@ internal bool TryGetChannelBinding(ref ChannelBinding value)
120120
{
121121
if (!Request.IsHttps)
122122
{
123-
Logger.LogDebug(LoggerEventIds.ChannelBindingNeedsHttps,"TryGetChannelBinding; Channel binding requires HTTPS.");
123+
Logger.LogDebug(LoggerEventIds.ChannelBindingNeedsHttps, "TryGetChannelBinding; Channel binding requires HTTPS.");
124124
return false;
125125
}
126126

127127
value = ClientCertLoader.GetChannelBindingFromTls(Server.RequestQueue, Request.UConnectionId, Logger);
128128

129129
Debug.Assert(value != null, "GetChannelBindingFromTls returned null even though OS supposedly supports Extended Protection");
130-
Logger.LogDebug(LoggerEventIds.ChannelBindingRetrived,"Channel binding retrieved.");
130+
Logger.LogDebug(LoggerEventIds.ChannelBindingRetrived, "Channel binding retrieved.");
131131
return value != null;
132132
}
133133

@@ -239,5 +239,80 @@ internal unsafe void SetResetCode(int errorCode)
239239
// RequestQueueHandle may have been closed
240240
}
241241
}
242+
243+
public async void Execute()
244+
{
245+
var messagePump = MessagePump;
246+
var application = messagePump.Application;
247+
248+
try
249+
{
250+
if (messagePump.Stopping)
251+
{
252+
SetFatalResponse(503);
253+
return;
254+
}
255+
256+
object context = null;
257+
messagePump.IncrementOutstandingRequest();
258+
try
259+
{
260+
var featureContext = new FeatureContext(this);
261+
context = application.CreateContext(featureContext.Features);
262+
try
263+
{
264+
await application.ProcessRequestAsync(context).SupressContext();
265+
await featureContext.CompleteAsync();
266+
}
267+
finally
268+
{
269+
await featureContext.OnCompleted();
270+
}
271+
application.DisposeContext(context, null);
272+
Dispose();
273+
}
274+
catch (Exception ex)
275+
{
276+
Logger.LogError(LoggerEventIds.RequestProcessError, ex, "ProcessRequestAsync");
277+
application.DisposeContext(context, ex);
278+
if (Response.HasStarted)
279+
{
280+
// HTTP/2 INTERNAL_ERROR = 0x2 https://tools.ietf.org/html/rfc7540#section-7
281+
// Otherwise the default is Cancel = 0x8.
282+
SetResetCode(2);
283+
Abort();
284+
}
285+
else
286+
{
287+
// We haven't sent a response yet, try to send a 500 Internal Server Error
288+
Response.Headers.IsReadOnly = false;
289+
Response.Trailers.IsReadOnly = false;
290+
Response.Headers.Clear();
291+
Response.Trailers.Clear();
292+
SetFatalResponse(500);
293+
}
294+
}
295+
finally
296+
{
297+
if (messagePump.DecrementOutstandingRequest() == 0 && messagePump.Stopping)
298+
{
299+
Logger.LogInformation(LoggerEventIds.RequestsDrained, "All requests drained.");
300+
messagePump.SetShutdownSignal();
301+
}
302+
}
303+
}
304+
catch (Exception ex)
305+
{
306+
Logger.LogError(LoggerEventIds.RequestError, ex, "ProcessRequestAsync");
307+
Abort();
308+
}
309+
}
310+
311+
private void SetFatalResponse(int status)
312+
{
313+
Response.StatusCode = status;
314+
Response.ContentLength = 0;
315+
Dispose();
316+
}
242317
}
243318
}

0 commit comments

Comments
 (0)