Wait to dispose RequestAborted CTS (#9333)

This commit is contained in:
Justin Kotalik 2019-04-12 16:30:26 -07:00 committed by GitHub
parent 0684498efc
commit e04c79bd3d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 50 additions and 22 deletions

View File

@ -35,7 +35,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
private Stack<KeyValuePair<Func<object, Task>, object>> _onCompleted;
private object _abortLock = new object();
private volatile bool _requestAborted;
private volatile bool _connectionAborted;
private bool _preventRequestAbortedCancellation;
private CancellationTokenSource _abortedCts;
private CancellationToken? _manuallySetRequestAbortToken;
@ -257,7 +257,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
return new CancellationToken(false);
}
if (_requestAborted)
if (_connectionAborted)
{
return new CancellationToken(true);
}
@ -375,18 +375,19 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
Scheme = _scheme;
_manuallySetRequestAbortToken = null;
_preventRequestAbortedCancellation = false;
// Lock to prevent CancelRequestAbortedToken from attempting to cancel an disposed CTS.
// Lock to prevent CancelRequestAbortedToken from attempting to cancel a disposed CTS.
CancellationTokenSource localAbortCts = null;
lock (_abortLock)
{
if (!_requestAborted)
{
_abortedCts?.Dispose();
_abortedCts = null;
}
_preventRequestAbortedCancellation = false;
localAbortCts = _abortedCts;
_abortedCts = null;
}
localAbortCts?.Dispose();
if (_wrapperObjectsToDispose != null)
{
foreach (var disposable in _wrapperObjectsToDispose)
@ -442,9 +443,20 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
{
try
{
_abortedCts.Cancel();
_abortedCts.Dispose();
_abortedCts = null;
CancellationTokenSource localAbortCts = null;
lock (_abortLock)
{
if (_abortedCts != null && !_preventRequestAbortedCancellation)
{
localAbortCts = _abortedCts;
_abortedCts = null;
}
}
// If we cancel the cts, we don't dispose as people may still be using
// the cts. It also isn't necessary to dispose a canceled cts.
localAbortCts?.Cancel();
}
catch (Exception ex)
{
@ -454,17 +466,20 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
protected void AbortRequest()
{
var shouldScheduleCancellation = false;
lock (_abortLock)
{
if (_requestAborted)
if (_connectionAborted)
{
return;
}
_requestAborted = true;
shouldScheduleCancellation = _abortedCts != null && !_preventRequestAbortedCancellation;
_connectionAborted = true;
}
if (_abortedCts != null)
if (shouldScheduleCancellation)
{
// Potentially calling user code. CancelRequestAbortedToken logs any exceptions.
ServiceContext.Scheduler.Schedule(state => ((HttpProtocol)state).CancelRequestAbortedToken(), this);
@ -481,14 +496,12 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
{
lock (_abortLock)
{
if (_requestAborted)
if (_connectionAborted)
{
return;
}
_preventRequestAbortedCancellation = true;
_abortedCts?.Dispose();
_abortedCts = null;
}
}
@ -594,7 +607,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
// Run the application code for this request
await application.ProcessRequestAsync(context);
if (!_requestAborted)
if (!_connectionAborted)
{
VerifyResponseContentLength();
}
@ -630,7 +643,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
// 4XX responses are written by TryProduceInvalidRequestResponse during connection tear down.
if (_requestRejectedException == null)
{
if (!_requestAborted)
if (!_connectionAborted)
{
// Call ProduceEnd() before consuming the rest of the request body to prevent
// delaying clients waiting for the chunk terminator:
@ -662,7 +675,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
application.DisposeContext(context, _applicationException);
// Even for non-keep-alive requests, try to consume the entire body to avoid RSTs.
if (!_requestAborted && _requestRejectedException == null && !messageBody.IsEmpty)
if (!_connectionAborted && _requestRejectedException == null && !messageBody.IsEmpty)
{
await messageBody.ConsumeAsync();
}
@ -964,7 +977,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
protected Task TryProduceInvalidRequestResponse()
{
// If _requestAborted is set, the connection has already been closed.
if (_requestRejectedException != null && !_requestAborted)
if (_requestRejectedException != null && !_connectionAborted)
{
return ProduceEnd();
}

View File

@ -729,6 +729,20 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
Assert.False(_http1Connection.RequestAborted.IsCancellationRequested);
}
[Fact]
public void RequestAbortedTokenIsFullyUsableAfterCancellation()
{
var originalToken = _http1Connection.RequestAborted;
var originalRegistration = originalToken.Register(() => { });
_http1Connection.Abort(new ConnectionAbortedException());
Assert.True(originalToken.WaitHandle.WaitOne(TestConstants.DefaultTimeout));
Assert.True(_http1Connection.RequestAborted.WaitHandle.WaitOne(TestConstants.DefaultTimeout));
Assert.Equal(originalToken, originalRegistration.Token);
}
[Fact]
public void RequestAbortedTokenIsUsableAfterCancellation()
{

View File

@ -217,6 +217,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests
}
[Fact]
[Repeat(20)]
public async Task DoesNotThrowObjectDisposedExceptionFromWriteAsyncAfterConnectionIsAborted()
{
var tcs = new TaskCompletionSource<object>(TaskCreationOptions.RunContinuationsAsynchronously);