Reuse the stack for OnCompleted and OnStarting calls (#6833)
- Reviewing profiles for MVC and other things that use RegisterForDispose, this shows up as a significant amount of allocations.
This commit is contained in:
parent
549f8e1773
commit
447f4bc298
|
|
@ -1,4 +1,4 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<root>
|
<root>
|
||||||
<!--
|
<!--
|
||||||
Microsoft ResX Schema
|
Microsoft ResX Schema
|
||||||
|
|
@ -590,4 +590,4 @@ For more information on configuring HTTPS see https://go.microsoft.com/fwlink/?l
|
||||||
<data name="Http2ErrorStreamAborted" xml:space="preserve">
|
<data name="Http2ErrorStreamAborted" xml:space="preserve">
|
||||||
<value>A frame of type {frameType} was received after stream {streamId} was reset or aborted.</value>
|
<value>A frame of type {frameType} was received after stream {streamId} was reset or aborted.</value>
|
||||||
</data>
|
</data>
|
||||||
</root>
|
</root>
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,6 @@ using Microsoft.AspNetCore.Connections;
|
||||||
using Microsoft.AspNetCore.Hosting.Server;
|
using Microsoft.AspNetCore.Hosting.Server;
|
||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Http;
|
||||||
using Microsoft.AspNetCore.Http.Features;
|
using Microsoft.AspNetCore.Http.Features;
|
||||||
using Microsoft.AspNetCore.Http.Internal;
|
|
||||||
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure;
|
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using Microsoft.Extensions.Primitives;
|
using Microsoft.Extensions.Primitives;
|
||||||
|
|
@ -31,9 +30,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
||||||
private static readonly byte[] _bytesTransferEncodingChunked = Encoding.ASCII.GetBytes("\r\nTransfer-Encoding: chunked");
|
private static readonly byte[] _bytesTransferEncodingChunked = Encoding.ASCII.GetBytes("\r\nTransfer-Encoding: chunked");
|
||||||
private static readonly byte[] _bytesServer = Encoding.ASCII.GetBytes("\r\nServer: " + Constants.ServerName);
|
private static readonly byte[] _bytesServer = Encoding.ASCII.GetBytes("\r\nServer: " + Constants.ServerName);
|
||||||
|
|
||||||
private readonly object _onStartingSync = new Object();
|
|
||||||
private readonly object _onCompletedSync = new Object();
|
|
||||||
|
|
||||||
protected Streams _streams;
|
protected Streams _streams;
|
||||||
|
|
||||||
private Stack<KeyValuePair<Func<object, Task>, object>> _onStarting;
|
private Stack<KeyValuePair<Func<object, Task>, object>> _onStarting;
|
||||||
|
|
@ -313,8 +309,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
||||||
|
|
||||||
public void Reset()
|
public void Reset()
|
||||||
{
|
{
|
||||||
_onStarting = null;
|
_onStarting?.Clear();
|
||||||
_onCompleted = null;
|
_onCompleted?.Clear();
|
||||||
|
|
||||||
_requestProcessingStatus = RequestProcessingStatus.RequestPending;
|
_requestProcessingStatus = RequestProcessingStatus.RequestPending;
|
||||||
_autoChunk = false;
|
_autoChunk = false;
|
||||||
|
|
@ -588,7 +584,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
||||||
// already failed. If an OnStarting callback throws we can go through
|
// already failed. If an OnStarting callback throws we can go through
|
||||||
// our normal error handling in ProduceEnd.
|
// our normal error handling in ProduceEnd.
|
||||||
// https://github.com/aspnet/KestrelHttpServer/issues/43
|
// https://github.com/aspnet/KestrelHttpServer/issues/43
|
||||||
if (!HasResponseStarted && _applicationException == null && _onStarting != null)
|
if (!HasResponseStarted && _applicationException == null && _onStarting?.Count > 0)
|
||||||
{
|
{
|
||||||
await FireOnStarting();
|
await FireOnStarting();
|
||||||
}
|
}
|
||||||
|
|
@ -624,7 +620,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_onCompleted != null)
|
if (_onCompleted?.Count > 0)
|
||||||
{
|
{
|
||||||
await FireOnCompleted();
|
await FireOnCompleted();
|
||||||
}
|
}
|
||||||
|
|
@ -649,43 +645,32 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
||||||
|
|
||||||
public void OnStarting(Func<object, Task> callback, object state)
|
public void OnStarting(Func<object, Task> callback, object state)
|
||||||
{
|
{
|
||||||
lock (_onStartingSync)
|
if (HasResponseStarted)
|
||||||
{
|
{
|
||||||
if (HasResponseStarted)
|
ThrowResponseAlreadyStartedException(nameof(OnStarting));
|
||||||
{
|
|
||||||
ThrowResponseAlreadyStartedException(nameof(OnStarting));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_onStarting == null)
|
|
||||||
{
|
|
||||||
_onStarting = new Stack<KeyValuePair<Func<object, Task>, object>>();
|
|
||||||
}
|
|
||||||
_onStarting.Push(new KeyValuePair<Func<object, Task>, object>(callback, state));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (_onStarting == null)
|
||||||
|
{
|
||||||
|
_onStarting = new Stack<KeyValuePair<Func<object, Task>, object>>();
|
||||||
|
}
|
||||||
|
_onStarting.Push(new KeyValuePair<Func<object, Task>, object>(callback, state));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void OnCompleted(Func<object, Task> callback, object state)
|
public void OnCompleted(Func<object, Task> callback, object state)
|
||||||
{
|
{
|
||||||
lock (_onCompletedSync)
|
if (_onCompleted == null)
|
||||||
{
|
{
|
||||||
if (_onCompleted == null)
|
_onCompleted = new Stack<KeyValuePair<Func<object, Task>, object>>();
|
||||||
{
|
|
||||||
_onCompleted = new Stack<KeyValuePair<Func<object, Task>, object>>();
|
|
||||||
}
|
|
||||||
_onCompleted.Push(new KeyValuePair<Func<object, Task>, object>(callback, state));
|
|
||||||
}
|
}
|
||||||
|
_onCompleted.Push(new KeyValuePair<Func<object, Task>, object>(callback, state));
|
||||||
}
|
}
|
||||||
|
|
||||||
protected Task FireOnStarting()
|
protected Task FireOnStarting()
|
||||||
{
|
{
|
||||||
Stack<KeyValuePair<Func<object, Task>, object>> onStarting;
|
var onStarting = _onStarting;
|
||||||
lock (_onStartingSync)
|
|
||||||
{
|
|
||||||
onStarting = _onStarting;
|
|
||||||
_onStarting = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (onStarting == null)
|
if (onStarting == null || onStarting.Count == 0)
|
||||||
{
|
{
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|
@ -700,10 +685,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var count = onStarting.Count;
|
while (onStarting.TryPop(out var entry))
|
||||||
for (var i = 0; i < count; i++)
|
|
||||||
{
|
{
|
||||||
var entry = onStarting.Pop();
|
|
||||||
var task = entry.Key.Invoke(entry.Value);
|
var task = entry.Key.Invoke(entry.Value);
|
||||||
if (!ReferenceEquals(task, Task.CompletedTask))
|
if (!ReferenceEquals(task, Task.CompletedTask))
|
||||||
{
|
{
|
||||||
|
|
@ -725,10 +708,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
||||||
{
|
{
|
||||||
await currentTask;
|
await currentTask;
|
||||||
|
|
||||||
var count = onStarting.Count;
|
while (onStarting.TryPop(out var entry))
|
||||||
for (var i = 0; i < count; i++)
|
|
||||||
{
|
{
|
||||||
var entry = onStarting.Pop();
|
|
||||||
await entry.Key.Invoke(entry.Value);
|
await entry.Key.Invoke(entry.Value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -740,24 +721,50 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
||||||
|
|
||||||
protected Task FireOnCompleted()
|
protected Task FireOnCompleted()
|
||||||
{
|
{
|
||||||
Stack<KeyValuePair<Func<object, Task>, object>> onCompleted;
|
var onCompleted = _onCompleted;
|
||||||
lock (_onCompletedSync)
|
|
||||||
{
|
|
||||||
onCompleted = _onCompleted;
|
|
||||||
_onCompleted = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (onCompleted == null)
|
if (onCompleted == null || onCompleted.Count == 0)
|
||||||
{
|
{
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
return FireOnCompletedAwaited(onCompleted);
|
return FireOnCompletedMayAwait(onCompleted);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task FireOnCompletedAwaited(Stack<KeyValuePair<Func<object, Task>, object>> onCompleted)
|
private Task FireOnCompletedMayAwait(Stack<KeyValuePair<Func<object, Task>, object>> onCompleted)
|
||||||
{
|
{
|
||||||
foreach (var entry in onCompleted)
|
|
||||||
|
while (onCompleted.TryPop(out var entry))
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var task = entry.Key.Invoke(entry.Value);
|
||||||
|
if (!ReferenceEquals(task, Task.CompletedTask))
|
||||||
|
{
|
||||||
|
return FireOnCompletedAwaited(task, onCompleted);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
ReportApplicationError(ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task FireOnCompletedAwaited(Task currentTask, Stack<KeyValuePair<Func<object, Task>, object>> onCompleted)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await currentTask;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Log.ApplicationError(ConnectionId, TraceIdentifier, ex);
|
||||||
|
}
|
||||||
|
|
||||||
|
while (onCompleted.TryPop(out var entry))
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
|
@ -768,6 +775,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
||||||
Log.ApplicationError(ConnectionId, TraceIdentifier, ex);
|
Log.ApplicationError(ConnectionId, TraceIdentifier, ex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task FlushAsync(CancellationToken cancellationToken = default(CancellationToken))
|
public Task FlushAsync(CancellationToken cancellationToken = default(CancellationToken))
|
||||||
|
|
|
||||||
|
|
@ -2001,6 +2001,99 @@ namespace Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests
|
||||||
Assert.Equal(2, TestApplicationErrorLogger.Messages.Where(message => message.LogLevel == LogLevel.Error).Count());
|
Assert.Equal(2, TestApplicationErrorLogger.Messages.Where(message => message.LogLevel == LogLevel.Error).Count());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task OnStartingThrowsInsideOnStartingCallbacksRuns()
|
||||||
|
{
|
||||||
|
var testContext = new TestServiceContext(LoggerFactory);
|
||||||
|
var tcs = new TaskCompletionSource<object>(TaskCreationOptions.RunContinuationsAsynchronously);
|
||||||
|
|
||||||
|
using (var server = new TestServer(async httpContext =>
|
||||||
|
{
|
||||||
|
var response = httpContext.Response;
|
||||||
|
response.OnStarting(state1 =>
|
||||||
|
{
|
||||||
|
response.OnStarting(state2 =>
|
||||||
|
{
|
||||||
|
tcs.TrySetResult(null);
|
||||||
|
return Task.CompletedTask;
|
||||||
|
},
|
||||||
|
null);
|
||||||
|
|
||||||
|
return Task.CompletedTask;
|
||||||
|
|
||||||
|
}, null);
|
||||||
|
|
||||||
|
response.Headers["Content-Length"] = new[] { "11" };
|
||||||
|
|
||||||
|
await response.Body.WriteAsync(Encoding.ASCII.GetBytes("Hello World"), 0, 11);
|
||||||
|
}, testContext))
|
||||||
|
{
|
||||||
|
using (var connection = server.CreateConnection())
|
||||||
|
{
|
||||||
|
await connection.Send(
|
||||||
|
"GET / HTTP/1.1",
|
||||||
|
"Host:",
|
||||||
|
"",
|
||||||
|
"");
|
||||||
|
await connection.Receive(
|
||||||
|
"HTTP/1.1 200 OK",
|
||||||
|
$"Date: {testContext.DateHeaderValue}",
|
||||||
|
"");
|
||||||
|
|
||||||
|
await tcs.Task.DefaultTimeout();
|
||||||
|
}
|
||||||
|
|
||||||
|
await server.StopAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task OnCompletedThrowsInsideOnCompletedCallbackRuns()
|
||||||
|
{
|
||||||
|
var testContext = new TestServiceContext(LoggerFactory);
|
||||||
|
var tcs = new TaskCompletionSource<object>(TaskCreationOptions.RunContinuationsAsynchronously);
|
||||||
|
|
||||||
|
using (var server = new TestServer(async httpContext =>
|
||||||
|
{
|
||||||
|
var response = httpContext.Response;
|
||||||
|
response.OnCompleted(state1 =>
|
||||||
|
{
|
||||||
|
response.OnCompleted(state2 =>
|
||||||
|
{
|
||||||
|
tcs.TrySetResult(null);
|
||||||
|
|
||||||
|
return Task.CompletedTask;
|
||||||
|
},
|
||||||
|
null);
|
||||||
|
|
||||||
|
return Task.CompletedTask;
|
||||||
|
|
||||||
|
}, null);
|
||||||
|
|
||||||
|
response.Headers["Content-Length"] = new[] { "11" };
|
||||||
|
|
||||||
|
await response.Body.WriteAsync(Encoding.ASCII.GetBytes("Hello World"), 0, 11);
|
||||||
|
}, testContext))
|
||||||
|
{
|
||||||
|
using (var connection = server.CreateConnection())
|
||||||
|
{
|
||||||
|
await connection.Send(
|
||||||
|
"GET / HTTP/1.1",
|
||||||
|
"Host:",
|
||||||
|
"",
|
||||||
|
"");
|
||||||
|
await connection.Receive(
|
||||||
|
"HTTP/1.1 200 OK",
|
||||||
|
$"Date: {testContext.DateHeaderValue}",
|
||||||
|
"");
|
||||||
|
|
||||||
|
await tcs.Task.DefaultTimeout();
|
||||||
|
}
|
||||||
|
|
||||||
|
await server.StopAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task ThrowingInOnCompletedIsLogged()
|
public async Task ThrowingInOnCompletedIsLogged()
|
||||||
{
|
{
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue