Commit ac21a3f9 authored by Andreas Müller's avatar Andreas Müller

UnitTests TcpClient enhanced. Minor fixes.

parent 6a6c6a4c
Pipeline #34 passed with stage
in 42 seconds
......@@ -3,7 +3,6 @@
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp2.2</TargetFramework>
<LangVersion>7.1</LangVersion>
</PropertyGroup>
<ItemGroup>
......
......@@ -13,7 +13,6 @@
<PackageId>AMWD.Modbus.Common</PackageId>
<AssemblyName>AMWD.Modbus.Common</AssemblyName>
<RootNamespace>AMWD.Modbus.Common</RootNamespace>
<LangVersion>7.1</LangVersion>
</PropertyGroup>
<PropertyGroup>
<NrtRevisionFormat>{semvertag}</NrtRevisionFormat>
......
......@@ -13,7 +13,6 @@
<PackageId>AMWD.Modbus.Serial</PackageId>
<AssemblyName>AMWD.Modbus.Serial</AssemblyName>
<RootNamespace>AMWD.Modbus.Serial</RootNamespace>
<LangVersion>7.1</LangVersion>
</PropertyGroup>
<PropertyGroup>
<NrtRevisionFormat>{semvertag}</NrtRevisionFormat>
......
......@@ -35,7 +35,6 @@ namespace AMWD.Modbus.Tcp.Client
private bool isStarted = false;
private bool wasConnected = false;
private bool isReconnecting = false;
private TaskCompletionSource<bool> reconnectTcs;
private Task receiveTask;
// Transaction handling
......@@ -137,7 +136,7 @@ namespace AMWD.Modbus.Tcp.Client
/// <summary>
/// Gets a value indicating whether the connection is established.
/// </summary>
public bool IsConnected => tcpClient?.Connected ?? false;
public bool IsConnected => !isReconnecting && (tcpClient?.Connected ?? false);
/// <summary>
/// Gets or sets the max. reconnect timespan until the reconnect is aborted.
......@@ -980,7 +979,7 @@ namespace AMWD.Modbus.Tcp.Client
#region Private Methods
private async Task ReceiveLoop(CancellationToken ct)
private async void ReceiveLoop(CancellationToken ct)
{
logger?.LogInformation("ModbusClient.ReceiveLoop started");
var reported = false;
......@@ -1008,7 +1007,9 @@ namespace AMWD.Modbus.Tcp.Client
SpinWait.SpinUntil(() => stream.DataAvailable || ct.IsCancellationRequested);
if (ct.IsCancellationRequested)
{
continue;
}
var bytes = new List<byte>();
......@@ -1022,7 +1023,9 @@ namespace AMWD.Modbus.Tcp.Client
}
while (expectedCount > 0 && !ct.IsCancellationRequested);
if (ct.IsCancellationRequested)
{
continue;
}
var lenBytes = bytes.Skip(4).Take(2).ToArray();
if (BitConverter.IsLittleEndian)
......@@ -1040,7 +1043,9 @@ namespace AMWD.Modbus.Tcp.Client
}
while (expectedCount > 0 && !ct.IsCancellationRequested);
if (ct.IsCancellationRequested)
{
continue;
}
var response = new Response(bytes.ToArray());
if (awaitedResponses.TryRemove(response.TransactionId, out var tcs))
......@@ -1052,6 +1057,10 @@ namespace AMWD.Modbus.Tcp.Client
{
// stream already gone
}
catch (InvalidOperationException) when (isReconnecting)
{
// server broken
}
catch (Exception ex)
{
logger?.LogError(ex, "ModbusClient.ReceiveLoop");
......@@ -1124,7 +1133,7 @@ namespace AMWD.Modbus.Tcp.Client
}
}
private async void Reconnect(CancellationToken ct)
private async Task Reconnect(CancellationToken ct)
{
if (isReconnecting || ct.IsCancellationRequested)
{
......@@ -1133,7 +1142,6 @@ namespace AMWD.Modbus.Tcp.Client
logger?.LogInformation("ModbusClient.Reconnect started");
isReconnecting = true;
reconnectTcs = new TaskCompletionSource<bool>();
if (wasConnected)
{
......@@ -1145,77 +1153,63 @@ namespace AMWD.Modbus.Tcp.Client
int maxTimeout = 30;
var startTime = DateTime.UtcNow;
using (ct.Register(() => reconnectTcs.TrySetCanceled()))
try
{
try
while (!ct.IsCancellationRequested)
{
while (!ct.IsCancellationRequested)
try
{
try
tcpClient?.Dispose();
tcpClient = new TcpClient(AddressFamily.InterNetworkV6);
tcpClient.Client.DualMode = true;
var task = tcpClient.ConnectAsync(Host, Port);
if (await Task.WhenAny(task, Task.Delay(TimeSpan.FromSeconds(timeout), ct)) == task && tcpClient.Connected)
{
tcpClient?.Dispose();
tcpClient = new TcpClient(AddressFamily.InterNetworkV6);
tcpClient.Client.DualMode = true;
var task = tcpClient.ConnectAsync(Host, Port);
if (await Task.WhenAny(task, Task.Delay(TimeSpan.FromSeconds(timeout), ct)) == task && tcpClient.Connected)
{
logger?.LogInformation("ModbusClient.Reconnect connected");
Task.Run(() => Connected?.Invoke(this, EventArgs.Empty)).Forget();
reconnectTcs.TrySetResult(true);
reconnectTcs = null;
wasConnected = true;
return;
}
else if (ct.IsCancellationRequested)
logger?.LogInformation("ModbusClient.Reconnect connected");
Task.Run(() => Connected?.Invoke(this, EventArgs.Empty)).Forget();
wasConnected = true;
return;
}
else if (ct.IsCancellationRequested)
{
logger?.LogInformation("ModbusClient.Reconnect was cancelled");
return;
}
else
{
timeout += 2;
if (timeout > maxTimeout)
{
logger?.LogInformation("ModbusClient.Reconnect was cancelled");
return;
timeout = maxTimeout;
}
else
{
timeout += 2;
if (timeout > maxTimeout)
{
timeout = maxTimeout;
}
else
{
logger?.LogWarning($"ModbusClient.Reconnect failed to connect within {timeout-2} seconds");
}
throw new SocketException((int)SocketError.TimedOut);
logger?.LogWarning($"ModbusClient.Reconnect failed to connect within {timeout - 2} seconds");
}
}
catch (SocketException) when (ReconnectTimeSpan == TimeSpan.MaxValue || DateTime.UtcNow <= startTime + ReconnectTimeSpan)
{
await Task.Delay(1000, ct);
continue;
}
catch (Exception ex)
{
logger?.LogError(ex, "ModbusClient.Reconnect failed");
reconnectTcs.TrySetException(ex);
throw new SocketException((int)SocketError.TimedOut);
}
}
catch (SocketException) when (ReconnectTimeSpan == TimeSpan.MaxValue || DateTime.UtcNow <= startTime + ReconnectTimeSpan)
{
await Task.Delay(1000, ct);
continue;
}
catch (Exception ex)
{
logger?.LogError(ex, "ModbusClient.Reconnect failed");
}
}
finally
{
isReconnecting = false;
}
}
finally
{
isReconnecting = false;
}
}
private async Task GetWaitTask(CancellationToken ct)
{
var rTcs = reconnectTcs;
if (rTcs != null)
{
await rTcs.Task;
}
else
{
await Task.Run(() => SpinWait.SpinUntil(() => IsConnected || ct.IsCancellationRequested));
}
await Task.Run(() => SpinWait.SpinUntil(() => IsConnected || ct.IsCancellationRequested));
}
private async Task DisconnectInternal(bool disposing)
......@@ -1236,9 +1230,7 @@ namespace AMWD.Modbus.Tcp.Client
try
{
reconnectTcs?.TrySetResult(false);
mainCts?.Cancel();
reconnectTcs = null;
}
catch (Exception ex)
{
......
......@@ -13,7 +13,6 @@
<PackageId>AMWD.Modbus.Tcp</PackageId>
<AssemblyName>AMWD.Modbus.Tcp</AssemblyName>
<RootNamespace>AMWD.Modbus.Tcp</RootNamespace>
<LangVersion>7.1</LangVersion>
</PropertyGroup>
<PropertyGroup>
<NrtRevisionFormat>{semvertag}</NrtRevisionFormat>
......
using Microsoft.Extensions.Logging;
using System;
namespace UnitTests
{
internal class ConsoleLogger : ILogger
{
private readonly object syncObj = new object();
public ConsoleLogger()
{
}
public ConsoleLogger(string name)
{
Name = name;
}
public ConsoleLogger(string name, ConsoleLogger parentLogger)
{
Name = name;
ParentLogger = parentLogger;
}
public string Name { get; }
public ConsoleLogger ParentLogger { get; }
public bool DisableColors { get; set; }
public string TimestampFormat { get; set; }
public LogLevel MinLevel { get; set; }
public IDisposable BeginScope<TState>(TState state)
{
throw new NotImplementedException();
}
public bool IsEnabled(LogLevel logLevel)
{
return logLevel >= MinLevel;
}
public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter)
{
if (!IsEnabled(logLevel))
{
return;
}
if (formatter == null)
{
throw new ArgumentNullException(nameof(formatter));
}
string message = formatter(state, exception);
if (!string.IsNullOrEmpty(message) || exception != null)
{
if (ParentLogger != null)
{
ParentLogger.WriteMessage(Name, logLevel, eventId.Id, message, exception);
}
else
{
WriteMessage(Name, logLevel, eventId.Id, message, exception);
}
}
}
private void WriteMessage(string name, LogLevel logLevel, int eventId, string message, Exception exception)
{
if (exception != null)
{
if (!string.IsNullOrEmpty(message))
{
message += Environment.NewLine + exception.ToString();
}
else
{
message = exception.ToString();
}
}
bool changedColor;
string timestampPadding = "";
lock (syncObj)
{
if (!string.IsNullOrEmpty(TimestampFormat))
{
changedColor = false;
if (!DisableColors)
{
switch (logLevel)
{
case LogLevel.Trace:
Console.ForegroundColor = ConsoleColor.DarkGray;
changedColor = true;
break;
}
}
string timestamp = DateTime.Now.ToString(TimestampFormat) + " ";
Console.Write(timestamp);
timestampPadding = new string(' ', timestamp.Length);
if (changedColor)
{
Console.ResetColor();
}
}
changedColor = false;
if (!DisableColors)
{
switch (logLevel)
{
case LogLevel.Trace:
Console.ForegroundColor = ConsoleColor.DarkGray;
changedColor = true;
break;
case LogLevel.Information:
Console.ForegroundColor = ConsoleColor.DarkGreen;
changedColor = true;
break;
case LogLevel.Warning:
Console.ForegroundColor = ConsoleColor.Yellow;
changedColor = true;
break;
case LogLevel.Error:
Console.ForegroundColor = ConsoleColor.Black;
Console.BackgroundColor = ConsoleColor.Red;
changedColor = true;
break;
case LogLevel.Critical:
Console.ForegroundColor = ConsoleColor.White;
Console.BackgroundColor = ConsoleColor.Red;
changedColor = true;
break;
}
}
Console.Write(GetLogLevelString(logLevel));
if (changedColor)
{
Console.ResetColor();
}
changedColor = false;
if (!DisableColors)
{
switch (logLevel)
{
case LogLevel.Trace:
Console.ForegroundColor = ConsoleColor.DarkGray;
changedColor = true;
break;
}
}
Console.WriteLine(": " + (!string.IsNullOrEmpty(name) ? "[" + name + "] " : "") + message.Replace("\n", "\n " + timestampPadding));
if (changedColor)
{
Console.ResetColor();
}
}
}
private static string GetLogLevelString(LogLevel logLevel)
{
switch (logLevel)
{
case LogLevel.Trace:
return "trce";
case LogLevel.Debug:
return "dbug";
case LogLevel.Information:
return "info";
case LogLevel.Warning:
return "warn";
case LogLevel.Error:
return "fail";
case LogLevel.Critical:
return "crit";
default:
throw new ArgumentOutOfRangeException(nameof(logLevel));
}
}
}
}
......@@ -46,7 +46,7 @@ namespace UnitTests
using (var server = new MiniTestServer())
{
server.Start();
using (var client = new ModbusClient(IPAddress.Loopback, server.Port))
using (var client = new ModbusClient(IPAddress.Loopback, server.Port, new ConsoleLogger()))
{
await client.Connect();
Assert.IsTrue(client.IsConnected);
......@@ -54,6 +54,7 @@ namespace UnitTests
await server.Stop();
await client.ReadHoldingRegisters(0, 0, 1);
await EnsureWait(); // time to set all information
Assert.IsFalse(client.IsConnected);
server.Start();
......@@ -71,7 +72,7 @@ namespace UnitTests
using (var server = new MiniTestServer())
{
server.Start();
using (var client = new ModbusClient(IPAddress.Loopback, server.Port))
using (var client = new ModbusClient(IPAddress.Loopback, server.Port, new ConsoleLogger()))
{
client.Connected += (sender, args) =>
{
......@@ -95,6 +96,7 @@ namespace UnitTests
await server.Stop();
await client.ReadHoldingRegisters(0, 0, 1);
await EnsureWait(); // time to set all information
Assert.IsFalse(client.IsConnected);
await EnsureWait(); // get events raised
......@@ -131,7 +133,7 @@ namespace UnitTests
return new byte[] { request[0], request[1], 0, 0, 0, 3, 2, 129, 11 };
};
server.Start();
using (var client = new ModbusClient(IPAddress.Loopback, server.Port))
using (var client = new ModbusClient(IPAddress.Loopback, server.Port, new ConsoleLogger()))
{
await client.Connect();
Assert.IsTrue(client.IsConnected);
......@@ -179,7 +181,7 @@ namespace UnitTests
return new byte[] { request[0], request[1], 0, 0, 0, 5, 12, 1, 2, 205, 1 };
};
server.Start();
using (var client = new ModbusClient(IPAddress.Loopback, server.Port))
using (var client = new ModbusClient(IPAddress.Loopback, server.Port, new ConsoleLogger()))
{
await client.Connect();
Assert.IsTrue(client.IsConnected);
......@@ -212,7 +214,7 @@ namespace UnitTests
return new byte[] { request[0], request[1], 0, 0, 0, 4, 1, 2, 1, 3 };
};
server.Start();
using (var client = new ModbusClient(IPAddress.Loopback, server.Port))
using (var client = new ModbusClient(IPAddress.Loopback, server.Port, new ConsoleLogger()))
{
await client.Connect();
Assert.IsTrue(client.IsConnected);
......@@ -245,7 +247,7 @@ namespace UnitTests
return new byte[] { request[0], request[1], 0, 0, 0, 7, 5, 3, 4, 0, 3, 0, 7 };
};
server.Start();
using (var client = new ModbusClient(IPAddress.Loopback, server.Port))
using (var client = new ModbusClient(IPAddress.Loopback, server.Port, new ConsoleLogger()))
{
await client.Connect();
Assert.IsTrue(client.IsConnected);
......@@ -279,7 +281,7 @@ namespace UnitTests
return new byte[] { request[0], request[1], 0, 0, 0, 9, 3, 4, 6, 0, 123, 0, 0, 48, 57 };
};
server.Start();
using (var client = new ModbusClient(IPAddress.Loopback, server.Port))
using (var client = new ModbusClient(IPAddress.Loopback, server.Port, new ConsoleLogger()))
{
await client.Connect();
Assert.IsTrue(client.IsConnected);
......@@ -328,7 +330,7 @@ namespace UnitTests
return bytes.ToArray();
};
server.Start();
using (var client = new ModbusClient(IPAddress.Loopback, server.Port))
using (var client = new ModbusClient(IPAddress.Loopback, server.Port, new ConsoleLogger()))
{
await client.Connect();
Assert.IsTrue(client.IsConnected);
......@@ -375,7 +377,7 @@ namespace UnitTests
return bytes.ToArray();
};
server.Start();
using (var client = new ModbusClient(IPAddress.Loopback, server.Port))
using (var client = new ModbusClient(IPAddress.Loopback, server.Port, new ConsoleLogger()))
{
await client.Connect();
Assert.IsTrue(client.IsConnected);
......@@ -407,7 +409,7 @@ namespace UnitTests
return request;
};
server.Start();
using (var client = new ModbusClient(IPAddress.Loopback, server.Port))
using (var client = new ModbusClient(IPAddress.Loopback, server.Port, new ConsoleLogger()))
{
await client.Connect();
Assert.IsTrue(client.IsConnected);
......@@ -440,7 +442,7 @@ namespace UnitTests
return request;
};
server.Start();
using (var client = new ModbusClient(IPAddress.Loopback, server.Port))
using (var client = new ModbusClient(IPAddress.Loopback, server.Port, new ConsoleLogger()))
{
await client.Connect();
Assert.IsTrue(client.IsConnected);
......@@ -473,7 +475,7 @@ namespace UnitTests
return new byte[] { request[0], request[1], 0, 0, 0, 6, 4, 15, 0, 20, 0, 10 };
};
server.Start();
using (var client = new ModbusClient(IPAddress.Loopback, server.Port))
using (var client = new ModbusClient(IPAddress.Loopback, server.Port, new ConsoleLogger()))
{
await client.Connect();
Assert.IsTrue(client.IsConnected);
......@@ -514,7 +516,7 @@ namespace UnitTests
return new byte[] { request[0], request[1], 0, 0, 0, 6, 10, 16, 0, 2, 0, 2 };
};
server.Start();
using (var client = new ModbusClient(IPAddress.Loopback, server.Port))
using (var client = new ModbusClient(IPAddress.Loopback, server.Port, new ConsoleLogger()))
{
await client.Connect();
Assert.IsTrue(client.IsConnected);
......
......@@ -3,7 +3,6 @@
<PropertyGroup>
<TargetFramework>netcoreapp2.2</TargetFramework>
<IsPackable>false</IsPackable>
<LangVersion>7.1</LangVersion>
</PropertyGroup>
<ItemGroup>
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment