-
Notifications
You must be signed in to change notification settings - Fork 447
Implement #1157 by adding client timeout for C# client #1165
Changes from all commits
9da982b
6436e0a
975941f
c4f910b
a54ca5d
db3b58e
de0b3aa
fc3c7b9
e0933c9
dffc24e
fed0b1b
f0ffc9b
85dc2d0
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -24,6 +24,8 @@ namespace Microsoft.AspNetCore.SignalR.Client | |
| { | ||
| public class HubConnection | ||
| { | ||
| public static readonly TimeSpan DefaultServerTimeout = TimeSpan.FromSeconds(30); // Server ping rate is 15 sec, this is 2 times that. | ||
|
|
||
| private readonly ILoggerFactory _loggerFactory; | ||
| private readonly ILogger _logger; | ||
| private readonly IConnection _connection; | ||
|
|
@@ -38,9 +40,17 @@ public class HubConnection | |
|
|
||
| private int _nextId = 0; | ||
| private volatile bool _startCalled; | ||
| private Timer _timeoutTimer; | ||
| private bool _needKeepAlive; | ||
|
|
||
| public Task Closed { get; } | ||
|
|
||
| /// <summary> | ||
| /// Gets or sets the server timeout interval for the connection. Changes to this value | ||
| /// will not be applied until the Keep Alive timer is next reset. | ||
| /// </summary> | ||
| public TimeSpan ServerTimeout { get; set; } = DefaultServerTimeout; | ||
|
|
||
| public HubConnection(IConnection connection, IHubProtocol protocol, ILoggerFactory loggerFactory) | ||
| { | ||
| if (connection == null) | ||
|
|
@@ -64,6 +74,9 @@ public HubConnection(IConnection connection, IHubProtocol protocol, ILoggerFacto | |
| Shutdown(task.Exception); | ||
| return task; | ||
| }).Unwrap(); | ||
|
|
||
| // Create the timer for timeout, but disabled by default (we enable it when started). | ||
| _timeoutTimer = new Timer(state => ((HubConnection)state).TimeoutElapsed(), this, Timeout.Infinite, Timeout.Infinite); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Just be aware that we'll need to set the timer to disabled in
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. yep |
||
| } | ||
|
|
||
| public async Task StartAsync() | ||
|
|
@@ -78,6 +91,20 @@ public async Task StartAsync() | |
| } | ||
| } | ||
|
|
||
| private void TimeoutElapsed() | ||
| { | ||
| _connection.AbortAsync(new TimeoutException($"Server timeout ({ServerTimeout.TotalMilliseconds:0.00}ms) elapsed without receiving a message from the server.")); | ||
| } | ||
|
|
||
| private void ResetTimeoutTimer() | ||
| { | ||
| if (_needKeepAlive) | ||
| { | ||
| _logger.ResettingKeepAliveTimer(); | ||
| _timeoutTimer.Change(ServerTimeout, Timeout.InfiniteTimeSpan); | ||
| } | ||
| } | ||
|
|
||
| private async Task StartAsyncCore() | ||
| { | ||
| var transferModeFeature = _connection.Features.Get<ITransferModeFeature>(); | ||
|
|
@@ -94,6 +121,7 @@ private async Task StartAsyncCore() | |
|
|
||
| transferModeFeature.TransferMode = requestedTransferMode; | ||
| await _connection.StartAsync(); | ||
| _needKeepAlive = _connection.Features.Get<IConnectionInherentKeepAliveFeature>() == null; | ||
| var actualTransferMode = transferModeFeature.TransferMode; | ||
|
|
||
| _protocolReaderWriter = new HubProtocolReaderWriter(_protocol, GetDataEncoder(requestedTransferMode, actualTransferMode)); | ||
|
|
@@ -105,6 +133,8 @@ private async Task StartAsyncCore() | |
| NegotiationProtocol.WriteMessage(new NegotiationMessage(_protocol.Name), memoryStream); | ||
| await _connection.SendAsync(memoryStream.ToArray(), _connectionActive.Token); | ||
| } | ||
|
|
||
| ResetTimeoutTimer(); | ||
| } | ||
|
|
||
| private IDataEncoder GetDataEncoder(TransferMode requestedTransferMode, TransferMode actualTransferMode) | ||
|
|
@@ -125,6 +155,7 @@ private IDataEncoder GetDataEncoder(TransferMode requestedTransferMode, Transfer | |
|
|
||
| private async Task DisposeAsyncCore() | ||
| { | ||
| _timeoutTimer.Dispose(); | ||
| await _connection.DisposeAsync(); | ||
| await Closed; | ||
| } | ||
|
|
@@ -298,6 +329,7 @@ private async Task SendAsyncCore(string methodName, object[] args, CancellationT | |
|
|
||
| private async Task OnDataReceivedAsync(byte[] data) | ||
| { | ||
| ResetTimeoutTimer(); | ||
| if (_protocolReaderWriter.ReadMessages(data, _binder, out var messages)) | ||
| { | ||
| foreach (var message in messages) | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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. | ||
|
|
||
| using System; | ||
|
|
@@ -13,6 +13,7 @@ public interface IConnection | |
| Task StartAsync(); | ||
| Task SendAsync(byte[] data, CancellationToken cancellationToken); | ||
| Task DisposeAsync(); | ||
| Task AbortAsync(Exception ex); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is a sign we should unify the client and sever connection objects. This will turn into
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Agreed, but I don't want to block this PR on that right now since I don't need to unify them to implement this. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. #1177 to track that |
||
|
|
||
| IDisposable OnReceived(Func<byte[], object, Task> callback, object state); | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -9,7 +9,7 @@ namespace Microsoft.AspNetCore.Sockets.Client | |
| { | ||
| public interface ITransport | ||
| { | ||
| Task StartAsync(Uri url, Channel<byte[], SendMessage> application, TransferMode requestedTransferMode, string connectionId); | ||
| Task StartAsync(Uri url, Channel<byte[], SendMessage> application, TransferMode requestedTransferMode, string connectionId, IConnection connection); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This isn't very clean. We shouldn't need to pass the IConnection here.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. How else should transports add features? I actually think it makes sense, it's much like how it works on the server. |
||
| Task StopAsync(); | ||
| TransferMode? Mode { get; } | ||
| } | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"Changes to this value are ignored after StartAsync is called"
False,
ResetTimeoutTimeris called in theOnDataReceivedAsynccallback