Skip to content

Start each request on fresh ExecutionContext #14146

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Apr 3, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
119 changes: 38 additions & 81 deletions src/Servers/Kestrel/Core/src/Internal/Http/HttpProtocol.cs
Original file line number Diff line number Diff line change
Expand Up @@ -547,6 +547,8 @@ public async Task ProcessRequestsAsync<TContext>(IHttpApplication<TContext> appl
{
try
{
// We run the request processing loop in a seperate async method so per connection
// exception handling doesn't complicate the generated asm for the loop.
await ProcessRequests(application);
}
catch (BadHttpRequestException ex)
Expand Down Expand Up @@ -624,6 +626,26 @@ private async Task ProcessRequests<TContext>(IHttpApplication<TContext> applicat

InitializeBodyControl(messageBody);

// We run user controlled request processing in a seperate async method
// so any changes made to ExecutionContext are undone when it returns and
// each request starts with a fresh ExecutionContext state.
await ProcessRequest(application);

// Even for non-keep-alive requests, try to consume the entire body to avoid RSTs.
if (!_connectionAborted && _requestRejectedException == null && !messageBody.IsEmpty)
{
await messageBody.ConsumeAsync();
}

if (HasStartedConsumingRequestBody)
{
await messageBody.StopAsync();
}
}
}

private async ValueTask ProcessRequest<TContext>(IHttpApplication<TContext> application)
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Task is a more performant return here than ValueTask and will allocate the same; i.e none in the sync completion and the async box in the async wait scenario (subject to dotnet/coreclr#26310 when ValueTask becomes better as it doesn't allocate in async)

{
var context = application.CreateContext(this);

try
Expand Down Expand Up @@ -709,18 +731,6 @@ private async Task ProcessRequests<TContext>(IHttpApplication<TContext> applicat
}

application.DisposeContext(context, _applicationException);

// Even for non-keep-alive requests, try to consume the entire body to avoid RSTs.
if (!_connectionAborted && _requestRejectedException == null && !messageBody.IsEmpty)
{
await messageBody.ConsumeAsync();
}

if (HasStartedConsumingRequestBody)
{
await messageBody.StopAsync();
}
}
}

public void OnStarting(Func<object, Task> callback, object state)
Expand Down Expand Up @@ -749,108 +759,55 @@ public void OnCompleted(Func<object, Task> callback, object state)
protected Task FireOnStarting()
{
var onStarting = _onStarting;

if (onStarting == null || onStarting.Count == 0)
{
return Task.CompletedTask;
}
else
{
return FireOnStartingMayAwait(onStarting);
}
}

private Task FireOnStartingMayAwait(Stack<KeyValuePair<Func<object, Task>, object>> onStarting)
{
try
{
while (onStarting.TryPop(out var entry))
if (onStarting?.Count > 0)
{
var task = entry.Key.Invoke(entry.Value);
if (!task.IsCompletedSuccessfully)
{
return FireOnStartingAwaited(task, onStarting);
}
}
}
catch (Exception ex)
{
ReportApplicationError(ex);
return ProcessEvents(this, onStarting);
}

return Task.CompletedTask;
}

private async Task FireOnStartingAwaited(Task currentTask, Stack<KeyValuePair<Func<object, Task>, object>> onStarting)
static async Task ProcessEvents(HttpProtocol protocol, Stack<KeyValuePair<Func<object, Task>, object>> events)
{
// Try/Catch is outside the loop as any error that occurs is before the request starts.
// So we want to report it as an ApplicationError to fail the request and not process more events.
try
{
await currentTask;

while (onStarting.TryPop(out var entry))
while (events.TryPop(out var entry))
{
await entry.Key.Invoke(entry.Value);
}
}
catch (Exception ex)
{
ReportApplicationError(ex);
protocol.ReportApplicationError(ex);
}
}
}

protected Task FireOnCompleted()
{
var onCompleted = _onCompleted;

if (onCompleted == null || onCompleted.Count == 0)
if (onCompleted?.Count > 0)
{
return Task.CompletedTask;
}

return FireOnCompletedMayAwait(onCompleted);
}

private Task FireOnCompletedMayAwait(Stack<KeyValuePair<Func<object, Task>, object>> onCompleted)
{
while (onCompleted.TryPop(out var entry))
{
try
{
var task = entry.Key.Invoke(entry.Value);
if (!task.IsCompletedSuccessfully)
{
return FireOnCompletedAwaited(task, onCompleted);
}
}
catch (Exception ex)
{
ReportApplicationError(ex);
}
return ProcessEvents(this, onCompleted);
}

return Task.CompletedTask;
}

private async Task FireOnCompletedAwaited(Task currentTask, Stack<KeyValuePair<Func<object, Task>, object>> onCompleted)
static async Task ProcessEvents(HttpProtocol protocol, Stack<KeyValuePair<Func<object, Task>, object>> events)
{
try
{
await currentTask;
}
catch (Exception ex)
{
Log.ApplicationError(ConnectionId, TraceIdentifier, ex);
}

while (onCompleted.TryPop(out var entry))
// Try/Catch is inside the loop as any error that occurs is after the request has finished.
// So we will just log it and keep processing the events, as the completion has already happened.
while (events.TryPop(out var entry))
{
try
{
await entry.Key.Invoke(entry.Value);
}
catch (Exception ex)
{
Log.ApplicationError(ConnectionId, TraceIdentifier, ex);
protocol.Log.ApplicationError(protocol.ConnectionId, protocol.TraceIdentifier, ex);
}
}
}
}
Expand Down
Loading