Skip to content

Commit 19c492f

Browse files
davidfowlJamesNK
andauthored
Clean up some http.sys code (#28308)
* Clean up some http.sys code - Remove SuppressContext and ToIAsyncResult helpers. Use TaskToApm instead. - Dispatch off the current thread to start the accept loop - Use logging pattern in ResponseBody - Replace Contract.Assert with Debug.Assert - Use new ValidateBufferArguments to verify WriteAsync overloads * Update src/Servers/HttpSys/src/RequestProcessing/ResponseBody.cs Co-authored-by: James Newton-King <[email protected]> * Update src/Servers/HttpSys/src/RequestProcessing/ResponseBody.cs Co-authored-by: James Newton-King <[email protected]> * Fix one of the async voids Co-authored-by: James Newton-King <[email protected]>
1 parent 63e29f1 commit 19c492f

File tree

6 files changed

+98
-71
lines changed

6 files changed

+98
-71
lines changed

src/Servers/HttpSys/src/Helpers.cs

Lines changed: 0 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,7 @@
33

44
using System;
55
using System.Globalization;
6-
using System.Runtime.CompilerServices;
76
using System.Text;
8-
using System.Threading;
9-
using System.Threading.Tasks;
107

118
namespace Microsoft.AspNetCore.Server.HttpSys
129
{
@@ -15,42 +12,6 @@ internal static class Helpers
1512
internal static readonly byte[] ChunkTerminator = new byte[] { (byte)'0', (byte)'\r', (byte)'\n', (byte)'\r', (byte)'\n' };
1613
internal static readonly byte[] CRLF = new byte[] { (byte)'\r', (byte)'\n' };
1714

18-
internal static ConfiguredTaskAwaitable SupressContext(this Task task)
19-
{
20-
return task.ConfigureAwait(continueOnCapturedContext: false);
21-
}
22-
23-
internal static ConfiguredTaskAwaitable<T> SupressContext<T>(this Task<T> task)
24-
{
25-
return task.ConfigureAwait(continueOnCapturedContext: false);
26-
}
27-
28-
internal static IAsyncResult ToIAsyncResult(this Task task, AsyncCallback callback, object state)
29-
{
30-
var tcs = new TaskCompletionSource<int>(state);
31-
task.ContinueWith(t =>
32-
{
33-
if (t.IsFaulted)
34-
{
35-
tcs.TrySetException(t.Exception.InnerExceptions);
36-
}
37-
else if (t.IsCanceled)
38-
{
39-
tcs.TrySetCanceled();
40-
}
41-
else
42-
{
43-
tcs.TrySetResult(0);
44-
}
45-
46-
if (callback != null)
47-
{
48-
callback(tcs.Task);
49-
}
50-
}, CancellationToken.None, TaskContinuationOptions.None, TaskScheduler.Default);
51-
return tcs.Task;
52-
}
53-
5415
internal static ArraySegment<byte> GetChunkHeader(long size)
5516
{
5617
if (size < int.MaxValue)

src/Servers/HttpSys/src/LoggerEventIds.cs

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -50,9 +50,5 @@ internal static class LoggerEventIds
5050
public static EventId DisconnectTriggered = new EventId(44, "DisconnectTriggered");
5151
public static EventId ListenerStopError = new EventId(45, "ListenerStopError");
5252
public static EventId ListenerDisposing = new EventId(46, "ListenerDisposing");
53-
54-
55-
56-
5753
}
5854
}

src/Servers/HttpSys/src/MessagePump.cs

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33

44
using System;
55
using System.Collections.Generic;
6-
using System.Diagnostics.Contracts;
6+
using System.Diagnostics;
77
using System.Linq;
88
using System.Threading;
99
using System.Threading.Tasks;
@@ -119,9 +119,9 @@ public Task StartAsync<TContext>(IHttpApplication<TContext> application, Cancell
119119
// else // Attaching to an existing queue, don't add a default.
120120

121121
// Can't call Start twice
122-
Contract.Assert(Application == null);
122+
Debug.Assert(Application == null);
123123

124-
Contract.Assert(application != null);
124+
Debug.Assert(application != null);
125125

126126
Application = new ApplicationWrapper<TContext>(application);
127127

@@ -134,7 +134,8 @@ public Task StartAsync<TContext>(IHttpApplication<TContext> application, Cancell
134134
_serverAddresses.Addresses.Add(prefix.FullPrefix);
135135
}
136136

137-
ActivateRequestProcessingLimits();
137+
// Dispatch to get off the SynchronizationContext and use UnsafeQueueUserWorkItem to avoid capturing the ExecutionContext
138+
ThreadPool.UnsafeQueueUserWorkItem(state => state.ActivateRequestProcessingLimits(), this, preferLocal: false);
138139

139140
return Task.CompletedTask;
140141
}
@@ -143,7 +144,8 @@ private void ActivateRequestProcessingLimits()
143144
{
144145
for (int i = _acceptorCounts; i < _maxAccepts; i++)
145146
{
146-
ProcessRequestsWorker();
147+
// Ignore the result
148+
_ = ProcessRequestsWorker();
147149
}
148150
}
149151

@@ -174,7 +176,7 @@ internal void SetShutdownSignal()
174176
// When we start listening for the next request on one thread, we may need to be sure that the
175177
// completion continues on another thread as to not block the current request processing.
176178
// The awaits will manage stack depth for us.
177-
private async void ProcessRequestsWorker()
179+
private async Task ProcessRequestsWorker()
178180
{
179181
int workerIndex = Interlocked.Increment(ref _acceptorCounts);
180182
while (!Stopping && workerIndex <= _maxAccepts)
@@ -183,13 +185,13 @@ private async void ProcessRequestsWorker()
183185
RequestContext requestContext;
184186
try
185187
{
186-
requestContext = await Listener.AcceptAsync().SupressContext();
188+
requestContext = await Listener.AcceptAsync();
187189
// Assign the message pump to this request context
188190
requestContext.MessagePump = this;
189191
}
190192
catch (Exception exception)
191193
{
192-
Contract.Assert(Stopping);
194+
Debug.Assert(Stopping);
193195
if (Stopping)
194196
{
195197
_logger.LogDebug(LoggerEventIds.AcceptErrorStopping, exception, "Failed to accept a request, the server is stopping.");

src/Servers/HttpSys/src/RequestProcessing/Request.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -370,7 +370,7 @@ public X509Certificate2 ClientCertificate
370370
var certLoader = new ClientCertLoader(RequestContext, cancellationToken);
371371
try
372372
{
373-
await certLoader.LoadClientCertificateAsync().SupressContext();
373+
await certLoader.LoadClientCertificateAsync();
374374
// Populate the environment.
375375
if (certLoader.ClientCert != null)
376376
{

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -262,7 +262,7 @@ public async void Execute()
262262
context = application.CreateContext(featureContext.Features);
263263
try
264264
{
265-
await application.ProcessRequestAsync(context).SupressContext();
265+
await application.ProcessRequestAsync(context);
266266
await featureContext.CompleteAsync();
267267
}
268268
finally

src/Servers/HttpSys/src/RequestProcessing/ResponseBody.cs

Lines changed: 86 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,7 @@
44
using System;
55
using System.Collections.Generic;
66
using System.Diagnostics;
7-
using System.Diagnostics.CodeAnalysis;
87
using System.IO;
9-
using System.Net;
108
using System.Runtime.InteropServices;
119
using System.Threading;
1210
using System.Threading.Tasks;
@@ -135,7 +133,7 @@ public void MarkDelegated()
135133
if (!RequestContext.DisconnectToken.IsCancellationRequested)
136134
{
137135
// This is logged rather than thrown because it is too late for an exception to be visible in user code.
138-
Logger.LogError(LoggerEventIds.FewerBytesThanExpected, "ResponseStream::Dispose; Fewer bytes were written than were specified in the Content-Length.");
136+
Log.FewerBytesThanExpected(Logger);
139137
}
140138
_requestContext.Abort();
141139
return;
@@ -180,14 +178,14 @@ public void MarkDelegated()
180178
if (ThrowWriteExceptions)
181179
{
182180
var exception = new IOException(string.Empty, new HttpSysException((int)statusCode));
183-
Logger.LogError(LoggerEventIds.WriteError, exception, "Flush");
181+
Log.WriteError(Logger, exception);
184182
Abort();
185183
throw exception;
186184
}
187185
else
188186
{
189187
// Abort the request but do not close the stream, let future writes complete silently
190-
Logger.LogDebug(LoggerEventIds.WriteErrorIgnored, $"Flush; Ignored write exception: {statusCode}");
188+
Log.WriteErrorIgnored(Logger, statusCode);
191189
Abort(dispose: false);
192190
}
193191
}
@@ -350,7 +348,7 @@ private unsafe Task FlushInternalAsync(ArraySegment<byte> data, CancellationToke
350348
}
351349
catch (Exception e)
352350
{
353-
Logger.LogError(LoggerEventIds.ErrorWhenFlushAsync, e, "FlushAsync");
351+
Log.ErrorWhenFlushAsync(Logger, e);
354352
asyncResult.Dispose();
355353
Abort();
356354
throw;
@@ -360,21 +358,21 @@ private unsafe Task FlushInternalAsync(ArraySegment<byte> data, CancellationToke
360358
{
361359
if (cancellationToken.IsCancellationRequested)
362360
{
363-
Logger.LogDebug(LoggerEventIds.WriteFlushCancelled,$"FlushAsync; Write cancelled with error code: {statusCode}");
361+
Log.WriteFlushCancelled(Logger, statusCode);
364362
asyncResult.Cancel(ThrowWriteExceptions);
365363
}
366364
else if (ThrowWriteExceptions)
367365
{
368366
asyncResult.Dispose();
369367
Exception exception = new IOException(string.Empty, new HttpSysException((int)statusCode));
370-
Logger.LogError(LoggerEventIds.ErrorWhenFlushAsync, exception, "FlushAsync");
368+
Log.ErrorWhenFlushAsync(Logger, exception);
371369
Abort();
372370
throw exception;
373371
}
374372
else
375373
{
376374
// Abort the request but do not close the stream, let future writes complete silently
377-
Logger.LogDebug(LoggerEventIds.WriteErrorIgnored,$"FlushAsync; Ignored write exception: {statusCode}");
375+
Log.WriteErrorIgnored(Logger, statusCode);
378376
asyncResult.FailSilently();
379377
}
380378
}
@@ -488,6 +486,8 @@ private HttpApiTypes.HTTP_FLAGS ComputeLeftToWrite(long writeCount, bool endOfRe
488486

489487
public override void Write(byte[] buffer, int offset, int count)
490488
{
489+
ValidateBufferArguments(buffer, offset, count);
490+
491491
if (!RequestContext.AllowSynchronousIO)
492492
{
493493
throw new InvalidOperationException("Synchronous IO APIs are disabled, see AllowSynchronousIO.");
@@ -522,7 +522,7 @@ private void CheckWriteCount(long? count)
522522

523523
public override IAsyncResult BeginWrite(byte[] buffer, int offset, int count, AsyncCallback callback, object state)
524524
{
525-
return WriteAsync(buffer, offset, count).ToIAsyncResult(callback, state);
525+
return TaskToApm.Begin(WriteAsync(buffer, offset, count), callback, state);
526526
}
527527

528528
public override void EndWrite(IAsyncResult asyncResult)
@@ -531,11 +531,14 @@ public override void EndWrite(IAsyncResult asyncResult)
531531
{
532532
throw new ArgumentNullException(nameof(asyncResult));
533533
}
534-
((Task)asyncResult).GetAwaiter().GetResult();
534+
535+
TaskToApm.End(asyncResult);
535536
}
536537

537538
public override Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
538539
{
540+
ValidateBufferArguments(buffer, offset, count);
541+
539542
// Validates for null and bounds. Allows count == 0.
540543
// TODO: Verbose log parameters
541544
var data = new ArraySegment<byte>(buffer, offset, count);
@@ -644,7 +647,7 @@ internal unsafe Task SendFileAsyncCore(string fileName, long offset, long? count
644647
}
645648
catch (Exception e)
646649
{
647-
Logger.LogError(LoggerEventIds.FileSendAsyncError, e, "SendFileAsync");
650+
Log.FileSendAsyncError(Logger, e);
648651
asyncResult.Dispose();
649652
Abort();
650653
throw;
@@ -654,21 +657,21 @@ internal unsafe Task SendFileAsyncCore(string fileName, long offset, long? count
654657
{
655658
if (cancellationToken.IsCancellationRequested)
656659
{
657-
Logger.LogDebug(LoggerEventIds.FileSendAsyncCancelled,$"SendFileAsync; Write cancelled with error code: {statusCode}");
660+
Log.FileSendAsyncCancelled(Logger, statusCode);
658661
asyncResult.Cancel(ThrowWriteExceptions);
659662
}
660663
else if (ThrowWriteExceptions)
661664
{
662665
asyncResult.Dispose();
663666
var exception = new IOException(string.Empty, new HttpSysException((int)statusCode));
664-
Logger.LogError(LoggerEventIds.FileSendAsyncError, exception, "SendFileAsync");
667+
Log.FileSendAsyncError(Logger, exception);
665668
Abort();
666669
throw exception;
667670
}
668671
else
669672
{
670-
// Abort the request but do not close the stream, let future writes complete silently
671-
Logger.LogDebug(LoggerEventIds.FileSendAsyncErrorIgnored,$"SendFileAsync; Ignored write exception: {statusCode}");
673+
// Abort the request but do not close the stream, let future writes complete
674+
Log.FileSendAsyncErrorIgnored(Logger, statusCode);
672675
asyncResult.FailSilently();
673676
}
674677
}
@@ -715,8 +718,6 @@ internal void SwitchToOpaqueMode()
715718

716719
// The final Content-Length async write can only be Canceled by CancelIoEx.
717720
// Sync can only be Canceled by CancelSynchronousIo, but we don't attempt this right now.
718-
[SuppressMessage("Microsoft.Usage", "CA1806:DoNotIgnoreMethodResults", Justification =
719-
"It is safe to ignore the return value on a cancel operation because the connection is being closed")]
720721
internal unsafe void CancelLastWrite()
721722
{
722723
ResponseStreamAsyncResult asyncState = _lastWrite;
@@ -733,5 +734,72 @@ private void CheckDisposed()
733734
throw new ObjectDisposedException(GetType().FullName);
734735
}
735736
}
737+
738+
private static class Log
739+
{
740+
private static readonly Action<ILogger, Exception> _fewerBytesThanExpected =
741+
LoggerMessage.Define(LogLevel.Error, LoggerEventIds.FewerBytesThanExpected, "ResponseStream::Dispose; Fewer bytes were written than were specified in the Content-Length.");
742+
743+
private static readonly Action<ILogger, Exception> _writeError =
744+
LoggerMessage.Define(LogLevel.Error, LoggerEventIds.WriteError, "Flush");
745+
746+
private static readonly Action<ILogger, uint, Exception> _writeErrorIgnored =
747+
LoggerMessage.Define<uint>(LogLevel.Debug, LoggerEventIds.WriteErrorIgnored, "Flush; Ignored write exception: {StatusCode}");
748+
749+
private static readonly Action<ILogger, Exception> _errorWhenFlushAsync =
750+
LoggerMessage.Define(LogLevel.Debug, LoggerEventIds.ErrorWhenFlushAsync, "FlushAsync");
751+
752+
private static readonly Action<ILogger, uint, Exception> _writeFlushCancelled =
753+
LoggerMessage.Define<uint>(LogLevel.Debug, LoggerEventIds.WriteFlushCancelled, "FlushAsync; Write cancelled with error code: {StatusCode}");
754+
755+
private static readonly Action<ILogger, Exception> _fileSendAsyncError =
756+
LoggerMessage.Define(LogLevel.Error, LoggerEventIds.FileSendAsyncError, "SendFileAsync");
757+
758+
private static readonly Action<ILogger, uint, Exception> _fileSendAsyncCancelled =
759+
LoggerMessage.Define<uint>(LogLevel.Debug, LoggerEventIds.FileSendAsyncCancelled, "SendFileAsync; Write cancelled with error code: {StatusCode}");
760+
761+
private static readonly Action<ILogger, uint, Exception> _fileSendAsyncErrorIgnored =
762+
LoggerMessage.Define<uint>(LogLevel.Debug, LoggerEventIds.FileSendAsyncErrorIgnored, "SendFileAsync; Ignored write exception: {StatusCode}");
763+
764+
public static void FewerBytesThanExpected(ILogger logger)
765+
{
766+
_fewerBytesThanExpected(logger, null);
767+
}
768+
769+
public static void WriteError(ILogger logger, IOException exception)
770+
{
771+
_writeError(logger, exception);
772+
}
773+
774+
public static void WriteErrorIgnored(ILogger logger, uint statusCode)
775+
{
776+
_writeErrorIgnored(logger, statusCode, null);
777+
}
778+
779+
public static void ErrorWhenFlushAsync(ILogger logger, Exception exception)
780+
{
781+
_errorWhenFlushAsync(logger, exception);
782+
}
783+
784+
public static void WriteFlushCancelled(ILogger logger, uint statusCode)
785+
{
786+
_writeFlushCancelled(logger, statusCode, null);
787+
}
788+
789+
public static void FileSendAsyncError(ILogger logger, Exception exception)
790+
{
791+
_fileSendAsyncError(logger, exception);
792+
}
793+
794+
public static void FileSendAsyncCancelled(ILogger logger, uint statusCode)
795+
{
796+
_fileSendAsyncCancelled(logger, statusCode, null);
797+
}
798+
799+
public static void FileSendAsyncErrorIgnored(ILogger logger, uint statusCode)
800+
{
801+
_fileSendAsyncErrorIgnored(logger, statusCode, null);
802+
}
803+
}
736804
}
737805
}

0 commit comments

Comments
 (0)