Fix race with CTS disposing (#11757)
This commit is contained in:
parent
4481046c45
commit
e40b218039
|
|
@ -253,7 +253,7 @@ namespace Microsoft.AspNetCore.Http.Connections.Internal
|
||||||
|
|
||||||
if (TransportType == HttpTransportType.WebSockets)
|
if (TransportType == HttpTransportType.WebSockets)
|
||||||
{
|
{
|
||||||
// The websocket transport will close the application output automatically when reading is cancelled
|
// The websocket transport will close the application output automatically when reading is canceled
|
||||||
Cancellation?.Cancel();
|
Cancellation?.Cancel();
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
|
@ -443,6 +443,45 @@ namespace Microsoft.AspNetCore.Http.Connections.Internal
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal async Task<bool> CancelPreviousPoll(HttpContext context)
|
||||||
|
{
|
||||||
|
CancellationTokenSource cts;
|
||||||
|
lock (_stateLock)
|
||||||
|
{
|
||||||
|
// Need to sync cts access with DisposeAsync as that will dispose the cts
|
||||||
|
if (Status == HttpConnectionStatus.Disposed)
|
||||||
|
{
|
||||||
|
cts = null;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
cts = Cancellation;
|
||||||
|
Cancellation = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
using (cts)
|
||||||
|
{
|
||||||
|
// Cancel the previous request
|
||||||
|
cts?.Cancel();
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Wait for the previous request to drain
|
||||||
|
await PreviousPollTask;
|
||||||
|
}
|
||||||
|
catch (OperationCanceledException)
|
||||||
|
{
|
||||||
|
// Previous poll canceled due to connection closing, close this poll too
|
||||||
|
context.Response.ContentType = "text/plain";
|
||||||
|
context.Response.StatusCode = StatusCodes.Status204NoContent;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void MarkInactive()
|
public void MarkInactive()
|
||||||
{
|
{
|
||||||
lock (_stateLock)
|
lock (_stateLock)
|
||||||
|
|
|
||||||
|
|
@ -164,7 +164,7 @@ namespace Microsoft.AspNetCore.Http.Connections.Internal
|
||||||
|
|
||||||
Log.EstablishedConnection(_logger);
|
Log.EstablishedConnection(_logger);
|
||||||
|
|
||||||
// Allow the reads to be cancelled
|
// Allow the reads to be canceled
|
||||||
connection.Cancellation = new CancellationTokenSource();
|
connection.Cancellation = new CancellationTokenSource();
|
||||||
|
|
||||||
var ws = new WebSocketsServerTransport(options.WebSockets, connection.Application, connection, _loggerFactory);
|
var ws = new WebSocketsServerTransport(options.WebSockets, connection.Application, connection, _loggerFactory);
|
||||||
|
|
@ -189,28 +189,15 @@ namespace Microsoft.AspNetCore.Http.Connections.Internal
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!await connection.CancelPreviousPoll(context))
|
||||||
|
{
|
||||||
|
// Connection closed. It's already set the response status code.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Create a new Tcs every poll to keep track of the poll finishing, so we can properly wait on previous polls
|
// Create a new Tcs every poll to keep track of the poll finishing, so we can properly wait on previous polls
|
||||||
var currentRequestTcs = new TaskCompletionSource<object>(TaskCreationOptions.RunContinuationsAsynchronously);
|
var currentRequestTcs = new TaskCompletionSource<object>(TaskCreationOptions.RunContinuationsAsynchronously);
|
||||||
|
|
||||||
using (connection.Cancellation)
|
|
||||||
{
|
|
||||||
// Cancel the previous request
|
|
||||||
connection.Cancellation?.Cancel();
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
// Wait for the previous request to drain
|
|
||||||
await connection.PreviousPollTask;
|
|
||||||
}
|
|
||||||
catch (OperationCanceledException)
|
|
||||||
{
|
|
||||||
// Previous poll canceled due to connection closing, close this poll too
|
|
||||||
context.Response.ContentType = "text/plain";
|
|
||||||
context.Response.StatusCode = StatusCodes.Status204NoContent;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!connection.TryActivateLongPollingConnection(
|
if (!connection.TryActivateLongPollingConnection(
|
||||||
connectionDelegate, context, options.LongPolling.PollTimeout,
|
connectionDelegate, context, options.LongPolling.PollTimeout,
|
||||||
currentRequestTcs.Task, _loggerFactory, _logger))
|
currentRequestTcs.Task, _loggerFactory, _logger))
|
||||||
|
|
|
||||||
|
|
@ -1148,9 +1148,22 @@ namespace Microsoft.AspNetCore.Http.Connections.Tests
|
||||||
Assert.True(request1.IsCompleted);
|
Assert.True(request1.IsCompleted);
|
||||||
|
|
||||||
request1 = dispatcher.ExecuteAsync(context1, options, app);
|
request1 = dispatcher.ExecuteAsync(context1, options, app);
|
||||||
|
var count = 0;
|
||||||
|
// Wait until the request has started internally
|
||||||
|
while (connection.TransportTask.IsCompleted && count < 50)
|
||||||
|
{
|
||||||
|
count++;
|
||||||
|
await Task.Delay(15);
|
||||||
|
}
|
||||||
|
if (count == 50)
|
||||||
|
{
|
||||||
|
Assert.True(false, "Poll took too long to start");
|
||||||
|
}
|
||||||
|
|
||||||
var request2 = dispatcher.ExecuteAsync(context2, options, app);
|
var request2 = dispatcher.ExecuteAsync(context2, options, app);
|
||||||
|
|
||||||
await request1;
|
// Wait for poll to be canceled
|
||||||
|
await request1.OrTimeout();
|
||||||
|
|
||||||
Assert.Equal(StatusCodes.Status204NoContent, context1.Response.StatusCode);
|
Assert.Equal(StatusCodes.Status204NoContent, context1.Response.StatusCode);
|
||||||
Assert.Equal(HttpConnectionStatus.Active, connection.Status);
|
Assert.Equal(HttpConnectionStatus.Active, connection.Status);
|
||||||
|
|
@ -1164,7 +1177,6 @@ namespace Microsoft.AspNetCore.Http.Connections.Tests
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
[Flaky("https://github.com/aspnet/AspNetCore-Internal/issues/2040", "All")]
|
|
||||||
public async Task MultipleRequestsToActiveConnectionId409ForLongPolling()
|
public async Task MultipleRequestsToActiveConnectionId409ForLongPolling()
|
||||||
{
|
{
|
||||||
using (StartVerifiableLog())
|
using (StartVerifiableLog())
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue