mirror of
https://github.com/esiur/esiur-dotnet.git
synced 2026-07-30 17:30:40 +00:00
234 lines
7.7 KiB
C#
234 lines
7.7 KiB
C#
using System.Net;
|
|
using Esiur.Core;
|
|
using Esiur.Net;
|
|
using Esiur.Net.Sockets;
|
|
using Esiur.Protocol;
|
|
using Esiur.Resource;
|
|
|
|
namespace Esiur.Tests.Unit;
|
|
|
|
public sealed class EpConnectionReconnectTests
|
|
{
|
|
[Fact]
|
|
public async Task InitialAutoReconnect_CreatesAFreshSocketAfterConnectFailure()
|
|
{
|
|
var failedSocket = new ConnectTestSocket(connects: false);
|
|
var connectedSocket = new ConnectTestSocket(connects: true);
|
|
var sockets = new Queue<ConnectTestSocket>(
|
|
new[] { failedSocket, connectedSocket });
|
|
var connection = new EpConnection
|
|
{
|
|
AutoReconnect = true,
|
|
ReconnectInterval = 0,
|
|
ClientSocketFactory = () => sockets.Dequeue(),
|
|
};
|
|
var warehouse = new Warehouse();
|
|
await warehouse.Put("client", connection);
|
|
|
|
var open = connection.Connect(
|
|
hostname: "localhost",
|
|
port: 10518,
|
|
domain: "test");
|
|
|
|
var completed = await Task.WhenAny(
|
|
connectedSocket.Began,
|
|
Task.Delay(TimeSpan.FromSeconds(2)));
|
|
Assert.True(
|
|
ReferenceEquals(completed, connectedSocket.Began),
|
|
$"Fresh socket was not started (remaining={sockets.Count}, " +
|
|
$"failed-connects={failedSocket.ConnectCount}, " +
|
|
$"replacement-connects={connectedSocket.ConnectCount}, " +
|
|
$"open-error={open.Exception?.Message}).");
|
|
|
|
Assert.Equal(1, failedSocket.ConnectCount);
|
|
Assert.Equal(1, connectedSocket.ConnectCount);
|
|
Assert.Equal(1, connectedSocket.BeginCount);
|
|
Assert.Same(connectedSocket, connection.Socket);
|
|
Assert.Empty(sockets);
|
|
|
|
connection.Destroy();
|
|
}
|
|
|
|
[Fact]
|
|
public async Task DestroyDuringPendingConnect_DoesNotAttachLateSocket()
|
|
{
|
|
var delayedSocket = new ConnectTestSocket(connects: null);
|
|
var connection = new EpConnection
|
|
{
|
|
ClientSocketFactory = () => delayedSocket,
|
|
};
|
|
var warehouse = new Warehouse();
|
|
await warehouse.Put("client", connection);
|
|
|
|
var open = connection.Connect(
|
|
hostname: "localhost",
|
|
port: 10518,
|
|
domain: "test");
|
|
|
|
await delayedSocket.ConnectInvoked.WaitAsync(TimeSpan.FromSeconds(2));
|
|
connection.Destroy();
|
|
delayedSocket.CompleteConnect(true);
|
|
|
|
Assert.Null(connection.Socket);
|
|
Assert.Equal(0, delayedSocket.BeginCount);
|
|
Assert.Equal(SocketState.Closed, delayedSocket.State);
|
|
Assert.NotNull(open.Exception);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task DisablingAutoReconnect_CompletesDelayedOpenAndAllowsAnotherConnect()
|
|
{
|
|
var failedSocket = new ConnectTestSocket(connects: false);
|
|
var factoryCalls = 0;
|
|
var connection = new EpConnection
|
|
{
|
|
AutoReconnect = true,
|
|
ReconnectInterval = 30,
|
|
ClientSocketFactory = () =>
|
|
{
|
|
factoryCalls++;
|
|
return failedSocket;
|
|
},
|
|
};
|
|
var warehouse = new Warehouse();
|
|
await warehouse.Put("client", connection);
|
|
|
|
var open = connection.Connect(
|
|
hostname: "localhost",
|
|
port: 10518,
|
|
domain: "test");
|
|
|
|
connection.AutoReconnect = false;
|
|
|
|
Assert.NotNull(open.Exception);
|
|
Assert.Equal(EpConnectionStatus.Closed, connection.Status);
|
|
Assert.Equal(1, factoryCalls);
|
|
|
|
var replacement = new ConnectTestSocket(connects: true);
|
|
var secondOpen = connection.Connect(replacement);
|
|
Assert.Null(secondOpen.Exception);
|
|
Assert.Same(replacement, connection.Socket);
|
|
|
|
connection.Destroy();
|
|
}
|
|
|
|
[Fact]
|
|
public async Task DisablingAutoReconnectDuringDisconnectDelay_PreventsReconnect()
|
|
{
|
|
var initialSocket = new ConnectTestSocket(connects: true);
|
|
var factoryCalls = 0;
|
|
var connection = new EpConnection
|
|
{
|
|
AutoReconnect = true,
|
|
ReconnectInterval = 1,
|
|
ClientSocketFactory = () =>
|
|
{
|
|
factoryCalls++;
|
|
return new ConnectTestSocket(connects: true);
|
|
},
|
|
};
|
|
var warehouse = new Warehouse();
|
|
await warehouse.Put("client", connection);
|
|
|
|
_ = connection.Connect(
|
|
initialSocket,
|
|
hostname: "localhost",
|
|
port: 10518,
|
|
domain: "test");
|
|
initialSocket.Disconnect();
|
|
connection.AutoReconnect = false;
|
|
|
|
await Task.Delay(TimeSpan.FromMilliseconds(1_200));
|
|
|
|
Assert.Equal(0, factoryCalls);
|
|
connection.Destroy();
|
|
}
|
|
|
|
private sealed class ConnectTestSocket : ISocket
|
|
{
|
|
private readonly bool? connects;
|
|
private readonly TaskCompletionSource began = new(
|
|
TaskCreationOptions.RunContinuationsAsynchronously);
|
|
private readonly TaskCompletionSource connectInvoked = new(
|
|
TaskCreationOptions.RunContinuationsAsynchronously);
|
|
private AsyncReply<bool>? pendingConnect;
|
|
private SocketState state = SocketState.Initial;
|
|
|
|
public ConnectTestSocket(bool? connects) => this.connects = connects;
|
|
|
|
public event DestroyedEvent? OnDestroy;
|
|
public SocketState State => state;
|
|
public INetworkReceiver<ISocket> Receiver { get; set; } = null!;
|
|
public IPEndPoint RemoteEndPoint { get; } =
|
|
new(IPAddress.Loopback, 10518);
|
|
public IPEndPoint LocalEndPoint { get; } =
|
|
new(IPAddress.Loopback, 50000);
|
|
public int ConnectCount { get; private set; }
|
|
public int BeginCount { get; private set; }
|
|
public Task Began => began.Task;
|
|
public Task ConnectInvoked => connectInvoked.Task;
|
|
|
|
public AsyncReply<bool> Connect(string hostname, ushort port)
|
|
{
|
|
ConnectCount++;
|
|
connectInvoked.TrySetResult();
|
|
if (connects is null)
|
|
{
|
|
pendingConnect = new AsyncReply<bool>();
|
|
return pendingConnect;
|
|
}
|
|
|
|
if (connects.Value)
|
|
{
|
|
state = SocketState.Established;
|
|
return new AsyncReply<bool>(true);
|
|
}
|
|
|
|
state = SocketState.Closed;
|
|
var reply = new AsyncReply<bool>();
|
|
reply.TriggerError(new InvalidOperationException("connect failed"));
|
|
return reply;
|
|
}
|
|
|
|
public void CompleteConnect(bool connected)
|
|
{
|
|
var reply = pendingConnect
|
|
?? throw new InvalidOperationException("No connection is pending.");
|
|
pendingConnect = null;
|
|
state = connected ? SocketState.Established : SocketState.Closed;
|
|
reply.Trigger(connected);
|
|
}
|
|
|
|
public void Disconnect()
|
|
{
|
|
state = SocketState.Closed;
|
|
Receiver.NetworkClose(this);
|
|
}
|
|
|
|
public bool Begin()
|
|
{
|
|
BeginCount++;
|
|
began.TrySetResult();
|
|
return state == SocketState.Established;
|
|
}
|
|
|
|
public AsyncReply<bool> BeginAsync() => new(Begin());
|
|
public AsyncReply<bool> SendAsync(byte[] message, int offset, int length) =>
|
|
new(state == SocketState.Established);
|
|
public void Send(byte[] message) { }
|
|
public void Send(byte[] message, int offset, int length) { }
|
|
public void Hold() { }
|
|
public void Unhold() { }
|
|
public void Close() => state = SocketState.Closed;
|
|
public void Destroy()
|
|
{
|
|
state = SocketState.Closed;
|
|
OnDestroy?.Invoke(this);
|
|
}
|
|
|
|
public AsyncReply<ISocket> AcceptAsync() =>
|
|
throw new NotSupportedException();
|
|
public ISocket Accept() => throw new NotSupportedException();
|
|
}
|
|
}
|