Change the HubSample to reconnect to the server (#1933)

- Modified the sample to test our reconnect patterns
- Add IConnectionInherentKeepAliveFeature to the tcp connection impl since Kestrel doesn't implement the heartbeat (yet).
This commit is contained in:
David Fowler 2018-04-10 12:44:30 -07:00 committed by GitHub
parent 5aafb9b8be
commit 5fa2892561
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 77 additions and 38 deletions

View File

@ -2,6 +2,7 @@
// 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;
using System.IO;
using System.Linq; using System.Linq;
using System.Net; using System.Net;
using System.Threading; using System.Threading;
@ -34,7 +35,6 @@ namespace ClientSample
.WithLogging(logging => .WithLogging(logging =>
{ {
logging.AddConsole(); logging.AddConsole();
logging.SetMinimumLevel(LogLevel.Trace);
}); });
if (uri.Scheme == "net.tcp") if (uri.Scheme == "net.tcp")
@ -48,59 +48,83 @@ namespace ClientSample
var connection = connectionBuilder.Build(); var connection = connectionBuilder.Build();
try Console.CancelKeyPress += (sender, a) =>
{ {
var closeTcs = new TaskCompletionSource<object>(); a.Cancel = true;
connection.Closed += e => closeTcs.SetResult(null); connection.DisposeAsync().GetAwaiter().GetResult();
// Set up handler };
connection.On<string>("Send", Console.WriteLine);
await ConnectAsync(connection); // Set up handler
connection.On<string>("Send", Console.WriteLine);
Console.WriteLine("Connected to {0}", uri); CancellationTokenSource closedTokenSource = null;
var sendCts = new CancellationTokenSource(); connection.Closed += e =>
{
// This should never be null by the time this fires
closedTokenSource.Cancel();
Console.CancelKeyPress += async (sender, a) => Console.WriteLine("Connection closed...");
};
while (true)
{
// Dispose the previous token
closedTokenSource?.Dispose();
// Create a new token for this run
closedTokenSource = new CancellationTokenSource();
// Connect to the server
if (!await ConnectAsync(connection))
{ {
a.Cancel = true; break;
Console.WriteLine("Stopping loops..."); }
sendCts.Cancel();
await connection.DisposeAsync();
};
while (!closeTcs.Task.IsCompleted) Console.WriteLine("Connected to {0}", uri); ;
// Handle the connected connection
while (true)
{ {
var completedTask = await Task.WhenAny(Task.Run(() => Console.ReadLine()), closeTcs.Task); try
if (completedTask == closeTcs.Task)
{ {
var line = Console.ReadLine();
if (line == null || closedTokenSource.Token.IsCancellationRequested)
{
break;
}
await connection.InvokeAsync<object>("Send", line);
}
catch (IOException)
{
// Process being shutdown
break; break;
} }
catch (OperationCanceledException)
var line = await (Task<string>)completedTask;
if (line == null)
{ {
// The connection closed
break;
}
catch (ObjectDisposedException)
{
// We're shutting down the client
break;
}
catch (Exception ex)
{
// Send could have failed because the connection closed
System.Console.WriteLine(ex);
break; break;
} }
await connection.InvokeAsync<object>("Send", line, sendCts.Token);
} }
} }
catch (AggregateException aex) when (aex.InnerExceptions.All(e => e is OperationCanceledException))
{
}
catch (OperationCanceledException)
{
}
finally
{
await connection.DisposeAsync();
}
return 0; return 0;
} }
private static async Task ConnectAsync(HubConnection connection) private static async Task<bool> ConnectAsync(HubConnection connection)
{ {
// Keep trying to until we can start // Keep trying to until we can start
while (true) while (true)
@ -108,11 +132,18 @@ namespace ClientSample
try try
{ {
await connection.StartAsync(); await connection.StartAsync();
return; return true;
}
catch (ObjectDisposedException)
{
// Client side killed the connection
return false;
} }
catch (Exception) catch (Exception)
{ {
await Task.Delay(1000); Console.WriteLine("Failed to connect, trying again in 5000(ms)");
await Task.Delay(5000);
} }
} }
} }

View File

@ -4,13 +4,15 @@ using System.IO;
using System.IO.Pipelines; using System.IO.Pipelines;
using System.Net; using System.Net;
using System.Net.Sockets; using System.Net.Sockets;
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;
namespace ClientSample namespace ClientSample
{ {
public class TcpConnection : ConnectionContext public class TcpConnection : ConnectionContext, IConnectionInherentKeepAliveFeature
{ {
private readonly Socket _socket; private readonly Socket _socket;
private volatile bool _aborted; private volatile bool _aborted;
@ -26,6 +28,10 @@ namespace ClientSample
_sender = new SocketSender(_socket, PipeScheduler.ThreadPool); _sender = new SocketSender(_socket, PipeScheduler.ThreadPool);
_receiver = new SocketReceiver(_socket, PipeScheduler.ThreadPool); _receiver = new SocketReceiver(_socket, PipeScheduler.ThreadPool);
// Add IConnectionInherentKeepAliveFeature to the tcp connection impl since Kestrel doesn't implement
// the IConnectionHeartbeatFeature
Features.Set<IConnectionInherentKeepAliveFeature>(this);
} }
public override IDuplexPipe Transport { get; set; } public override IDuplexPipe Transport { get; set; }
@ -34,6 +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;
public Task DisposeAsync() public Task DisposeAsync()
{ {
Transport?.Output.Complete(); Transport?.Output.Complete();