Fix race in LongPolling causing flaky tests (#8114)

This commit is contained in:
BrennanConroy 2019-03-12 10:24:54 -07:00 committed by GitHub
parent a95c4b6fd0
commit e4f5fef7ac
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 14 additions and 5 deletions

View File

@ -91,7 +91,7 @@ namespace Microsoft.AspNetCore.Http.Connections.Internal
public System.DateTime LastSeenUtc { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } }
public System.Threading.Tasks.Task PreviousPollTask { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } }
public System.Threading.SemaphoreSlim StateLock { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } }
public Microsoft.AspNetCore.Http.Connections.Internal.HttpConnectionStatus Status { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } }
public Microsoft.AspNetCore.Http.Connections.Internal.HttpConnectionStatus Status { get { throw null; } set { } }
public Microsoft.AspNetCore.Connections.TransferFormat SupportedFormats { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } }
public override System.IO.Pipelines.IDuplexPipe Transport { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } }
public System.Threading.Tasks.Task TransportTask { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } }
@ -102,6 +102,7 @@ namespace Microsoft.AspNetCore.Http.Connections.Internal
public System.Threading.Tasks.Task DisposeAsync(bool closeGracefully = false) { throw null; }
public void OnHeartbeat(System.Action<object> action, object state) { }
public void TickHeartbeat() { }
public bool TryChangeState(Microsoft.AspNetCore.Http.Connections.Internal.HttpConnectionStatus from, Microsoft.AspNetCore.Http.Connections.Internal.HttpConnectionStatus to) { throw null; }
}
public partial class HttpConnectionDispatcher
{

View File

@ -35,6 +35,7 @@ namespace Microsoft.AspNetCore.Http.Connections.Internal
private PipeWriterStream _applicationStream;
private IDuplexPipe _application;
private IDictionary<object, object> _items;
private int _status = (int)HttpConnectionStatus.Inactive;
// This tcs exists so that multiple calls to DisposeAsync all wait asynchronously
// on the same task
@ -95,7 +96,7 @@ namespace Microsoft.AspNetCore.Http.Connections.Internal
public DateTime LastSeenUtc { get; set; }
public HttpConnectionStatus Status { get; set; } = HttpConnectionStatus.Inactive;
public HttpConnectionStatus Status { get => (HttpConnectionStatus)_status; set => Interlocked.Exchange(ref _status, (int)value); }
public override string ConnectionId { get; set; }
@ -309,6 +310,11 @@ namespace Microsoft.AspNetCore.Http.Connections.Internal
}
}
public bool TryChangeState(HttpConnectionStatus from, HttpConnectionStatus to)
{
return Interlocked.CompareExchange(ref _status, (int)to, (int)from) == (int)from;
}
private static class Log
{
private static readonly Action<ILogger, string, Exception> _disposingConnection =

View File

@ -235,7 +235,7 @@ namespace Microsoft.AspNetCore.Http.Connections.Internal
}
// Mark the connection as active
connection.Status = HttpConnectionStatus.Active;
connection.TryChangeState(from: HttpConnectionStatus.Inactive, to: HttpConnectionStatus.Active);
// Raise OnConnected for new connections only since polls happen all the time
if (connection.ApplicationTask == null)
@ -331,7 +331,9 @@ namespace Microsoft.AspNetCore.Http.Connections.Internal
// Mark the connection as inactive
connection.LastSeenUtc = DateTime.UtcNow;
connection.Status = HttpConnectionStatus.Inactive;
// This is done outside a lock because the next poll might be waiting in the lock already and waiting for currentRequestTcs to complete
// A DELETE request could have set the status to Disposed. If that is the case we don't want to change the state ever.
connection.TryChangeState(from: HttpConnectionStatus.Active, to: HttpConnectionStatus.Inactive);
}
}
finally
@ -371,7 +373,7 @@ namespace Microsoft.AspNetCore.Http.Connections.Internal
}
// Mark the connection as active
connection.Status = HttpConnectionStatus.Active;
connection.TryChangeState(HttpConnectionStatus.Inactive, HttpConnectionStatus.Active);
// Call into the end point passing the connection
connection.ApplicationTask = ExecuteApplication(connectionDelegate, connection);