LongPolling should not ping (#15352)
This commit is contained in:
parent
8566d663c3
commit
dfba024c78
|
|
@ -1626,6 +1626,7 @@ namespace Microsoft.AspNetCore.SignalR.Client
|
||||||
{
|
{
|
||||||
private readonly HubConnection _hubConnection;
|
private readonly HubConnection _hubConnection;
|
||||||
private readonly ILogger _logger;
|
private readonly ILogger _logger;
|
||||||
|
private readonly bool _hasInherentKeepAlive;
|
||||||
|
|
||||||
private readonly object _lock = new object();
|
private readonly object _lock = new object();
|
||||||
private readonly Dictionary<string, InvocationRequest> _pendingCalls = new Dictionary<string, InvocationRequest>(StringComparer.Ordinal);
|
private readonly Dictionary<string, InvocationRequest> _pendingCalls = new Dictionary<string, InvocationRequest>(StringComparer.Ordinal);
|
||||||
|
|
@ -1637,7 +1638,6 @@ namespace Microsoft.AspNetCore.SignalR.Client
|
||||||
|
|
||||||
private long _nextActivationServerTimeout;
|
private long _nextActivationServerTimeout;
|
||||||
private long _nextActivationSendPing;
|
private long _nextActivationSendPing;
|
||||||
private bool _hasInherentKeepAlive;
|
|
||||||
|
|
||||||
public ConnectionContext Connection { get; }
|
public ConnectionContext Connection { get; }
|
||||||
public Task ReceiveTask { get; set; }
|
public Task ReceiveTask { get; set; }
|
||||||
|
|
@ -1764,7 +1764,10 @@ namespace Microsoft.AspNetCore.SignalR.Client
|
||||||
// Old clients never ping, and shouldn't be timed out, so ping to tell the server that we should be timed out if we stop.
|
// Old clients never ping, and shouldn't be timed out, so ping to tell the server that we should be timed out if we stop.
|
||||||
// The TimerLoop is started from the ReceiveLoop with the connection lock still acquired.
|
// The TimerLoop is started from the ReceiveLoop with the connection lock still acquired.
|
||||||
_hubConnection._state.AssertInConnectionLock();
|
_hubConnection._state.AssertInConnectionLock();
|
||||||
await _hubConnection.SendHubMessage(this, PingMessage.Instance);
|
if (!_hasInherentKeepAlive)
|
||||||
|
{
|
||||||
|
await _hubConnection.SendHubMessage(this, PingMessage.Instance);
|
||||||
|
}
|
||||||
|
|
||||||
// initialize the timers
|
// initialize the timers
|
||||||
timer.Start();
|
timer.Start();
|
||||||
|
|
@ -1794,7 +1797,12 @@ namespace Microsoft.AspNetCore.SignalR.Client
|
||||||
// Internal for testing
|
// Internal for testing
|
||||||
internal async Task RunTimerActions()
|
internal async Task RunTimerActions()
|
||||||
{
|
{
|
||||||
if (!_hasInherentKeepAlive && DateTime.UtcNow.Ticks > Volatile.Read(ref _nextActivationServerTimeout))
|
if (_hasInherentKeepAlive)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (DateTime.UtcNow.Ticks > Volatile.Read(ref _nextActivationServerTimeout))
|
||||||
{
|
{
|
||||||
OnServerTimeout();
|
OnServerTimeout();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -648,6 +648,33 @@ namespace Microsoft.AspNetCore.SignalR.Client.Tests
|
||||||
await connection.DisposeAsync().OrTimeout();
|
await connection.DisposeAsync().OrTimeout();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task ClientWithInherentKeepAliveDoesNotPing()
|
||||||
|
{
|
||||||
|
var connection = new TestConnection(hasInherentKeepAlive: true);
|
||||||
|
var hubConnection = CreateHubConnection(connection);
|
||||||
|
|
||||||
|
hubConnection.TickRate = TimeSpan.FromMilliseconds(30);
|
||||||
|
hubConnection.KeepAliveInterval = TimeSpan.FromMilliseconds(80);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await hubConnection.StartAsync().OrTimeout();
|
||||||
|
|
||||||
|
await Task.Delay(1000);
|
||||||
|
|
||||||
|
await hubConnection.DisposeAsync().OrTimeout();
|
||||||
|
await connection.DisposeAsync().OrTimeout();
|
||||||
|
|
||||||
|
Assert.Equal(0, (await connection.ReadAllSentMessagesAsync(ignorePings: false).OrTimeout()).Count);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
await hubConnection.DisposeAsync().OrTimeout();
|
||||||
|
await connection.DisposeAsync().OrTimeout();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ using System.Text;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
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.Features;
|
using Microsoft.AspNetCore.Http.Features;
|
||||||
using Microsoft.AspNetCore.Internal;
|
using Microsoft.AspNetCore.Internal;
|
||||||
using Microsoft.AspNetCore.SignalR.Protocol;
|
using Microsoft.AspNetCore.SignalR.Protocol;
|
||||||
|
|
@ -18,7 +19,7 @@ using Newtonsoft.Json.Linq;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.SignalR.Client.Tests
|
namespace Microsoft.AspNetCore.SignalR.Client.Tests
|
||||||
{
|
{
|
||||||
internal class TestConnection : ConnectionContext
|
internal class TestConnection : ConnectionContext, IConnectionInherentKeepAliveFeature
|
||||||
{
|
{
|
||||||
private readonly bool _autoHandshake;
|
private readonly bool _autoHandshake;
|
||||||
private readonly TaskCompletionSource<object> _started = new TaskCompletionSource<object>();
|
private readonly TaskCompletionSource<object> _started = new TaskCompletionSource<object>();
|
||||||
|
|
@ -30,6 +31,7 @@ namespace Microsoft.AspNetCore.SignalR.Client.Tests
|
||||||
|
|
||||||
private readonly Func<Task> _onStart;
|
private readonly Func<Task> _onStart;
|
||||||
private readonly Func<Task> _onDispose;
|
private readonly Func<Task> _onDispose;
|
||||||
|
private readonly bool _hasInherentKeepAlive;
|
||||||
|
|
||||||
public override string ConnectionId { get; set; }
|
public override string ConnectionId { get; set; }
|
||||||
|
|
||||||
|
|
@ -41,17 +43,22 @@ namespace Microsoft.AspNetCore.SignalR.Client.Tests
|
||||||
|
|
||||||
public override IDictionary<object, object> Items { get; set; } = new ConnectionItems();
|
public override IDictionary<object, object> Items { get; set; } = new ConnectionItems();
|
||||||
|
|
||||||
public TestConnection(Func<Task> onStart = null, Func<Task> onDispose = null, bool autoHandshake = true)
|
bool IConnectionInherentKeepAliveFeature.HasInherentKeepAlive => _hasInherentKeepAlive;
|
||||||
|
|
||||||
|
public TestConnection(Func<Task> onStart = null, Func<Task> onDispose = null, bool autoHandshake = true, bool hasInherentKeepAlive = false)
|
||||||
{
|
{
|
||||||
_autoHandshake = autoHandshake;
|
_autoHandshake = autoHandshake;
|
||||||
_onStart = onStart ?? (() => Task.CompletedTask);
|
_onStart = onStart ?? (() => Task.CompletedTask);
|
||||||
_onDispose = onDispose ?? (() => Task.CompletedTask);
|
_onDispose = onDispose ?? (() => Task.CompletedTask);
|
||||||
|
_hasInherentKeepAlive = hasInherentKeepAlive;
|
||||||
|
|
||||||
var options = new PipeOptions(readerScheduler: PipeScheduler.Inline, writerScheduler: PipeScheduler.Inline, useSynchronizationContext: false);
|
var options = new PipeOptions(readerScheduler: PipeScheduler.Inline, writerScheduler: PipeScheduler.Inline, useSynchronizationContext: false);
|
||||||
|
|
||||||
var pair = DuplexPipe.CreateConnectionPair(options, options);
|
var pair = DuplexPipe.CreateConnectionPair(options, options);
|
||||||
Application = pair.Application;
|
Application = pair.Application;
|
||||||
Transport = pair.Transport;
|
Transport = pair.Transport;
|
||||||
|
|
||||||
|
Features.Set<IConnectionInherentKeepAliveFeature>(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override ValueTask DisposeAsync() => DisposeCoreAsync();
|
public override ValueTask DisposeAsync() => DisposeCoreAsync();
|
||||||
|
|
@ -119,6 +126,10 @@ namespace Microsoft.AspNetCore.SignalR.Client.Tests
|
||||||
while (true)
|
while (true)
|
||||||
{
|
{
|
||||||
var result = await ReadSentTextMessageAsyncInner();
|
var result = await ReadSentTextMessageAsyncInner();
|
||||||
|
if (result == null)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
var receivedMessageType = (int?)JObject.Parse(result)["type"];
|
var receivedMessageType = (int?)JObject.Parse(result)["type"];
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -589,6 +589,10 @@ export class HubConnection {
|
||||||
}
|
}
|
||||||
|
|
||||||
private resetKeepAliveInterval() {
|
private resetKeepAliveInterval() {
|
||||||
|
if (this.connection.features.inherentKeepAlive) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
this.cleanupPingTimer();
|
this.cleanupPingTimer();
|
||||||
this.pingServerHandle = setTimeout(async () => {
|
this.pingServerHandle = setTimeout(async () => {
|
||||||
if (this.connectionState === HubConnectionState.Connected) {
|
if (this.connectionState === HubConnectionState.Connected) {
|
||||||
|
|
|
||||||
|
|
@ -127,6 +127,25 @@ describe("HubConnection", () => {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("does not send pings for connection with inherentKeepAlive", async () => {
|
||||||
|
await VerifyLogger.run(async (logger) => {
|
||||||
|
const connection = new TestConnection(true, true);
|
||||||
|
const hubConnection = createHubConnection(connection, logger);
|
||||||
|
|
||||||
|
hubConnection.keepAliveIntervalInMilliseconds = 5;
|
||||||
|
|
||||||
|
try {
|
||||||
|
await hubConnection.start();
|
||||||
|
await delayUntil(500);
|
||||||
|
|
||||||
|
const numPings = connection.sentData.filter((s) => JSON.parse(s).type === MessageType.Ping).length;
|
||||||
|
expect(numPings).toEqual(0);
|
||||||
|
} finally {
|
||||||
|
await hubConnection.stop();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("stop", () => {
|
describe("stop", () => {
|
||||||
|
|
|
||||||
|
|
@ -17,13 +17,14 @@ export class TestConnection implements IConnection {
|
||||||
|
|
||||||
private autoHandshake: boolean | null;
|
private autoHandshake: boolean | null;
|
||||||
|
|
||||||
constructor(autoHandshake: boolean = true) {
|
constructor(autoHandshake: boolean = true, hasInherentKeepAlive: boolean = false) {
|
||||||
this.onreceive = null;
|
this.onreceive = null;
|
||||||
this.onclose = null;
|
this.onclose = null;
|
||||||
this.sentData = [];
|
this.sentData = [];
|
||||||
this.lastInvocationId = null;
|
this.lastInvocationId = null;
|
||||||
this.autoHandshake = autoHandshake;
|
this.autoHandshake = autoHandshake;
|
||||||
this.baseUrl = "http://example.com";
|
this.baseUrl = "http://example.com";
|
||||||
|
this.features.inherentKeepAlive = hasInherentKeepAlive;
|
||||||
}
|
}
|
||||||
|
|
||||||
public start(): Promise<void> {
|
public start(): Promise<void> {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue