Skip to content

Commit dca408e

Browse files
authored
Refactor how we capture the IHttpApplication (#28413)
- Use a similar technique to capture the generic IHttpApplication that we do in IIS and Kestrel. Now the AsyncAcceptContext takes an IRequestContextFactory to delegate RequestContext creation to pull the request from the HTTP.sys queue.
1 parent 0f10946 commit dca408e

File tree

7 files changed

+166
-111
lines changed

7 files changed

+166
-111
lines changed
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3+
4+
using Microsoft.AspNetCore.Hosting.Server;
5+
6+
namespace Microsoft.AspNetCore.Server.HttpSys
7+
{
8+
internal class ApplicationRequestContextFactory<TContext> : IRequestContextFactory
9+
{
10+
private readonly IHttpApplication<TContext> _application;
11+
private readonly MessagePump _messagePump;
12+
13+
public ApplicationRequestContextFactory(IHttpApplication<TContext> application, MessagePump messagePump)
14+
{
15+
_application = application;
16+
_messagePump = messagePump;
17+
}
18+
19+
public RequestContext CreateRequestContext(uint? bufferSize, ulong requestId)
20+
{
21+
return new RequestContext<TContext>(_application, _messagePump, _messagePump.Listener, bufferSize, requestId);
22+
}
23+
}
24+
}

src/Servers/HttpSys/src/AsyncAcceptContext.cs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,10 +24,12 @@ internal unsafe class AsyncAcceptContext : IValueTaskSource<RequestContext>, IDi
2424
};
2525

2626
private RequestContext _requestContext;
27+
private readonly IRequestContextFactory _requestContextFactory;
2728

28-
internal AsyncAcceptContext(HttpSysListener server)
29+
internal AsyncAcceptContext(HttpSysListener server, IRequestContextFactory requestContextFactory)
2930
{
3031
Server = server;
32+
_requestContextFactory = requestContextFactory;
3133
_preallocatedOverlapped = new(IOCallback, state: this, pinData: null);
3234
}
3335

@@ -193,7 +195,7 @@ private void AllocateNativeRequest(uint? size = null, ulong requestId = 0)
193195
boundHandle.FreeNativeOverlapped(_overlapped);
194196
}
195197

196-
_requestContext = new RequestContext(Server, size, requestId);
198+
_requestContext = _requestContextFactory.CreateRequestContext(size, requestId);
197199
_overlapped = boundHandle.AllocateNativeOverlapped(_preallocatedOverlapped);
198200
}
199201

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3+
4+
namespace Microsoft.AspNetCore.Server.HttpSys
5+
{
6+
internal interface IRequestContextFactory
7+
{
8+
RequestContext CreateRequestContext(uint? bufferSize, ulong requestId);
9+
}
10+
}

src/Servers/HttpSys/src/MessagePump.cs

Lines changed: 5 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ public MessagePump(IOptions<HttpSysOptions> options, ILoggerFactory loggerFactor
6666

6767
internal HttpSysListener Listener { get; }
6868

69-
internal IHttpApplication<object> Application { get; set; }
69+
internal IRequestContextFactory RequestContextFactory { get; set; }
7070

7171
public IFeatureCollection Features { get; }
7272

@@ -118,12 +118,12 @@ public Task StartAsync<TContext>(IHttpApplication<TContext> application, Cancell
118118
}
119119
// else // Attaching to an existing queue, don't add a default.
120120

121-
// Can't call Start twice
122-
Debug.Assert(Application == null);
121+
// Can't start twice
122+
Debug.Assert(RequestContextFactory != null);
123123

124124
Debug.Assert(application != null);
125125

126-
Application = new ApplicationWrapper<TContext>(application);
126+
RequestContextFactory = new ApplicationRequestContextFactory<TContext>(application, this);
127127

128128
Listener.Start();
129129

@@ -179,7 +179,7 @@ internal void SetShutdownSignal()
179179
private async Task ProcessRequestsWorker()
180180
{
181181
// Allocate and accept context per loop and reuse it for all accepts
182-
using var acceptContext = new AsyncAcceptContext(Listener);
182+
using var acceptContext = new AsyncAcceptContext(Listener, RequestContextFactory);
183183

184184
int workerIndex = Interlocked.Increment(ref _acceptorCounts);
185185
while (!Stopping && workerIndex <= _maxAccepts)
@@ -189,8 +189,6 @@ private async Task ProcessRequestsWorker()
189189
try
190190
{
191191
requestContext = await Listener.AcceptAsync(acceptContext);
192-
// Assign the message pump to this request context
193-
requestContext.MessagePump = this;
194192
}
195193
catch (Exception exception)
196194
{
@@ -268,30 +266,5 @@ public void Dispose()
268266

269267
Listener.Dispose();
270268
}
271-
272-
private class ApplicationWrapper<TContext> : IHttpApplication<object>
273-
{
274-
private readonly IHttpApplication<TContext> _application;
275-
276-
public ApplicationWrapper(IHttpApplication<TContext> application)
277-
{
278-
_application = application;
279-
}
280-
281-
public object CreateContext(IFeatureCollection contextFeatures)
282-
{
283-
return _application.CreateContext(contextFeatures);
284-
}
285-
286-
public void DisposeContext(object context, Exception exception)
287-
{
288-
_application.DisposeContext((TContext)context, exception);
289-
}
290-
291-
public Task ProcessRequestAsync(object context)
292-
{
293-
return _application.ProcessRequestAsync((TContext)context);
294-
}
295-
}
296269
}
297270
}

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

Lines changed: 9 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414

1515
namespace Microsoft.AspNetCore.Server.HttpSys
1616
{
17-
internal sealed partial class RequestContext : NativeRequestContext, IThreadPoolWorkItem
17+
internal partial class RequestContext : NativeRequestContext, IThreadPoolWorkItem
1818
{
1919
private static readonly Action<object> AbortDelegate = Abort;
2020
private CancellationTokenSource _requestAbortSource;
@@ -29,8 +29,6 @@ public RequestContext(HttpSysListener server, uint? bufferSize, ulong requestId)
2929
AllowSynchronousIO = server.Options.AllowSynchronousIO;
3030
}
3131

32-
internal MessagePump MessagePump { get; set; }
33-
3432
internal HttpSysListener Server { get; }
3533

3634
internal ILogger Logger => Server.Logger;
@@ -243,82 +241,17 @@ internal unsafe void SetResetCode(int errorCode)
243241
}
244242
}
245243

246-
public async void Execute()
244+
protected virtual Task ExecuteAsync()
247245
{
248-
var messagePump = MessagePump;
249-
var application = messagePump.Application;
250-
251-
try
252-
{
253-
if (messagePump.Stopping)
254-
{
255-
SetFatalResponse(503);
256-
return;
257-
}
258-
259-
object context = null;
260-
messagePump.IncrementOutstandingRequest();
261-
try
262-
{
263-
context = application.CreateContext(Features);
264-
try
265-
{
266-
await application.ProcessRequestAsync(context);
267-
await CompleteAsync();
268-
}
269-
finally
270-
{
271-
await OnCompleted();
272-
}
273-
application.DisposeContext(context, null);
274-
Dispose();
275-
}
276-
catch (Exception ex)
277-
{
278-
Logger.LogError(LoggerEventIds.RequestProcessError, ex, "ProcessRequestAsync");
279-
application.DisposeContext(context, ex);
280-
if (Response.HasStarted)
281-
{
282-
// HTTP/2 INTERNAL_ERROR = 0x2 https://tools.ietf.org/html/rfc7540#section-7
283-
// Otherwise the default is Cancel = 0x8.
284-
SetResetCode(2);
285-
Abort();
286-
}
287-
else
288-
{
289-
// We haven't sent a response yet, try to send a 500 Internal Server Error
290-
Response.Headers.IsReadOnly = false;
291-
Response.Trailers.IsReadOnly = false;
292-
Response.Headers.Clear();
293-
Response.Trailers.Clear();
294-
295-
if (ex is BadHttpRequestException badHttpRequestException)
296-
{
297-
SetFatalResponse(badHttpRequestException.StatusCode);
298-
}
299-
else
300-
{
301-
SetFatalResponse(StatusCodes.Status500InternalServerError);
302-
}
303-
}
304-
}
305-
finally
306-
{
307-
if (messagePump.DecrementOutstandingRequest() == 0 && messagePump.Stopping)
308-
{
309-
Logger.LogInformation(LoggerEventIds.RequestsDrained, "All requests drained.");
310-
messagePump.SetShutdownSignal();
311-
}
312-
}
313-
}
314-
catch (Exception ex)
315-
{
316-
Logger.LogError(LoggerEventIds.RequestError, ex, "ProcessRequestAsync");
317-
Abort();
318-
}
246+
return Task.CompletedTask;
247+
}
248+
249+
public void Execute()
250+
{
251+
_ = ExecuteAsync();
319252
}
320253

321-
private void SetFatalResponse(int status)
254+
protected void SetFatalResponse(int status)
322255
{
323256
Response.StatusCode = status;
324257
Response.ContentLength = 0;
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
using System;
2+
using System.Threading.Tasks;
3+
using Microsoft.AspNetCore.Hosting.Server;
4+
using Microsoft.AspNetCore.Http;
5+
using Microsoft.Extensions.Logging;
6+
7+
namespace Microsoft.AspNetCore.Server.HttpSys
8+
{
9+
internal sealed class RequestContext<TContext> : RequestContext
10+
{
11+
private readonly IHttpApplication<TContext> _application;
12+
private readonly MessagePump _messagePump;
13+
14+
public RequestContext(IHttpApplication<TContext> application, MessagePump messagePump, HttpSysListener server, uint? bufferSize, ulong requestId)
15+
: base(server, bufferSize, requestId)
16+
{
17+
_application = application;
18+
_messagePump = messagePump;
19+
}
20+
21+
protected override async Task ExecuteAsync()
22+
{
23+
var messagePump = _messagePump;
24+
var application = _application;
25+
26+
try
27+
{
28+
if (messagePump.Stopping)
29+
{
30+
SetFatalResponse(503);
31+
return;
32+
}
33+
34+
TContext context = default;
35+
messagePump.IncrementOutstandingRequest();
36+
try
37+
{
38+
context = application.CreateContext(Features);
39+
try
40+
{
41+
await application.ProcessRequestAsync(context);
42+
await CompleteAsync();
43+
}
44+
finally
45+
{
46+
await OnCompleted();
47+
}
48+
application.DisposeContext(context, null);
49+
Dispose();
50+
}
51+
catch (Exception ex)
52+
{
53+
Logger.LogError(LoggerEventIds.RequestProcessError, ex, "ProcessRequestAsync");
54+
application.DisposeContext(context, ex);
55+
if (Response.HasStarted)
56+
{
57+
// HTTP/2 INTERNAL_ERROR = 0x2 https://tools.ietf.org/html/rfc7540#section-7
58+
// Otherwise the default is Cancel = 0x8.
59+
SetResetCode(2);
60+
Abort();
61+
}
62+
else
63+
{
64+
// We haven't sent a response yet, try to send a 500 Internal Server Error
65+
Response.Headers.IsReadOnly = false;
66+
Response.Trailers.IsReadOnly = false;
67+
Response.Headers.Clear();
68+
Response.Trailers.Clear();
69+
70+
if (ex is BadHttpRequestException badHttpRequestException)
71+
{
72+
SetFatalResponse(badHttpRequestException.StatusCode);
73+
}
74+
else
75+
{
76+
SetFatalResponse(StatusCodes.Status500InternalServerError);
77+
}
78+
}
79+
}
80+
finally
81+
{
82+
if (messagePump.DecrementOutstandingRequest() == 0 && messagePump.Stopping)
83+
{
84+
Logger.LogInformation(LoggerEventIds.RequestsDrained, "All requests drained.");
85+
messagePump.SetShutdownSignal();
86+
}
87+
}
88+
}
89+
catch (Exception ex)
90+
{
91+
Logger.LogError(LoggerEventIds.RequestError, ex, "ProcessRequestAsync");
92+
Abort();
93+
}
94+
}
95+
}
96+
97+
}

src/Servers/HttpSys/test/FunctionalTests/Listener/Utilities.cs

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,8 @@ internal static HttpSysListener CreateServerOnExistingQueue(AuthenticationScheme
113113
/// </summary>
114114
internal static async Task<RequestContext> AcceptAsync(this HttpSysListener server, TimeSpan timeout)
115115
{
116-
var acceptContext = new AsyncAcceptContext(server);
116+
var factory = new TestRequestContextFactory(server);
117+
var acceptContext = new AsyncAcceptContext(server, factory);
117118
var acceptTask = server.AcceptAsync(acceptContext).AsTask();
118119
var completedTask = await Task.WhenAny(acceptTask, Task.Delay(timeout));
119120

@@ -143,5 +144,20 @@ internal static async Task<RequestContext> Before<T>(this Task<RequestContext> a
143144
throw new InvalidOperationException("The response completed prematurely: " + response.ToString());
144145
}
145146
}
147+
148+
private class TestRequestContextFactory : IRequestContextFactory
149+
{
150+
private readonly HttpSysListener _server;
151+
152+
public TestRequestContextFactory(HttpSysListener server)
153+
{
154+
_server = server;
155+
}
156+
157+
public RequestContext CreateRequestContext(uint? bufferSize, ulong requestId)
158+
{
159+
return new RequestContext(_server, bufferSize, requestId);
160+
}
161+
}
146162
}
147163
}

0 commit comments

Comments
 (0)