Honor InherentKeepAliveFeature for server timeout (#2727)

This commit is contained in:
BrennanConroy 2018-08-06 10:37:12 -07:00 committed by GitHub
parent f0c4170e42
commit e78e3db6f4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 51 additions and 9 deletions

View File

@ -12,6 +12,7 @@ using System.Threading;
using System.Threading.Channels; using System.Threading.Channels;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore.Connections; using Microsoft.AspNetCore.Connections;
using Microsoft.AspNetCore.Connections.Features;
using Microsoft.AspNetCore.Internal; using Microsoft.AspNetCore.Internal;
using Microsoft.AspNetCore.SignalR.Client.Internal; using Microsoft.AspNetCore.SignalR.Client.Internal;
using Microsoft.AspNetCore.SignalR.Protocol; using Microsoft.AspNetCore.SignalR.Protocol;
@ -47,6 +48,7 @@ namespace Microsoft.AspNetCore.SignalR.Client
private long _nextActivationServerTimeout; private long _nextActivationServerTimeout;
private long _nextActivationSendPing; private long _nextActivationSendPing;
private bool _disposed; private bool _disposed;
private bool _hasInherentKeepAlive;
private readonly ConnectionLogScope _logScope; private readonly ConnectionLogScope _logScope;
@ -302,6 +304,7 @@ namespace Microsoft.AspNetCore.SignalR.Client
// Start the connection // Start the connection
var connection = await _connectionFactory.ConnectAsync(_protocol.TransferFormat); var connection = await _connectionFactory.ConnectAsync(_protocol.TransferFormat);
var startingConnectionState = new ConnectionState(connection, this); var startingConnectionState = new ConnectionState(connection, this);
_hasInherentKeepAlive = connection.Features.Get<IConnectionInherentKeepAliveFeature>()?.HasInherentKeepAlive ?? false;
// From here on, if an error occurs we need to shut down the connection because // From here on, if an error occurs we need to shut down the connection because
// we still own it. // we still own it.
@ -898,19 +901,25 @@ namespace Microsoft.AspNetCore.SignalR.Client
// await returns True until `timer.Stop()` is called in the `finally` block of `ReceiveLoop` // await returns True until `timer.Stop()` is called in the `finally` block of `ReceiveLoop`
while (await timer) while (await timer)
{ {
if (DateTime.UtcNow.Ticks > Volatile.Read(ref _nextActivationServerTimeout)) await RunTimerActions();
{
OnServerTimeout();
}
if (DateTime.UtcNow.Ticks > Volatile.Read(ref _nextActivationSendPing))
{
await PingServer();
}
} }
} }
} }
// Internal for testing
internal async Task RunTimerActions()
{
if (!_hasInherentKeepAlive && DateTime.UtcNow.Ticks > Volatile.Read(ref _nextActivationServerTimeout))
{
OnServerTimeout();
}
if (DateTime.UtcNow.Ticks > Volatile.Read(ref _nextActivationSendPing))
{
await PingServer();
}
}
private void OnServerTimeout() private void OnServerTimeout()
{ {
if (Debugger.IsAttached) if (Debugger.IsAttached)

View File

@ -5,6 +5,7 @@ using System;
using System.Buffers; using System.Buffers;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore.Connections; using Microsoft.AspNetCore.Connections;
using Microsoft.AspNetCore.Connections.Features;
using Microsoft.AspNetCore.SignalR.Protocol; using Microsoft.AspNetCore.SignalR.Protocol;
using Microsoft.AspNetCore.SignalR.Tests; using Microsoft.AspNetCore.SignalR.Tests;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
@ -119,6 +120,33 @@ namespace Microsoft.AspNetCore.SignalR.Client.Tests
Assert.Equal($"Server timeout ({hubConnection.ServerTimeout.TotalMilliseconds:0.00}ms) elapsed without receiving a message from the server.", exception.Message); Assert.Equal($"Server timeout ({hubConnection.ServerTimeout.TotalMilliseconds:0.00}ms) elapsed without receiving a message from the server.", exception.Message);
} }
[Fact]
public async Task ServerTimeoutIsDisabledWhenUsingTransportWithInherentKeepAlive()
{
using (StartVerifiableLog(out var loggerFactory))
{
var testConnection = new TestConnection();
testConnection.Features.Set<IConnectionInherentKeepAliveFeature>(new TestKeepAliveFeature() { HasInherentKeepAlive = true });
var hubConnection = CreateHubConnection(testConnection, loggerFactory: loggerFactory);
hubConnection.ServerTimeout = TimeSpan.FromMilliseconds(1);
await hubConnection.StartAsync().OrTimeout();
var closeTcs = new TaskCompletionSource<Exception>();
hubConnection.Closed += ex =>
{
closeTcs.TrySetResult(ex);
return Task.CompletedTask;
};
await hubConnection.RunTimerActions().OrTimeout();
Assert.False(closeTcs.Task.IsCompleted);
await hubConnection.DisposeAsync().OrTimeout();
}
}
[Fact] [Fact]
public async Task PendingInvocationsAreTerminatedIfServerTimeoutIntervalElapsesWithNoMessages() public async Task PendingInvocationsAreTerminatedIfServerTimeoutIntervalElapsesWithNoMessages()
{ {
@ -145,6 +173,11 @@ namespace Microsoft.AspNetCore.SignalR.Client.Tests
} }
} }
private struct TestKeepAliveFeature : IConnectionInherentKeepAliveFeature
{
public bool HasInherentKeepAlive { get; set; }
}
// Moq really doesn't handle out parameters well, so to make these tests work I added a manual mock -anurse // Moq really doesn't handle out parameters well, so to make these tests work I added a manual mock -anurse
private class MockHubProtocol : IHubProtocol private class MockHubProtocol : IHubProtocol
{ {