Skip to content

Commit bbb851e

Browse files
authored
General cleanup in IIS (#24754)
1 parent e041390 commit bbb851e

18 files changed

+207
-101
lines changed

src/Servers/IIS/IIS/src/Core/IISHttpContext.FeatureCollection.cs

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -271,7 +271,7 @@ string IServerVariablesFeature.this[string variableName]
271271
// Synchronize access to native methods that might run in parallel with IO loops
272272
lock (_contextLock)
273273
{
274-
return NativeMethods.HttpTryGetServerVariable(_pInProcessHandler, variableName, out var value) ? value : null;
274+
return NativeMethods.HttpTryGetServerVariable(_requestNativeHandle, variableName, out var value) ? value : null;
275275
}
276276
}
277277
set
@@ -284,7 +284,7 @@ string IServerVariablesFeature.this[string variableName]
284284
// Synchronize access to native methods that might run in parallel with IO loops
285285
lock (_contextLock)
286286
{
287-
NativeMethods.HttpSetServerVariable(_pInProcessHandler, variableName, value);
287+
NativeMethods.HttpSetServerVariable(_requestNativeHandle, variableName, value);
288288
}
289289
}
290290
}
@@ -345,7 +345,7 @@ async Task<Stream> IHttpUpgradeFeature.UpgradeAsync()
345345
HasStartedConsumingRequestBody = false;
346346

347347
// Upgrade async will cause the stream processing to go into duplex mode
348-
AsyncIO = new WebSocketsAsyncIOEngine(_contextLock, _pInProcessHandler);
348+
AsyncIO = new WebSocketsAsyncIOEngine(_contextLock, _requestNativeHandle);
349349

350350
await InitializeResponse(flushHeaders: true);
351351

@@ -419,7 +419,7 @@ unsafe X509Certificate2 ITlsConnectionFeature.ClientCertificate
419419
internal IHttpResponseTrailersFeature GetResponseTrailersFeature()
420420
{
421421
// Check version is above 2.
422-
if (HttpVersion >= System.Net.HttpVersion.Version20 && NativeMethods.HttpSupportTrailer(_pInProcessHandler))
422+
if (HttpVersion >= System.Net.HttpVersion.Version20 && NativeMethods.HttpSupportTrailer(_requestNativeHandle))
423423
{
424424
return this;
425425
}
@@ -436,7 +436,7 @@ IHeaderDictionary IHttpResponseTrailersFeature.Trailers
436436
internal IHttpResetFeature GetResetFeature()
437437
{
438438
// Check version is above 2.
439-
if (HttpVersion >= System.Net.HttpVersion.Version20 && NativeMethods.HttpSupportTrailer(_pInProcessHandler))
439+
if (HttpVersion >= System.Net.HttpVersion.Version20 && NativeMethods.HttpSupportTrailer(_requestNativeHandle))
440440
{
441441
return this;
442442
}
@@ -457,12 +457,12 @@ void IHttpResetFeature.Reset(int errorCode)
457457

458458
internal unsafe void SetResetCode(int errorCode)
459459
{
460-
NativeMethods.HttpResetStream(_pInProcessHandler, (ulong)errorCode);
460+
NativeMethods.HttpResetStream(_requestNativeHandle, (ulong)errorCode);
461461
}
462462

463463
void IHttpResponseBodyFeature.DisableBuffering()
464464
{
465-
NativeMethods.HttpDisableBuffering(_pInProcessHandler);
465+
NativeMethods.HttpDisableBuffering(_requestNativeHandle);
466466
DisableCompression();
467467
}
468468

src/Servers/IIS/IIS/src/Core/IISHttpContext.IO.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -272,7 +272,7 @@ public void Abort(Exception reason)
272272
{
273273
_bodyOutput.Abort(reason);
274274
_streams.Abort(reason);
275-
NativeMethods.HttpCloseConnection(_pInProcessHandler);
275+
NativeMethods.HttpCloseConnection(_requestNativeHandle);
276276

277277
AbortIO(clientDisconnect: false);
278278
}

src/Servers/IIS/IIS/src/Core/IISHttpContext.cs

Lines changed: 24 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ internal abstract partial class IISHttpContext : NativeRequestContext, IThreadPo
3434
private const int PauseWriterThreshold = 65536;
3535
private const int ResumeWriterTheshold = PauseWriterThreshold / 2;
3636

37-
protected readonly IntPtr _pInProcessHandler;
37+
protected readonly NativeSafeHandle _requestNativeHandle;
3838

3939
private readonly IISServerOptions _options;
4040

@@ -75,15 +75,15 @@ internal abstract partial class IISHttpContext : NativeRequestContext, IThreadPo
7575

7676
internal unsafe IISHttpContext(
7777
MemoryPool<byte> memoryPool,
78-
IntPtr pInProcessHandler,
78+
NativeSafeHandle pInProcessHandler,
7979
IISServerOptions options,
8080
IISHttpServer server,
8181
ILogger logger,
8282
bool useLatin1)
8383
: base((HttpApiTypes.HTTP_REQUEST*)NativeMethods.HttpGetRawRequest(pInProcessHandler), useLatin1: useLatin1)
8484
{
8585
_memoryPool = memoryPool;
86-
_pInProcessHandler = pInProcessHandler;
86+
_requestNativeHandle = pInProcessHandler;
8787
_options = options;
8888
_server = server;
8989
_logger = logger;
@@ -179,7 +179,7 @@ protected void InitializeContext()
179179

180180
ResetFeatureCollection();
181181

182-
if (!_server.IsWebSocketAvailable(_pInProcessHandler))
182+
if (!_server.IsWebSocketAvailable(_requestNativeHandle))
183183
{
184184
_currentIHttpUpgradeFeature = null;
185185
}
@@ -198,7 +198,7 @@ protected void InitializeContext()
198198
_bodyOutput = new OutputProducer(pipe);
199199
}
200200

201-
NativeMethods.HttpSetManagedContext(_pInProcessHandler, (IntPtr)_thisHandle);
201+
NativeMethods.HttpSetManagedContext(_requestNativeHandle, (IntPtr)_thisHandle);
202202
}
203203

204204
private string GetOriginalPath()
@@ -324,7 +324,7 @@ private void EnsureIOInitialized()
324324
// If at this point request was not upgraded just start a normal IO engine
325325
if (AsyncIO == null)
326326
{
327-
AsyncIO = new AsyncIOEngine(_contextLock, _pInProcessHandler);
327+
AsyncIO = new AsyncIOEngine(_contextLock, _requestNativeHandle);
328328
}
329329
}
330330

@@ -383,7 +383,7 @@ public unsafe void SetResponseHeaders()
383383
var reasonPhrase = string.IsNullOrEmpty(ReasonPhrase) ? ReasonPhrases.GetReasonPhrase(StatusCode) : ReasonPhrase;
384384

385385
// This copies data into the underlying buffer
386-
NativeMethods.HttpSetResponseStatusCode(_pInProcessHandler, (ushort)StatusCode, reasonPhrase);
386+
NativeMethods.HttpSetResponseStatusCode(_requestNativeHandle, (ushort)StatusCode, reasonPhrase);
387387

388388
HttpResponseHeaders.IsReadOnly = true;
389389
foreach (var headerPair in HttpResponseHeaders)
@@ -412,12 +412,12 @@ public unsafe void SetResponseHeaders()
412412
var headerNameBytes = Encoding.UTF8.GetBytes(headerPair.Key);
413413
fixed (byte* pHeaderName = headerNameBytes)
414414
{
415-
NativeMethods.HttpResponseSetUnknownHeader(_pInProcessHandler, pHeaderName, pHeaderValue, (ushort)headerValueBytes.Length, fReplace: isFirst);
415+
NativeMethods.HttpResponseSetUnknownHeader(_requestNativeHandle, pHeaderName, pHeaderValue, (ushort)headerValueBytes.Length, fReplace: isFirst);
416416
}
417417
}
418418
else
419419
{
420-
NativeMethods.HttpResponseSetKnownHeader(_pInProcessHandler, knownHeaderIndex, pHeaderValue, (ushort)headerValueBytes.Length, fReplace: isFirst);
420+
NativeMethods.HttpResponseSetKnownHeader(_requestNativeHandle, knownHeaderIndex, pHeaderValue, (ushort)headerValueBytes.Length, fReplace: isFirst);
421421
}
422422
}
423423
}
@@ -451,7 +451,7 @@ public unsafe void SetResponseTrailers()
451451
var headerValueBytes = Encoding.UTF8.GetBytes(headerValue);
452452
fixed (byte* pHeaderValue = headerValueBytes)
453453
{
454-
NativeMethods.HttpResponseSetTrailer(_pInProcessHandler, pHeaderName, pHeaderValue, (ushort)headerValueBytes.Length, replace: isFirst);
454+
NativeMethods.HttpResponseSetTrailer(_requestNativeHandle, pHeaderName, pHeaderValue, (ushort)headerValueBytes.Length, replace: isFirst);
455455
}
456456

457457
isFirst = false;
@@ -576,8 +576,8 @@ protected void ReportApplicationError(Exception ex)
576576

577577
public void PostCompletion(NativeMethods.REQUEST_NOTIFICATION_STATUS requestNotificationStatus)
578578
{
579-
NativeMethods.HttpSetCompletionStatus(_pInProcessHandler, requestNotificationStatus);
580-
NativeMethods.HttpPostCompletion(_pInProcessHandler, 0);
579+
NativeMethods.HttpSetCompletionStatus(_requestNativeHandle, requestNotificationStatus);
580+
NativeMethods.HttpPostCompletion(_requestNativeHandle, 0);
581581
}
582582

583583
internal void OnAsyncCompletion(int hr, int bytes)
@@ -628,7 +628,7 @@ private void ThrowResponseAlreadyStartedException(string name)
628628

629629
private WindowsPrincipal GetWindowsPrincipal()
630630
{
631-
NativeMethods.HttpGetAuthenticationInformation(_pInProcessHandler, out var authenticationType, out var token);
631+
NativeMethods.HttpGetAuthenticationInformation(_requestNativeHandle, out var authenticationType, out var token);
632632

633633
if (token != IntPtr.Zero && authenticationType != null)
634634
{
@@ -664,6 +664,17 @@ private async Task HandleRequest()
664664
// Post completion after completing the request to resume the state machine
665665
PostCompletion(ConvertRequestCompletionResults(successfulRequest));
666666

667+
// After disposing a safe handle, Dispose() will not block waiting for the pinvokes to finish.
668+
// Instead Safehandle will call ReleaseHandle on the pinvoke thread when the pinvokes complete
669+
// and the reference count goes to zero.
670+
671+
// What this means is we need to wait until ReleaseHandle is called to finish disposal.
672+
// This is to make sure it is safe to return back to native.
673+
// The handle implements IValueTaskSource
674+
_requestNativeHandle.Dispose();
675+
676+
await new ValueTask<object>(_requestNativeHandle, _requestNativeHandle.Version);
677+
667678
// Dispose the context
668679
Dispose();
669680
}

src/Servers/IIS/IIS/src/Core/IISHttpContextOfT.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ internal class IISHttpContextOfT<TContext> : IISHttpContext
1818
{
1919
private readonly IHttpApplication<TContext> _application;
2020

21-
public IISHttpContextOfT(MemoryPool<byte> memoryPool, IHttpApplication<TContext> application, IntPtr pInProcessHandler, IISServerOptions options, IISHttpServer server, ILogger logger, bool useLatin1)
21+
public IISHttpContextOfT(MemoryPool<byte> memoryPool, IHttpApplication<TContext> application, NativeSafeHandle pInProcessHandler, IISServerOptions options, IISHttpServer server, ILogger logger, bool useLatin1)
2222
: base(memoryPool, pInProcessHandler, options, server, logger, useLatin1)
2323
{
2424
_application = application;
@@ -64,7 +64,7 @@ public override async Task<bool> ProcessRequestAsync()
6464
// Dispose
6565
}
6666

67-
if (!success && HasResponseStarted && NativeMethods.HttpSupportTrailer(_pInProcessHandler))
67+
if (!success && HasResponseStarted && NativeMethods.HttpSupportTrailer(_requestNativeHandle))
6868
{
6969
// HTTP/2 INTERNAL_ERROR = 0x2 https://tools.ietf.org/html/rfc7540#section-7
7070
// Otherwise the default is Cancel = 0x8.

src/Servers/IIS/IIS/src/Core/IISHttpServer.cs

Lines changed: 48 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -40,11 +40,12 @@ internal class IISHttpServer : IServer
4040
private readonly TaskCompletionSource<object> _shutdownSignal = new TaskCompletionSource<object>(TaskCreationOptions.RunContinuationsAsynchronously);
4141
private bool? _websocketAvailable;
4242
private CancellationTokenRegistration _cancellationTokenRegistration;
43+
private bool _disposed;
4344

4445
public IFeatureCollection Features { get; } = new FeatureCollection();
4546

4647
// TODO: Remove pInProcessHandler argument
47-
public bool IsWebSocketAvailable(IntPtr pInProcessHandler)
48+
public bool IsWebSocketAvailable(NativeSafeHandle pInProcessHandler)
4849
{
4950
// Check if the Http upgrade feature is available in IIS.
5051
// To check this, we can look at the server variable WEBSOCKET_VERSION
@@ -114,6 +115,12 @@ public Task StopAsync(CancellationToken cancellationToken)
114115

115116
public void Dispose()
116117
{
118+
if (_disposed)
119+
{
120+
return;
121+
}
122+
_disposed = true;
123+
117124
// Block any more calls into managed from native as we are unloading.
118125
_nativeApplication.StopCallsIntoManaged();
119126
_shutdownSignal.TrySetResult(null);
@@ -135,7 +142,14 @@ private static NativeMethods.REQUEST_NOTIFICATION_STATUS HandleRequest(IntPtr pI
135142
// Unwrap the server so we can create an http context and process the request
136143
server = (IISHttpServer)GCHandle.FromIntPtr(pvRequestContext).Target;
137144

138-
var context = server._iisContextFactory.CreateHttpContext(pInProcessHandler);
145+
// server can be null if ungraceful shutdown.
146+
if (server == null)
147+
{
148+
return NativeMethods.REQUEST_NOTIFICATION_STATUS.RQ_NOTIFICATION_FINISH_REQUEST;
149+
}
150+
151+
var safehandle = new NativeSafeHandle(pInProcessHandler);
152+
var context = server._iisContextFactory.CreateHttpContext(safehandle);
139153

140154
ThreadPool.UnsafeQueueUserWorkItem(context, preferLocal: false);
141155

@@ -155,6 +169,14 @@ private static bool HandleShutdown(IntPtr pvRequestContext)
155169
try
156170
{
157171
server = (IISHttpServer)GCHandle.FromIntPtr(pvRequestContext).Target;
172+
173+
// server can be null if ungraceful shutdown.
174+
if (server == null)
175+
{
176+
// return value isn't checked.
177+
return true;
178+
}
179+
158180
server._applicationLifetime.StopApplication();
159181
}
160182
catch (Exception ex)
@@ -170,7 +192,14 @@ private static void OnDisconnect(IntPtr pvManagedHttpContext)
170192
try
171193
{
172194
context = (IISHttpContext)GCHandle.FromIntPtr(pvManagedHttpContext).Target;
173-
context?.AbortIO(clientDisconnect: true);
195+
196+
// Context can be null if ungraceful shutdown.
197+
if (context == null)
198+
{
199+
return;
200+
}
201+
202+
context.AbortIO(clientDisconnect: true);
174203
}
175204
catch (Exception ex)
176205
{
@@ -184,7 +213,14 @@ private static NativeMethods.REQUEST_NOTIFICATION_STATUS OnAsyncCompletion(IntPt
184213
try
185214
{
186215
context = (IISHttpContext)GCHandle.FromIntPtr(pvManagedHttpContext).Target;
187-
context?.OnAsyncCompletion(hr, bytes);
216+
217+
// Context can be null if ungraceful shutdown.
218+
if (context == null)
219+
{
220+
return NativeMethods.REQUEST_NOTIFICATION_STATUS.RQ_NOTIFICATION_FINISH_REQUEST;
221+
}
222+
223+
context.OnAsyncCompletion(hr, bytes);
188224
return NativeMethods.REQUEST_NOTIFICATION_STATUS.RQ_NOTIFICATION_PENDING;
189225
}
190226
catch (Exception ex)
@@ -202,6 +238,12 @@ private static void OnRequestsDrained(IntPtr serverContext)
202238
{
203239
server = (IISHttpServer)GCHandle.FromIntPtr(serverContext).Target;
204240

241+
// server can be null if ungraceful shutdown.
242+
if (server == null)
243+
{
244+
return;
245+
}
246+
205247
server._nativeApplication.StopCallsIntoManaged();
206248
server._shutdownSignal.TrySetResult(null);
207249
server._cancellationTokenRegistration.Dispose();
@@ -233,7 +275,7 @@ public IISContextFactory(MemoryPool<byte> memoryPool, IHttpApplication<T> applic
233275
AppContext.TryGetSwitch(Latin1Suppport, out _useLatin1);
234276
}
235277

236-
public IISHttpContext CreateHttpContext(IntPtr pInProcessHandler)
278+
public IISHttpContext CreateHttpContext(NativeSafeHandle pInProcessHandler)
237279
{
238280
return new IISHttpContextOfT<T>(_memoryPool, _application, pInProcessHandler, _options, _server, _logger, _useLatin1);
239281
}
@@ -243,6 +285,6 @@ public IISHttpContext CreateHttpContext(IntPtr pInProcessHandler)
243285
// Over engineering to avoid allocations...
244286
internal interface IISContextFactory
245287
{
246-
IISHttpContext CreateHttpContext(IntPtr pInProcessHandler);
288+
IISHttpContext CreateHttpContext(NativeSafeHandle pInProcessHandler);
247289
}
248290
}

src/Servers/IIS/IIS/src/Core/IISNativeApplication.cs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,9 @@ namespace Microsoft.AspNetCore.Server.IIS.Core
88
{
99
internal class IISNativeApplication
1010
{
11-
private readonly IntPtr _nativeApplication;
11+
private readonly NativeSafeHandle _nativeApplication;
1212

13-
public IISNativeApplication(IntPtr nativeApplication)
13+
public IISNativeApplication(NativeSafeHandle nativeApplication)
1414
{
1515
_nativeApplication = nativeApplication;
1616
}
@@ -48,6 +48,9 @@ public void RegisterCallbacks(
4848
public void Dispose()
4949
{
5050
GC.SuppressFinalize(this);
51+
52+
// Don't need to await here because pinvokes should never been called after disposing the safe handle.
53+
_nativeApplication.Dispose();
5154
}
5255

5356
~IISNativeApplication()

src/Servers/IIS/IIS/src/Core/IO/AsyncIOEngine.Flush.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,15 +11,15 @@ internal class AsyncFlushOperation : AsyncIOOperation
1111
{
1212
private readonly AsyncIOEngine _engine;
1313

14-
private IntPtr _requestHandler;
14+
private NativeSafeHandle _requestHandler;
1515
private bool _moreData;
1616

1717
public AsyncFlushOperation(AsyncIOEngine engine)
1818
{
1919
_engine = engine;
2020
}
2121

22-
public void Initialize(IntPtr requestHandler, bool moreData)
22+
public void Initialize(NativeSafeHandle requestHandler, bool moreData)
2323
{
2424
_requestHandler = requestHandler;
2525
_moreData = moreData;

src/Servers/IIS/IIS/src/Core/IO/AsyncIOEngine.Read.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ internal class AsyncReadOperation : AsyncIOOperation
1414

1515
private MemoryHandle _inputHandle;
1616

17-
private IntPtr _requestHandler;
17+
private NativeSafeHandle _requestHandler;
1818

1919
private Memory<byte> _memory;
2020

@@ -23,7 +23,7 @@ public AsyncReadOperation(AsyncIOEngine engine)
2323
_engine = engine;
2424
}
2525

26-
public void Initialize(IntPtr requestHandler, Memory<byte> memory)
26+
public void Initialize(NativeSafeHandle requestHandler, Memory<byte> memory)
2727
{
2828
_requestHandler = requestHandler;
2929
_memory = memory;

src/Servers/IIS/IIS/src/Core/IO/AsyncIOEngine.Write.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ public AsyncWriteOperation(AsyncIOEngine engine)
1717
_engine = engine;
1818
}
1919

20-
protected override unsafe int WriteChunks(IntPtr requestHandler, int chunkCount, HttpApiTypes.HTTP_DATA_CHUNK* dataChunks,
20+
protected override unsafe int WriteChunks(NativeSafeHandle requestHandler, int chunkCount, HttpApiTypes.HTTP_DATA_CHUNK* dataChunks,
2121
out bool completionExpected)
2222
{
2323
return NativeMethods.HttpWriteResponseBytes(requestHandler, dataChunks, chunkCount, out completionExpected);

0 commit comments

Comments
 (0)