React to aspnet/KestrelHttpServer#2496: make IConnectionInherentKeepAliveFeature a boolean feature (#2041)

This commit is contained in:
Andrew Stanton-Nurse 2018-04-16 11:34:12 -07:00 committed by GitHub
parent a426334018
commit c05c5a6b2a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 63 additions and 58 deletions

View File

@ -1,4 +1,4 @@
// Copyright (c) .NET Foundation. All rights reserved. // Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System; using System;
@ -122,4 +122,4 @@ namespace Microsoft.AspNetCore.SignalR.Microbenchmarks
return _hubLifetimeManager.SendUsersAsync(_userIdentifiers, "MethodName", Array.Empty<object>()); return _hubLifetimeManager.SendUsersAsync(_userIdentifiers, "MethodName", Array.Empty<object>());
} }
} }
} }

View File

@ -76,6 +76,6 @@ namespace Microsoft.AspNetCore.SignalR.Microbenchmarks
public class TestConnectionInherentKeepAliveFeature : IConnectionInherentKeepAliveFeature public class TestConnectionInherentKeepAliveFeature : IConnectionInherentKeepAliveFeature
{ {
public TimeSpan KeepAliveInterval { get; } = TimeSpan.Zero; public bool HasInherentKeepAlive { get; } = true;
} }
} }

View File

@ -1,13 +0,0 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using Microsoft.AspNetCore.Connections.Features;
namespace Microsoft.AspNetCore.SignalR.Microbenchmarks.Shared
{
public class TestConnectionInherentKeepAliveFeature : IConnectionInherentKeepAliveFeature
{
public TimeSpan KeepAliveInterval { get; } = TimeSpan.Zero;
}
}

View File

@ -1,4 +1,4 @@
<Project> <Project>
<PropertyGroup> <PropertyGroup>
<MSBuildAllProjects>$(MSBuildAllProjects);$(MSBuildThisFileFullPath)</MSBuildAllProjects> <MSBuildAllProjects>$(MSBuildAllProjects);$(MSBuildThisFileFullPath)</MSBuildAllProjects>
</PropertyGroup> </PropertyGroup>
@ -14,7 +14,7 @@
<MicrosoftAspNetCoreAuthorizationPackageVersion>2.1.0-preview3-32233</MicrosoftAspNetCoreAuthorizationPackageVersion> <MicrosoftAspNetCoreAuthorizationPackageVersion>2.1.0-preview3-32233</MicrosoftAspNetCoreAuthorizationPackageVersion>
<MicrosoftAspNetCoreAuthorizationPolicyPackageVersion>2.1.0-preview3-32233</MicrosoftAspNetCoreAuthorizationPolicyPackageVersion> <MicrosoftAspNetCoreAuthorizationPolicyPackageVersion>2.1.0-preview3-32233</MicrosoftAspNetCoreAuthorizationPolicyPackageVersion>
<MicrosoftAspNetCoreBenchmarkRunnerSourcesPackageVersion>2.1.0-preview3-32233</MicrosoftAspNetCoreBenchmarkRunnerSourcesPackageVersion> <MicrosoftAspNetCoreBenchmarkRunnerSourcesPackageVersion>2.1.0-preview3-32233</MicrosoftAspNetCoreBenchmarkRunnerSourcesPackageVersion>
<MicrosoftAspNetCoreConnectionsAbstractionsPackageVersion>2.1.0-preview3-32233</MicrosoftAspNetCoreConnectionsAbstractionsPackageVersion> <MicrosoftAspNetCoreConnectionsAbstractionsPackageVersion>2.1.0-a-preview3-inherent-keep-alive-bool-17670</MicrosoftAspNetCoreConnectionsAbstractionsPackageVersion>
<MicrosoftAspNetCoreCorsPackageVersion>2.1.0-preview3-32233</MicrosoftAspNetCoreCorsPackageVersion> <MicrosoftAspNetCoreCorsPackageVersion>2.1.0-preview3-32233</MicrosoftAspNetCoreCorsPackageVersion>
<MicrosoftAspNetCoreDiagnosticsEntityFrameworkCorePackageVersion>2.1.0-preview3-32233</MicrosoftAspNetCoreDiagnosticsEntityFrameworkCorePackageVersion> <MicrosoftAspNetCoreDiagnosticsEntityFrameworkCorePackageVersion>2.1.0-preview3-32233</MicrosoftAspNetCoreDiagnosticsEntityFrameworkCorePackageVersion>
<MicrosoftAspNetCoreDiagnosticsPackageVersion>2.1.0-preview3-32233</MicrosoftAspNetCoreDiagnosticsPackageVersion> <MicrosoftAspNetCoreDiagnosticsPackageVersion>2.1.0-preview3-32233</MicrosoftAspNetCoreDiagnosticsPackageVersion>
@ -28,7 +28,7 @@
<MicrosoftAspNetCoreRoutingPackageVersion>2.1.0-preview3-32233</MicrosoftAspNetCoreRoutingPackageVersion> <MicrosoftAspNetCoreRoutingPackageVersion>2.1.0-preview3-32233</MicrosoftAspNetCoreRoutingPackageVersion>
<MicrosoftAspNetCoreServerIISIntegrationPackageVersion>2.1.0-preview3-32233</MicrosoftAspNetCoreServerIISIntegrationPackageVersion> <MicrosoftAspNetCoreServerIISIntegrationPackageVersion>2.1.0-preview3-32233</MicrosoftAspNetCoreServerIISIntegrationPackageVersion>
<MicrosoftAspNetCoreServerIntegrationTestingPackageVersion>0.5.0-preview2-32233</MicrosoftAspNetCoreServerIntegrationTestingPackageVersion> <MicrosoftAspNetCoreServerIntegrationTestingPackageVersion>0.5.0-preview2-32233</MicrosoftAspNetCoreServerIntegrationTestingPackageVersion>
<MicrosoftAspNetCoreServerKestrelPackageVersion>2.1.0-preview3-32233</MicrosoftAspNetCoreServerKestrelPackageVersion> <MicrosoftAspNetCoreServerKestrelPackageVersion>2.1.0-a-preview3-inherent-keep-alive-bool-17670</MicrosoftAspNetCoreServerKestrelPackageVersion>
<MicrosoftAspNetCoreStaticFilesPackageVersion>2.1.0-preview3-32233</MicrosoftAspNetCoreStaticFilesPackageVersion> <MicrosoftAspNetCoreStaticFilesPackageVersion>2.1.0-preview3-32233</MicrosoftAspNetCoreStaticFilesPackageVersion>
<MicrosoftAspNetCoreTestHostPackageVersion>2.1.0-preview3-32233</MicrosoftAspNetCoreTestHostPackageVersion> <MicrosoftAspNetCoreTestHostPackageVersion>2.1.0-preview3-32233</MicrosoftAspNetCoreTestHostPackageVersion>
<MicrosoftAspNetCoreTestingPackageVersion>2.1.0-preview3-32233</MicrosoftAspNetCoreTestingPackageVersion> <MicrosoftAspNetCoreTestingPackageVersion>2.1.0-preview3-32233</MicrosoftAspNetCoreTestingPackageVersion>

View File

@ -1,4 +1,4 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.IO.Pipelines; using System.IO.Pipelines;
@ -40,7 +40,8 @@ namespace ClientSample
public override string ConnectionId { get; set; } = Guid.NewGuid().ToString(); public override string ConnectionId { get; set; } = Guid.NewGuid().ToString();
public override IDictionary<object, object> Items { get; set; } = new ConnectionItems(); public override IDictionary<object, object> Items { get; set; } = new ConnectionItems();
public TimeSpan KeepAliveInterval => Timeout.InfiniteTimeSpan; // We claim to have inherent keep-alive so the client doesn't kill the connection when it hasn't seen ping frames.
public bool HasInherentKeepAlive { get; } = true;
public Task DisposeAsync() public Task DisposeAsync()
{ {

View File

@ -12,14 +12,13 @@ using System.Threading.Tasks;
using Microsoft.AspNetCore.Connections; using Microsoft.AspNetCore.Connections;
using Microsoft.AspNetCore.Connections.Features; using Microsoft.AspNetCore.Connections.Features;
using Microsoft.AspNetCore.Http.Connections.Client.Internal; using Microsoft.AspNetCore.Http.Connections.Client.Internal;
using Microsoft.AspNetCore.Http.Connections.Features;
using Microsoft.AspNetCore.Http.Features; using Microsoft.AspNetCore.Http.Features;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions; using Microsoft.Extensions.Logging.Abstractions;
namespace Microsoft.AspNetCore.Http.Connections.Client namespace Microsoft.AspNetCore.Http.Connections.Client
{ {
public partial class HttpConnection : ConnectionContext public partial class HttpConnection : ConnectionContext, IConnectionInherentKeepAliveFeature
{ {
private static readonly TimeSpan HttpClientTimeout = TimeSpan.FromSeconds(120); private static readonly TimeSpan HttpClientTimeout = TimeSpan.FromSeconds(120);
#if !NETCOREAPP2_1 #if !NETCOREAPP2_1
@ -31,6 +30,7 @@ namespace Microsoft.AspNetCore.Http.Connections.Client
private readonly SemaphoreSlim _connectionLock = new SemaphoreSlim(1, 1); private readonly SemaphoreSlim _connectionLock = new SemaphoreSlim(1, 1);
private bool _started; private bool _started;
private bool _disposed; private bool _disposed;
private bool _hasInherentKeepAlive;
private readonly HttpClient _httpClient; private readonly HttpClient _httpClient;
private readonly HttpConnectionOptions _httpConnectionOptions; private readonly HttpConnectionOptions _httpConnectionOptions;
@ -59,6 +59,8 @@ namespace Microsoft.AspNetCore.Http.Connections.Client
public override string ConnectionId { get; set; } public override string ConnectionId { get; set; }
public override IDictionary<object, object> Items { get; set; } = new ConnectionItems(); public override IDictionary<object, object> Items { get; set; } = new ConnectionItems();
bool IConnectionInherentKeepAliveFeature.HasInherentKeepAlive => _hasInherentKeepAlive;
public HttpConnection(Uri url) public HttpConnection(Uri url)
: this(url, HttpTransports.All) : this(url, HttpTransports.All)
{ } { }
@ -102,6 +104,8 @@ namespace Microsoft.AspNetCore.Http.Connections.Client
_transportFactory = new DefaultTransportFactory(httpConnectionOptions.Transports, _loggerFactory, _httpClient, httpConnectionOptions); _transportFactory = new DefaultTransportFactory(httpConnectionOptions.Transports, _loggerFactory, _httpClient, httpConnectionOptions);
_logScope = new ConnectionLogScope(); _logScope = new ConnectionLogScope();
_scopeDisposable = _logger.BeginScope(_logScope); _scopeDisposable = _logger.BeginScope(_logScope);
Features.Set<IConnectionInherentKeepAliveFeature>(this);
} }
// Used by unit tests // Used by unit tests
@ -347,11 +351,8 @@ namespace Microsoft.AspNetCore.Http.Connections.Client
throw; throw;
} }
if (transportType == HttpTransportType.LongPolling) // Disable keep alives for long polling
{ _hasInherentKeepAlive = transportType == HttpTransportType.LongPolling;
// Disable keep alives for long polling
Features.Set<IConnectionInherentKeepAliveFeature>(new ConnectionInherentKeepAliveFeature(_httpClient.Timeout));
}
// We successfully started, set the transport properties (we don't want to set these until the transport is definitely running). // We successfully started, set the transport properties (we don't want to set these until the transport is definitely running).
_transport = transport; _transport = transport;
@ -465,4 +466,4 @@ namespace Microsoft.AspNetCore.Http.Connections.Client
return negotiationResponse; return negotiationResponse;
} }
} }
} }

View File

@ -1,18 +0,0 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using Microsoft.AspNetCore.Connections.Features;
namespace Microsoft.AspNetCore.Http.Connections.Features
{
public class ConnectionInherentKeepAliveFeature : IConnectionInherentKeepAliveFeature
{
public TimeSpan KeepAliveInterval { get; }
public ConnectionInherentKeepAliveFeature(TimeSpan keepAliveInterval)
{
KeepAliveInterval = keepAliveInterval;
}
}
}

View File

@ -24,7 +24,8 @@ namespace Microsoft.AspNetCore.Http.Connections.Internal
IConnectionHeartbeatFeature, IConnectionHeartbeatFeature,
ITransferFormatFeature, ITransferFormatFeature,
IHttpContextFeature, IHttpContextFeature,
IHttpTransportFeature IHttpTransportFeature,
IConnectionInherentKeepAliveFeature
{ {
private readonly object _itemsLock = new object(); private readonly object _itemsLock = new object();
private readonly object _heartbeatLock = new object(); private readonly object _heartbeatLock = new object();
@ -65,6 +66,7 @@ namespace Microsoft.AspNetCore.Http.Connections.Internal
Features.Set<ITransferFormatFeature>(this); Features.Set<ITransferFormatFeature>(this);
Features.Set<IHttpContextFeature>(this); Features.Set<IHttpContextFeature>(this);
Features.Set<IHttpTransportFeature>(this); Features.Set<IHttpTransportFeature>(this);
Features.Set<IConnectionInherentKeepAliveFeature>(this);
} }
public HttpConnectionContext(string id, IDuplexPipe transport, IDuplexPipe application, ILogger logger = null) public HttpConnectionContext(string id, IDuplexPipe transport, IDuplexPipe application, ILogger logger = null)
@ -97,6 +99,8 @@ namespace Microsoft.AspNetCore.Http.Connections.Internal
public ClaimsPrincipal User { get; set; } public ClaimsPrincipal User { get; set; }
public bool HasInherentKeepAlive { get; set; }
public override IDictionary<object, object> Items public override IDictionary<object, object> Items
{ {
get get

View File

@ -366,10 +366,7 @@ namespace Microsoft.AspNetCore.Http.Connections.Internal
private async Task ExecuteApplication(ConnectionDelegate connectionDelegate, HttpConnectionContext connection) private async Task ExecuteApplication(ConnectionDelegate connectionDelegate, HttpConnectionContext connection)
{ {
// Verify some initialization invariants // Verify some initialization invariants
// We want to be positive that the IConnectionInherentKeepAliveFeature is initialized before invoking the application, if the long polling transport is in use.
Debug.Assert(connection.TransportType != HttpTransportType.None, "Transport has not been initialized yet"); Debug.Assert(connection.TransportType != HttpTransportType.None, "Transport has not been initialized yet");
Debug.Assert(connection.TransportType != HttpTransportType.LongPolling ||
connection.Features.Get<IConnectionInherentKeepAliveFeature>() != null, "Long-polling transport is in use but IConnectionInherentKeepAliveFeature as not configured");
// Jump onto the thread pool thread so blocking user code doesn't block the setup of the // Jump onto the thread pool thread so blocking user code doesn't block the setup of the
// connection and transport // connection and transport
@ -555,7 +552,7 @@ namespace Microsoft.AspNetCore.Http.Connections.Internal
// Configure transport-specific features. // Configure transport-specific features.
if (transportType == HttpTransportType.LongPolling) if (transportType == HttpTransportType.LongPolling)
{ {
connection.Features.Set<IConnectionInherentKeepAliveFeature>(new ConnectionInherentKeepAliveFeature(options.LongPolling.PollTimeout)); connection.HasInherentKeepAlive = true;
// For long polling, the requests come and go but the connection is still alive. // For long polling, the requests come and go but the connection is still alive.
// To make the IHttpContextFeature work well, we make a copy of the relevant properties // To make the IHttpContextFeature work well, we make a copy of the relevant properties

View File

@ -720,7 +720,9 @@ namespace Microsoft.AspNetCore.SignalR.Client
{ {
// Check if we need keep-alive // Check if we need keep-alive
Timer timeoutTimer = null; Timer timeoutTimer = null;
if (connectionState.Connection.Features.Get<IConnectionInherentKeepAliveFeature>() == null)
// We use '!== true' because it could be null, which we treat as false.
if (connectionState.Connection.Features.Get<IConnectionInherentKeepAliveFeature>()?.HasInherentKeepAlive != true)
{ {
Log.StartingServerTimeoutTimer(_logger, ServerTimeout); Log.StartingServerTimeoutTimer(_logger, ServerTimeout);
timeoutTimer = new Timer( timeoutTimer = new Timer(

View File

@ -339,7 +339,8 @@ namespace Microsoft.AspNetCore.SignalR
UserIdentifier = userIdProvider.GetUserId(this); UserIdentifier = userIdProvider.GetUserId(this);
if (Features.Get<IConnectionInherentKeepAliveFeature>() == null) // != true needed because it could be null (which we treat as false)
if (Features.Get<IConnectionInherentKeepAliveFeature>()?.HasInherentKeepAlive != true)
{ {
// Only register KeepAlive after protocol handshake otherwise KeepAliveTick could try to write without having a ProtocolReaderWriter // Only register KeepAlive after protocol handshake otherwise KeepAliveTick could try to write without having a ProtocolReaderWriter
Features.Get<IConnectionHeartbeatFeature>()?.OnHeartbeat(state => ((HubConnectionContext)state).KeepAliveTick(), this); Features.Get<IConnectionHeartbeatFeature>()?.OnHeartbeat(state => ((HubConnectionContext)state).KeepAliveTick(), this);

View File

@ -1587,12 +1587,12 @@ namespace Microsoft.AspNetCore.Http.Connections.Tests
var options = new HttpConnectionDispatcherOptions(); var options = new HttpConnectionDispatcherOptions();
options.LongPolling.PollTimeout = TimeSpan.FromMilliseconds(1); // We don't care about the poll itself options.LongPolling.PollTimeout = TimeSpan.FromMilliseconds(1); // We don't care about the poll itself
Assert.Null(connection.Features.Get<IConnectionInherentKeepAliveFeature>());
await dispatcher.ExecuteAsync(context, options, app).OrTimeout(); await dispatcher.ExecuteAsync(context, options, app).OrTimeout();
Assert.NotNull(connection.Features.Get<IConnectionInherentKeepAliveFeature>()); Assert.True(connection.HasInherentKeepAlive);
Assert.Equal(options.LongPolling.PollTimeout, connection.Features.Get<IConnectionInherentKeepAliveFeature>().KeepAliveInterval);
// Check via the feature as well to make sure it's there.
Assert.True(connection.Features.Get<IConnectionInherentKeepAliveFeature>().HasInherentKeepAlive);
} }
} }

View File

@ -9,6 +9,7 @@ using System.Reflection;
using System.Text; using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore.Connections; using Microsoft.AspNetCore.Connections;
using Microsoft.AspNetCore.Connections.Features;
using Microsoft.AspNetCore.Http.Connections; using Microsoft.AspNetCore.Http.Connections;
using Microsoft.AspNetCore.Http.Connections.Client; using Microsoft.AspNetCore.Http.Connections.Client;
using Microsoft.AspNetCore.Http.Connections.Client.Internal; using Microsoft.AspNetCore.Http.Connections.Client.Internal;
@ -69,6 +70,35 @@ namespace Microsoft.AspNetCore.SignalR.Client.Tests
Assert.True(requestsExecuted); Assert.True(requestsExecuted);
} }
[Theory]
[InlineData(HttpTransportType.LongPolling, true)]
[InlineData(HttpTransportType.ServerSentEvents, false)]
public async Task HttpConnectionSetsInherentKeepAliveFeature(HttpTransportType transportType, bool expectedValue)
{
var testHttpHandler = new TestHttpMessageHandler(autoNegotiate: false);
testHttpHandler.OnRequest((request, next, token) =>
{
return Task.FromResult(ResponseUtils.CreateResponse(HttpStatusCode.NoContent));
});
testHttpHandler.OnNegotiate((_, cancellationToken) =>
{
return ResponseUtils.CreateResponse(HttpStatusCode.OK, ResponseUtils.CreateNegotiationContent());
});
await WithConnectionAsync(
CreateConnection(testHttpHandler, transportType: transportType),
async (connection) =>
{
await connection.StartAsync(TransferFormat.Text).OrTimeout();
var feature = connection.Features.Get<IConnectionInherentKeepAliveFeature>();
Assert.NotNull(feature);
Assert.Equal(expectedValue, feature.HasInherentKeepAlive);
});
}
[Theory] [Theory]
[InlineData(HttpTransportType.LongPolling)] [InlineData(HttpTransportType.LongPolling)]
[InlineData(HttpTransportType.ServerSentEvents)] [InlineData(HttpTransportType.ServerSentEvents)]